using System.Text.Json.Nodes; using decePubClient.E2E.Support; using Microsoft.Playwright; namespace decePubClient.E2E.Render { // One object of each kind from each platform, opened in decePub (/expand/{id}) by a persona it was for, and checked // against what its author put in it: the text, the content warning, every picture's alt text and its proxied source, // the poll's options, the quoted post. town.sh expect says what each object holds and who may read it. public sealed class RenderMatrixTests(Browser browser) { static readonly Lazy Expected = new(() => Pasture.Town("expect").GetAwaiter().GetResult().AsArray()); public static TheoryData Samples() { var data = new TheoryData(); var seen = new HashSet(); var personas = Pasture.Personas().Select(p => p.UserName).ToHashSet(); foreach (var item in Expected.Value) { var key = $"{item!["origin"]}|{item["kind"]}|{item["visibility"]}"; if (!item["readers"]!.AsArray().Any(r => personas.Contains(r!.GetValue())) || !seen.Add(key)) continue; data.Add(item["origin"]!.GetValue(), $"{item["kind"]}.{item["visibility"]}", item["ref"]!.GetValue()); } return data; } [Theory] [MemberData(nameof(Samples))] public async Task Renders_as_written(string origin, string kind, string reference) { var item = Expected.Value.First(i => i!["ref"]!.GetValue() == reference)!; var personas = Pasture.Personas(); var reader = item["readers"]!.AsArray().Select(r => r!.GetValue()) .Select(name => personas.FirstOrDefault(p => p.UserName == name)).First(p => p is not null); await Results.Cell($"client.render.{kind}", origin, reference, async () => { await using var session = await SignIn.As(browser, reader); await session.Page.GotoAsync($"{Pasture.Site}/expand/{item["id"]!.GetValue()}"); var card = session.Page.Locator($"article[data-uri='{item["uri"]!.GetValue()}']").First; await card.WaitForAsync(new() { Timeout = 30000 }); var marker = item["marker"]!.GetValue(); if (item["cw"]?.GetValue() is { } cw) { await Assertions.Expect(card).ToContainTextAsync(cw); Assert.False(await card.GetByText(marker).IsVisibleAsync(), "the text shows before its content warning is opened"); await card.GetByText(cw).First.ClickAsync(); } await Assertions.Expect(card.Locator(".post-content").First).ToContainTextAsync(marker); // each reveal re-renders the media, so reveal the first hidden one until none is left var hidden = card.GetByRole(AriaRole.Button).Filter(new() { HasText = "Sensitive media" }); for (var i = 0; i < 8 && await hidden.CountAsync() > 0; i++) await hidden.First.ClickAsync(); foreach (var alt in item["alts"]!.AsArray().Select(a => a!.GetValue())) { var picture = card.Locator($"img[alt='{alt}']").First; await Assertions.Expect(picture).ToBeVisibleAsync(); var src = new Uri(await picture.GetAttributeAsync("src") ?? ""); Assert.Equal("privapub.test", src.Host); Assert.True(await picture.EvaluateAsync("img => img.complete && img.naturalWidth > 0"), $"picture '{alt}' did not load"); } if (item["poll"] is JsonArray options) foreach (var option in options) await Assertions.Expect(card).ToContainTextAsync(option!.GetValue()); if (item["quote"]?.GetValue() is { } quoted) { var quotedRef = Expected.Value.FirstOrDefault(i => i!["uri"]!.GetValue() == quoted)?["marker"]?.GetValue(); Assert.True(await card.Locator("article").CountAsync() > 0, "the quoted post is not shown"); if (quotedRef is not null) await Assertions.Expect(card.Locator("article").First).ToContainTextAsync(quotedRef); } if (item["title"]?.GetValue() is { } title) await Assertions.Expect(card).ToContainTextAsync(title); await session.AssertGuards(); }); } } }