Files

115 lines
6.5 KiB
C#
Raw Permalink Normal View History

using System.Text.Json.Nodes;
using decePubClient.E2E.Support;
using Microsoft.Playwright;
namespace decePubClient.E2E.Actions
{
// A list in decePub: made on /lists, filled with an account the persona follows (found by its name among the accounts
// it follows), read as the feed's Lists tab, made exclusive and deleted. Each step is checked against PrivaPub's own
// answer to the persona's token, so the page and the server agree on the list, its members and its posts.
public sealed class ListTests(Browser browser)
{
static Task<JsonNode> Api(Pasture.Persona persona, string path, HttpMethod method = null, JsonObject body = null) =>
PersonaApi.Call(persona, path, method, body);
// an account the persona follows that has written something
static async Task<JsonNode> Followed(Pasture.Persona persona) =>
(await Api(persona, $"/api/v1/accounts/{persona.LocalId}/following?limit=80"))!.AsArray()
.FirstOrDefault(a => a!["statuses_count"]!.GetValue<int>() > 0 && !a["acct"]!.GetValue<string>().StartsWith("st_"));
[Fact]
public async Task An_account_joins_and_leaves_a_list_from_its_profile()
{
var persona = Pasture.Personas().First();
var member = await Followed(persona);
Assert.SkipWhen(member is null, $"{persona.UserName} follows nobody who has posted");
var memberId = member!["id"]!.GetValue<string>();
var listId = (await Api(persona, "/api/v1/lists", HttpMethod.Post, new JsonObject { ["title"] = $"e2e {Guid.NewGuid():N}"[..12] }))!["id"]!.GetValue<string>();
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
try
{
await Results.Cell("client.action.list-member", "privapub", persona.UserName, async () =>
{
async Task<bool> InList() =>
(await Api(persona, $"/api/v1/lists/{listId}/accounts"))!.AsArray().Any(a => a!["id"]!.GetValue<string>() == memberId);
await page.GotoAsync($"{Pasture.Site}/account/{memberId}");
var profile = page.Locator("article[data-account-id]").First;
await profile.WaitForAsync(new() { Timeout = 30000 });
await profile.GetByRole(AriaRole.Button, new() { Name = "Lists" }).ClickAsync();
var box = profile.Locator($"[data-list-id='{listId}'] input");
await box.CheckAsync();
Assert.True(await Pasture.Eventually(InList, 15), "the profile's checkbox never put the account in the list");
await box.UncheckAsync();
Assert.True(await Pasture.Eventually(async () => !await InList(), 15), "the profile's checkbox never took the account out");
await session.AssertGuards();
}, page);
}
finally
{
await Api(persona, $"/api/v1/lists/{listId}", HttpMethod.Delete);
}
}
[Fact]
public async Task A_list_is_made_filled_read_as_a_feed_and_deleted()
{
var persona = Pasture.Personas().First();
var member = await Followed(persona);
Assert.SkipWhen(member is null, $"{persona.UserName} follows nobody who has posted");
var memberId = member!["id"]!.GetValue<string>();
var title = $"e2e {Guid.NewGuid():N}"[..12];
// lists an earlier failed run left behind
foreach (var left in (await Api(persona, "/api/v1/lists"))!.AsArray().Where(l => l!["title"]!.GetValue<string>().StartsWith("e2e ")))
await Api(persona, $"/api/v1/lists/{left!["id"]!.GetValue<string>()}", HttpMethod.Delete);
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
await Results.Cell("client.action.list", "privapub", persona.UserName, async () =>
{
await page.GotoAsync($"{Pasture.Site}/lists");
await page.Locator("[data-list-new] input, input[data-list-new]").First.FillAsync(title);
await page.GetByRole(AriaRole.Button, new() { Name = "Make a list" }).ClickAsync();
var article = page.Locator("article[data-list-id]").Filter(new() { HasText = title });
await article.WaitForAsync(new() { Timeout = 15000 });
var listId = await article.GetAttributeAsync("data-list-id");
Assert.Contains((await Api(persona, "/api/v1/lists"))!.AsArray(), l => l!["id"]!.GetValue<string>() == listId);
// the list opens once made: find the member among the accounts followed, and add it
await article.Locator("[data-list-find] input, input[data-list-find]").First.FillAsync(member["username"]!.GetValue<string>());
await article.Locator("[data-list-find] input, input[data-list-find]").First.PressAsync("Enter");
var candidate = article.Locator($"[data-candidate-id='{memberId}']");
await candidate.WaitForAsync(new() { Timeout = 15000 });
await candidate.GetByRole(AriaRole.Button, new() { Name = "Add" }).ClickAsync();
await article.Locator($"[data-member-id='{memberId}']").WaitForAsync(new() { Timeout = 15000 });
Assert.Equal([memberId], (await Api(persona, $"/api/v1/lists/{listId}/accounts"))!.AsArray().Select(a => a!["id"]!.GetValue<string>()));
// read as a feed: the cards are the list timeline's first page, as PrivaPub answers it
await article.GetByRole(AriaRole.Button, new() { Name = "Read the list" }).ClickAsync();
await page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
var expected = (await Api(persona, $"/api/v1/timelines/list/{listId}?limit=20"))!.AsArray()
.Select(s => (s!["reblog"] ?? s)["uri"]!.GetValue<string>()).ToHashSet();
var shown = await page.Locator("article[data-uri]").EvaluateAllAsync<string[]>("cards => cards.map(c => c.dataset.uri)");
Assert.NotEmpty(expected);
Assert.Subset(expected, shown.ToHashSet());
// exclusive, then deleted
await page.GotoAsync($"{Pasture.Site}/lists");
article = page.Locator($"article[data-list-id='{listId}']");
await article.GetByRole(AriaRole.Button, new() { Name = "Edit" }).ClickAsync();
await article.GetByLabel("Keep its members out of the home feed").CheckAsync();
await article.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
Assert.True(await Pasture.Eventually(async () => (await Api(persona, $"/api/v1/lists/{listId}"))?["exclusive"]?.GetValue<bool>() == true, 15),
"PrivaPub never made the list exclusive");
// the icon's glyph (CSS content) is part of a button's name, so names are matched as substrings
await article.GetByRole(AriaRole.Button, new() { Name = "Delete" }).First.ClickAsync();
await article.GetByRole(AriaRole.Button, new() { Name = "Delete it for good" }).ClickAsync();
await Assertions.Expect(article).ToHaveCountAsync(0, new() { Timeout = 15000 });
Assert.Null(await Api(persona, $"/api/v1/lists/{listId}"));
await session.AssertGuards();
}, page);
}
}
}