Filters: made on /filters, applied by each card where it is
The persona's filters (PrivaPub's /api/v2/filters): made, given words and whole-word rules, contexts, an action and an expiry on /filters (linked from Settings), and deleted; posts filtered one by one can be taken out. Every status says which filters it matches; each page tells its cards where they are (home and lists, notifications, public feeds, the thread around the opened post, profiles), and a card leaves its post out (hide), folds it behind the filter's title with a way to show it anyway (warn), or blurs its media (blur). The e2e test folds and then hides a post of the persona's own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
e2491de16e
commit
9a752d9288
18 files changed
+1182
-240
No files matched your search
@@ -0,0 +1,72 @@
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using decePubClient.E2E.Support;
|
||||
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace decePubClient.E2E.Actions
|
||||
{
|
||||
// A filter in decePub: made on /filters with a word only one post has, which the home feed then folds behind the
|
||||
// filter's title (warn) and leaves out once the filter hides; PrivaPub keeps what the page sent.
|
||||
public sealed class FilterTests(Browser browser)
|
||||
{
|
||||
[Fact]
|
||||
public async Task A_filters_word_folds_then_hides_a_post_in_the_home_feed()
|
||||
{
|
||||
var persona = Pasture.Personas().First();
|
||||
var word = $"zq{Guid.NewGuid():N}"[..10];
|
||||
var title = $"e2e {word}";
|
||||
foreach (var left in (await PersonaApi.Call(persona, "/api/v2/filters"))!.AsArray().Where(f => f!["title"]!.GetValue<string>().StartsWith("e2e ")))
|
||||
await PersonaApi.Call(persona, $"/api/v2/filters/{left!["id"]!.GetValue<string>()}", HttpMethod.Delete);
|
||||
var post = (await PersonaApi.Call(persona, "/api/v1/statuses", HttpMethod.Post, new JsonObject { ["status"] = $"a post about {word}", ["visibility"] = "unlisted" }))!;
|
||||
var uri = post["uri"]!.GetValue<string>();
|
||||
await using var session = await SignIn.As(browser, persona);
|
||||
var page = session.Page;
|
||||
try
|
||||
{
|
||||
await Results.Cell("client.action.filter", "privapub", persona.UserName, async () =>
|
||||
{
|
||||
await page.GotoAsync($"{Pasture.Site}/filters");
|
||||
await page.Locator("input[data-filter-new]").FillAsync(title);
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Make a filter" }).ClickAsync();
|
||||
var article = page.Locator("article[data-filter-id]").Filter(new() { HasText = title });
|
||||
await article.Locator("input[data-filter-keyword-new]").FillAsync(word);
|
||||
await article.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
|
||||
var filterId = await article.GetAttributeAsync("data-filter-id");
|
||||
Assert.True(await Pasture.Eventually(async () => (await PersonaApi.Call(persona, $"/api/v2/filters/{filterId}"))?["keywords"]?.AsArray()
|
||||
.Any(k => k!["keyword"]!.GetValue<string>() == word) == true, 15), "PrivaPub never got the filter's word");
|
||||
|
||||
// warn: the card is folded behind the filter's title, and opens on request
|
||||
// the feed opens on its default tab, Home, when no tab is remembered
|
||||
await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')");
|
||||
await page.GotoAsync($"{Pasture.Site}/");
|
||||
var card = page.Locator($"article[data-uri='{uri}']");
|
||||
await card.WaitForAsync(new() { Timeout = 30000 });
|
||||
await Assertions.Expect(card.Locator("[data-filtered]")).ToContainTextAsync(title);
|
||||
await Assertions.Expect(card).Not.ToContainTextAsync($"a post about {word}");
|
||||
await card.GetByRole(AriaRole.Button, new() { Name = "Show anyway" }).ClickAsync();
|
||||
await Assertions.Expect(card).ToContainTextAsync($"a post about {word}");
|
||||
|
||||
// hide: the card is left out
|
||||
await page.GotoAsync($"{Pasture.Site}/filters");
|
||||
article = page.Locator($"article[data-filter-id='{filterId}']");
|
||||
await article.GetByRole(AriaRole.Button, new() { Name = "Edit" }).ClickAsync();
|
||||
await article.Locator("select").First.SelectOptionAsync("Hide");
|
||||
await article.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
|
||||
Assert.True(await Pasture.Eventually(async () => (await PersonaApi.Call(persona, $"/api/v2/filters/{filterId}"))?["filter_action"]?
|
||||
.GetValue<string>() == "hide", 15), "PrivaPub never made the filter hide");
|
||||
await page.GotoAsync($"{Pasture.Site}/");
|
||||
await page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
|
||||
await Assertions.Expect(page.Locator($"article[data-uri='{uri}']")).ToHaveCountAsync(0);
|
||||
await session.AssertGuards();
|
||||
}, page);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PersonaApi.Call(persona, $"/api/v1/statuses/{post["id"]!.GetValue<string>()}", HttpMethod.Delete);
|
||||
foreach (var made in (await PersonaApi.Call(persona, "/api/v2/filters"))!.AsArray().Where(f => f!["title"]!.GetValue<string>() == title))
|
||||
await PersonaApi.Call(persona, $"/api/v2/filters/{made!["id"]!.GetValue<string>()}", HttpMethod.Delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using decePubClient.E2E.Support;
|
||||
@@ -13,19 +11,8 @@ namespace decePubClient.E2E.Actions
|
||||
// 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 async Task<JsonNode> Api(Pasture.Persona persona, string path, HttpMethod method = null, JsonObject body = null)
|
||||
{
|
||||
using var http = Pasture.Http();
|
||||
using var request = new HttpRequestMessage(method ?? HttpMethod.Get, Pasture.Api + path);
|
||||
if (body is not null)
|
||||
request.Content = new StringContent(body.ToJsonString(), System.Text.Encoding.UTF8, "application/json");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", persona.Token);
|
||||
using var response = await http.SendAsync(request);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return JsonNode.Parse(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
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) =>
|
||||
|
||||
Reference in new issue
Block a user