Files
decePubClient/tests/e2e/Actions/DiscoveryTests.cs
T
thepraandClaude Opus 5.5 6d5cf17723 Posts for later, hashtags followed, and Explore
The composer's clock button gives a post a time (scheduled_at, at least five
minutes ahead; located and group posts have none), and /scheduled moves or
drops what waits. A hashtag's feed tab shows its week and follows it, so its
public posts come home. Explore (/explore) shows what PrivaPub says is
trending, hashtags, posts and links from public posts it holds, the
directory by activity or by joining, and the hashtags the persona follows.
DiscoveryService does the calls; the e2e tests check each against PrivaPub's
own answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-05 03:53:51 +02:00

111 lines
5.8 KiB
C#

using System.Text.Json.Nodes;
using decePubClient.E2E.Support;
using Microsoft.Playwright;
namespace decePubClient.E2E.Actions
{
// Posts written for later, hashtags followed and what Explore shows, each checked against PrivaPub's own answer
public sealed class DiscoveryTests(Browser browser)
{
[Fact]
public async Task A_post_scheduled_in_the_composer_waits_is_moved_and_is_dropped()
{
var persona = Pasture.Personas().First();
var text = $"later {Guid.NewGuid():N}"[..20];
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
await Results.Cell("client.action.schedule", "privapub", persona.UserName, async () =>
{
await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')");
await page.GotoAsync($"{Pasture.Site}/");
await page.GetByRole(AriaRole.Button, new() { Name = "Post" }).First.ClickAsync();
await page.Locator("textarea").First.FillAsync(text);
await page.Locator("button[data-schedule]").ClickAsync();
await page.Locator("input[data-schedule-at]").FillAsync(DateTime.Now.AddHours(2).ToString("yyyy-MM-ddTHH:mm"));
await page.GetByRole(AriaRole.Button, new() { Name = "Schedule" }).ClickAsync();
JsonNode waiting = null;
Assert.True(await Pasture.Eventually(async () => (waiting = (await PersonaApi.Call(persona, "/api/v1/scheduled_statuses"))!.AsArray()
.FirstOrDefault(s => s!["params"]!["text"]?.GetValue<string>() == text)) is not null, 15), "PrivaPub never kept the scheduled post");
var id = waiting!["id"]!.GetValue<string>();
Assert.DoesNotContain((await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=10"))!.AsArray(),
s => s!["content"]!.GetValue<string>().Contains(text));
await page.GotoAsync($"{Pasture.Site}/scheduled");
var item = page.Locator($"article[data-scheduled-id='{id}']");
await Assertions.Expect(item).ToContainTextAsync(text, new() { Timeout = 15000 });
var later = DateTime.Now.AddHours(5);
await item.Locator("input[type=datetime-local]").FillAsync(later.ToString("yyyy-MM-ddTHH:mm"));
await item.GetByRole(AriaRole.Button, new() { Name = "Move" }).ClickAsync();
Assert.True(await Pasture.Eventually(async () =>
(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"))?["scheduled_at"]?.GetValue<DateTime>().ToUniversalTime()
is { } at && Math.Abs((at - later.ToUniversalTime()).TotalMinutes) < 2, 15), "PrivaPub never moved the post");
await item.GetByRole(AriaRole.Button, new() { Name = "Never publish it" }).ClickAsync();
await Assertions.Expect(item).ToHaveCountAsync(0, new() { Timeout = 15000 });
Assert.Null(await PersonaApi.Call(persona, $"/api/v1/scheduled_statuses/{id}"));
await session.AssertGuards();
}, page);
}
[Fact]
public async Task A_hashtag_is_followed_from_its_feed_tab_and_listed_in_explore()
{
var persona = Pasture.Personas().First();
var tag = $"e2etag{Guid.NewGuid():N}"[..14];
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
try
{
await Results.Cell("client.action.follow-tag", "privapub", persona.UserName, async () =>
{
// a reader finds the hashtag through search, which opens it as a tab of the feed
await page.GotoAsync($"{Pasture.Site}/search?q={Uri.EscapeDataString("#" + tag)}");
await page.GetByRole(AriaRole.Button, new() { Name = "#" + tag }).ClickAsync(new() { Timeout = 30000 });
var follow = page.Locator("button[data-tag-follow]");
await Assertions.Expect(follow).ToContainTextAsync($"Follow #{tag}", new() { Timeout = 30000 });
await follow.ClickAsync();
await Assertions.Expect(follow).ToContainTextAsync($"Stop following #{tag}", new() { Timeout = 15000 });
Assert.True((await PersonaApi.Call(persona, $"/api/v1/tags/{tag}"))!["following"]!.GetValue<bool>());
await page.GotoAsync($"{Pasture.Site}/explore");
await page.GetByRole(AriaRole.Tab, new() { Name = "Followed hashtags" }).ClickAsync();
var row = page.Locator($"[data-tag='{tag}']");
await row.WaitForAsync(new() { Timeout = 15000 });
await row.GetByRole(AriaRole.Button, new() { Name = "Unfollow" }).ClickAsync();
await Assertions.Expect(row).ToHaveCountAsync(0, new() { Timeout = 15000 });
Assert.False((await PersonaApi.Call(persona, $"/api/v1/tags/{tag}"))!["following"]!.GetValue<bool>());
await session.AssertGuards();
}, page);
}
finally
{
await PersonaApi.Call(persona, $"/api/v1/tags/{tag}/unfollow", HttpMethod.Post);
}
}
[Fact]
public async Task Explore_shows_what_privapub_says_is_trending_and_its_directory()
{
var persona = Pasture.Personas().First();
var trending = (await PersonaApi.Call(persona, "/api/v1/trends/tags?limit=20"))!.AsArray().Select(t => t!["name"]!.GetValue<string>()).ToList();
var directory = (await PersonaApi.Call(persona, "/api/v1/directory?order=active&local=false&limit=40"))!.AsArray().Select(a => a!["id"]!.GetValue<string>()).ToList();
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
await Results.Cell("client.render.explore", "privapub", persona.UserName, async () =>
{
await page.GotoAsync($"{Pasture.Site}/explore");
await page.Locator("[data-tag], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
var shownTags = await page.Locator("[data-tag]").EvaluateAllAsync<string[]>("rows => rows.map(r => r.dataset.tag)");
Assert.Equal(trending, shownTags);
await page.GetByRole(AriaRole.Tab, new() { Name = "People" }).ClickAsync();
await page.Locator("[data-account], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
var shownPeople = await page.Locator("[data-account]").EvaluateAllAsync<string[]>("rows => rows.map(r => r.dataset.account)");
Assert.Equal(directory, shownPeople);
await session.AssertGuards();
}, page);
}
}
}