A mention or hashtag being written at the end of the words is offered, once typing pauses, the accounts or tags PrivaPub already knows (/api/v2/search, never resolving); a click writes it out. e2e: a town persona's handle, begun, is offered and completed. CLAUDE.md also says that FEP-5624's moderated comments (PeerTube) show as pending on a card, as GoToSocial's policies do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
141 lines
6.6 KiB
C#
141 lines
6.6 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);
|
|
}
|
|
}
|
|
|
|
// a mention being written is offered the accounts PrivaPub knows, and a click writes the whole handle out
|
|
[Fact]
|
|
public async Task A_mention_being_written_is_completed_from_the_accounts_the_server_knows()
|
|
{
|
|
var personas = Pasture.Personas();
|
|
Assert.SkipWhen(personas.Count < 2, "the town has one persona");
|
|
var (author, other) = (personas[0], personas[1]);
|
|
var acct = (await PersonaApi.Call(other, "/api/v1/accounts/verify_credentials"))!["acct"]!.GetValue<string>();
|
|
await using var session = await SignIn.As(browser, author);
|
|
var page = session.Page;
|
|
await Results.Cell("client.action.mention-suggestion", "privapub", author.UserName, async () =>
|
|
{
|
|
await OpenHome(page);
|
|
await page.GetByRole(AriaRole.Button, new() { Name = "Post" }).First.ClickAsync();
|
|
var box = page.Locator("textarea[data-compose]").First;
|
|
await box.FillAsync("hello @" + acct[..Math.Min(4, acct.Length)]);
|
|
await page.Locator($"[data-suggestions] [data-suggestion='@{acct}']").ClickAsync(new() { Timeout = 15000 });
|
|
await Assertions.Expect(box).ToHaveValueAsync($"hello @{acct} ");
|
|
await Assertions.Expect(page.Locator("[data-suggestions]")).ToHaveCountAsync(0);
|
|
await session.AssertGuards();
|
|
}, page);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
}
|
|
}
|
|
}
|