The card marks an edited post with a button that opens its versions, newest first (/statuses/:id/history), each with its time, warning and words; PrivaPub now has a remote post's earlier versions from Pleroma's formerRepresentations too. The composer's e2e test opens the history after its edit. The wall test retries smuser's post while Smithereen has not yet had the persona's Accept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
118 lines
5.4 KiB
C#
118 lines
5.4 KiB
C#
using System.Text.Json.Nodes;
|
|
|
|
using decePubClient.E2E.Support;
|
|
|
|
using Microsoft.Playwright;
|
|
|
|
namespace decePubClient.E2E.Actions
|
|
{
|
|
// The composer beyond words: a poll in a language picked, the persona's own post edited and its history shown, and a
|
|
// quote, each checked on PrivaPub
|
|
public sealed class ComposerTests(Browser browser)
|
|
{
|
|
static async Task OpenHome(IPage page)
|
|
{
|
|
await page.GotoAsync($"{Pasture.Site}/");
|
|
await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')");
|
|
await page.GotoAsync($"{Pasture.Site}/");
|
|
await page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
|
|
}
|
|
|
|
static async Task<JsonNode> Latest(Pasture.Persona persona) =>
|
|
(await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=1"))!.AsArray().FirstOrDefault();
|
|
|
|
[Fact]
|
|
public async Task A_poll_is_published_from_the_composer()
|
|
{
|
|
var persona = Pasture.Personas().First();
|
|
var text = $"which one {Guid.NewGuid():N}"[..18];
|
|
await using var session = await SignIn.As(browser, persona);
|
|
var page = session.Page;
|
|
string made = null;
|
|
try
|
|
{
|
|
await Results.Cell("client.action.poll", "privapub", persona.UserName, async () =>
|
|
{
|
|
await OpenHome(page);
|
|
await page.GetByRole(AriaRole.Button, new() { Name = "Post" }).First.ClickAsync();
|
|
await page.Locator("textarea[data-compose]").First.FillAsync(text);
|
|
await page.Locator("button[data-poll-toggle]").ClickAsync();
|
|
var choices = page.Locator("[data-poll-editor] input[type=text]");
|
|
await choices.Nth(0).FillAsync("tea");
|
|
await choices.Nth(1).FillAsync("coffee");
|
|
// and in the language picked
|
|
await page.Locator("[data-compose-language]").First.SelectOptionAsync("it");
|
|
await page.GetByRole(AriaRole.Button, new() { Name = "Post" }).Last.ClickAsync();
|
|
JsonNode latest = null;
|
|
Assert.True(await Pasture.Eventually(async () => (latest = await Latest(persona))?["content"]?.GetValue<string>().Contains(text) == true, 15),
|
|
"the poll was never published");
|
|
made = latest!["id"]!.GetValue<string>();
|
|
Assert.Equal(new[] { "tea", "coffee" }, latest["poll"]!["options"]!.AsArray().Select(o => o!["title"]!.GetValue<string>()));
|
|
Assert.Equal("it", latest["language"]?.GetValue<string>());
|
|
await session.AssertGuards();
|
|
}, page);
|
|
}
|
|
finally
|
|
{
|
|
if (made is not null)
|
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{made}", HttpMethod.Delete);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task The_personas_own_post_is_edited_and_quoted_from_its_card()
|
|
{
|
|
var persona = Pasture.Personas().First();
|
|
var text = $"first words {Guid.NewGuid():N}"[..20];
|
|
var post = (await PersonaApi.Call(persona, "/api/v1/statuses", HttpMethod.Post, new JsonObject { ["status"] = text, ["visibility"] = "public" }))!;
|
|
var id = post["id"]!.GetValue<string>();
|
|
string quote = null;
|
|
await using var session = await SignIn.As(browser, persona);
|
|
var page = session.Page;
|
|
try
|
|
{
|
|
await Results.Cell("client.action.edit-quote", "privapub", persona.UserName, async () =>
|
|
{
|
|
await OpenHome(page);
|
|
var card = page.Locator($"article[data-uri='{post["uri"]!.GetValue<string>()}']").First;
|
|
await card.WaitForAsync(new() { Timeout = 30000 });
|
|
|
|
await card.Locator("footer [title='Other'] > [role=button]").ClickAsync();
|
|
await card.Locator("button[data-edit]").ClickAsync();
|
|
var box = card.Locator("textarea[data-compose]");
|
|
await Assertions.Expect(box).ToHaveValueAsync(text, new() { Timeout = 10000 });
|
|
await box.FillAsync(text + " (edited)");
|
|
await card.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
|
|
await Assertions.Expect(card).ToContainTextAsync(text + " (edited)", new() { Timeout = 10000 });
|
|
Assert.Contains("(edited)", (await PersonaApi.Call(persona, $"/api/v1/statuses/{id}"))!["content"]!.GetValue<string>());
|
|
await card.Locator("button[data-history]").ClickAsync();
|
|
var versions = card.Locator("[data-history-list] [data-version]");
|
|
await Assertions.Expect(versions).ToHaveCountAsync(2, new() { Timeout = 10000 });
|
|
await Assertions.Expect(versions.First).ToContainTextAsync(text + " (edited)");
|
|
await Assertions.Expect(versions.Last).ToContainTextAsync(text);
|
|
await Assertions.Expect(versions.Last).Not.ToContainTextAsync("(edited)");
|
|
await page.Keyboard.PressAsync("Escape");
|
|
await Assertions.Expect(card.Locator("[data-history-list]")).ToHaveCountAsync(0);
|
|
|
|
await card.Locator("footer [title='Other'] > [role=button]").ClickAsync();
|
|
await card.Locator("button[data-quote]").ClickAsync();
|
|
await card.Locator("textarea[data-compose]").FillAsync("quoting myself");
|
|
await card.GetByRole(AriaRole.Button, new() { Name = "Quote" }).Last.ClickAsync();
|
|
JsonNode latest = null;
|
|
Assert.True(await Pasture.Eventually(async () => (latest = await Latest(persona))?["content"]?.GetValue<string>().Contains("quoting myself") == true, 15),
|
|
"the quote was never published");
|
|
quote = latest!["id"]!.GetValue<string>();
|
|
Assert.Equal(id, (latest["quote"]?["quoted_status"]?["id"] ?? latest["quote"]?["quoted_status_id"])?.GetValue<string>());
|
|
await session.AssertGuards();
|
|
}, page);
|
|
}
|
|
finally
|
|
{
|
|
if (quote is not null)
|
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{quote}", HttpMethod.Delete);
|
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{id}", HttpMethod.Delete);
|
|
}
|
|
}
|
|
}
|
|
}
|