commit e0676e48592b6c6879a5a034a3e7be1d7f3fb0aa
parent 0e59ad100944b060f3942fc36028263249a0fdf5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 17:44:18 -0400
export(e2e): search-in after the review — a "Posts" leaf with Posts unticked, a tag chip finishes, no video type kept, the refusal described
search-in.spec: case 9 now also runs a "Posts" leaf with Transcripts and
Posts unticked (L3, M2); a plain query under a tag chip finishes with its
results, and with posts alone says "No matching videos." (M1); with Videos
and Livestreams unticked a plain query reads the posts and finishes (M1);
the refusal line is always there and empty, the disabled Search button is
described by it, and Save as is disabled with it (L1, L2). posts-search:
unticking Posts leaves the "Posts" leaf's two posts (M2).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 70 insertions(+), 7 deletions(-)
diff --git a/export/e2e/posts-search.spec.ts b/export/e2e/posts-search.spec.ts
@@ -162,7 +162,7 @@ test.describe("social-post corpus — search", () => {
).toBeVisible();
});
- test("the Posts box switches the corpus off", async ({ page }) => {
+ test("the Posts box leaves a \"Posts\" leaf reading posts", async ({ page }) => {
const tree: SGroup = {
k: "g",
o: "OR",
@@ -181,8 +181,8 @@ test.describe("social-post corpus — search", () => {
]);
// The Posts box sits under Search in (release 16; it was in the Type row,
- // same key `nop`). Unticked, it also keeps the posts corpus out of the
- // scope a "Posts" leaf reads, as it always has.
+ // same key `nop`) and says what a plain query reads. A leaf whose scope is
+ // "Posts" was asked for by name, and still reads its two posts unticked.
await page.getByRole("checkbox", { name: "Posts" }).uncheck();
await page.getByTestId("search-submit").click();
@@ -190,6 +190,8 @@ test.describe("social-post corpus — search", () => {
TRANSCRIPT_ONLY_SLUG,
CHAT_SMALL_SLUG,
CHAT_LARGE_SLUG,
+ POST_ROOT_SLUG,
+ POST_REPLY_SLUG,
]);
});
});
diff --git a/export/e2e/search-in.spec.ts b/export/e2e/search-in.spec.ts
@@ -1,6 +1,7 @@
import { expect, test, type Page } from "@playwright/test";
import {
CHANNEL_SLUG,
+ TAG_COLLAB,
POST_CHANNEL_SLUG,
POST_REPLY_ID,
POST_ROOT_ID,
@@ -8,7 +9,7 @@ import {
VIDEO_CHAT_SMALL,
VIDEO_TRANSCRIPT_ONLY,
} from "./fixtures/data";
-import { installRoutes, openFilters, showAll } from "./helpers";
+import { installRoutes, installTagRoutes, openFilters, showAll } from "./helpers";
// Release 16, slice CK: the Filters panel's "Search in" row — Transcripts,
// Posts, Live chat — says what a plain query (a "transcripts" leaf) reads.
@@ -52,6 +53,11 @@ async function search(page: Page, q: string) {
await page.getByTestId("search-submit").click();
}
+// The search has finished: the progress line has lost its ellipsis.
+async function expectFinished(page: Page) {
+ await expect(page.getByText(/^searched \d+\/\d+$/)).toBeVisible({ timeout: 15_000 });
+}
+
test.describe("Search in", () => {
test.beforeEach(async ({ page }) => {
await installRoutes(page);
@@ -74,7 +80,8 @@ test.describe("Search in", () => {
await expect(hint).toBeVisible();
await box(page, "Live chat").check();
await expect(hint).toHaveCount(0);
- await expect(page.getByTestId("search-in-empty")).toHaveCount(0);
+ // The refusal line is always there, and empty unless Search is refused.
+ await expect(page.getByTestId("search-in-empty")).toBeEmpty();
});
test("a plain query reads posts by default; unticking Posts drops them", async ({
@@ -175,6 +182,11 @@ test.describe("Search in", () => {
const empty = page.getByTestId("search-in-empty");
await expect(submit).toBeDisabled();
await expect(empty).toHaveText("Search in: pick at least one");
+ await expect(empty).toHaveAttribute("role", "status");
+ // The disabled button says why, to a screen reader as well as on hover.
+ await expect(submit).toHaveAccessibleDescription("Search in: pick at least one");
+ // A save commits the draft, so Save as… is refused the same way.
+ await expect(page.getByRole("button", { name: "Save as…" })).toBeDisabled();
await expect(page.getByText("Press Enter or click Search to apply")).toHaveCount(0);
// Enter does not commit it either.
await leafInput(page).press("Enter");
@@ -184,7 +196,9 @@ test.describe("Search in", () => {
await box(page, "Live chat").check();
await expect(submit).toBeEnabled();
- await expect(empty).toHaveCount(0);
+ await expect(empty).toBeEmpty();
+ await expect(submit).toHaveAccessibleDescription("");
+ await expect(page.getByRole("button", { name: "Save as…" })).toBeEnabled();
await box(page, "Live chat").uncheck();
await expect(submit).toBeDisabled();
await box(page, "Posts").check();
@@ -256,11 +270,58 @@ test.describe("Search in", () => {
test("a leaf asked for by name is not changed by the row", async ({ page }) => {
// A "Live chat" leaf reads the chat with the row at its default (Live
- // chat unticked), and a "Posts" leaf reads posts with Transcripts off.
+ // chat unticked)…
const chatLeaf = { k: "g", o: "AND", c: [{ k: "l", q: "message", s: "chat" }] };
await page.goto(`/?qt=${encodeURIComponent(JSON.stringify(chatLeaf))}`);
await expectResultSlugs(page, [CHAT_SMALL_SLUG]);
await openFilters(page);
await expect(box(page, "Live chat")).not.toBeChecked();
+
+ // …and a "Posts" leaf reads posts with Transcripts and Posts unticked
+ // (Live chat ticked, so the row reads something and can be committed).
+ await box(page, "Transcripts").uncheck();
+ await box(page, "Posts").uncheck();
+ await box(page, "Live chat").check();
+ await page.getByTestId("search-submit").click();
+ const postsLeaf = { k: "g", o: "AND", c: [{ k: "l", q: "kappa", s: "posts" }] };
+ await page.goto(`/?qt=${encodeURIComponent(JSON.stringify(postsLeaf))}`);
+ await openFilters(page);
+ await expect(box(page, "Transcripts")).not.toBeChecked();
+ await expect(box(page, "Posts")).not.toBeChecked();
+ await expectResultSlugs(page, POST_SLUGS);
+ });
+
+ test("a plain query with a tag chip on finishes, with its results or none", async ({
+ page,
+ }) => {
+ // Posts carry no curated tags, so under a chip the plain query reads no
+ // posts; before the fix the posts copy it read had an empty scope and the
+ // search never finished.
+ await installTagRoutes(page);
+ await page.goto("/");
+ await openFilters(page);
+ await page.locator(`[data-testid="tag-chip"][data-tag-id="${TAG_COLLAB}"]`).click();
+ await search(page, "alpha");
+ await expectResultSlugs(page, [CHAT_SMALL_SLUG, CHAT_LARGE_SLUG]);
+ await expectFinished(page);
+
+ // Posts alone under a chip: nothing can match, and it says so.
+ await box(page, "Transcripts").uncheck();
+ await search(page, "kappa");
+ await expect(page.getByText("No matching videos.")).toBeVisible({ timeout: 15_000 });
+ await expectFinished(page);
+ });
+
+ test("with no video type kept, a plain query still reads the posts, and finishes", async ({
+ page,
+ }) => {
+ // The transcripts copy's scope is then empty (the Type row keeps no video).
+ await page.goto("/");
+ await openFilters(page);
+ await page.getByRole("checkbox", { name: "Videos" }).uncheck();
+ await page.getByRole("checkbox", { name: "Livestreams" }).uncheck();
+ await search(page, "kappa");
+ await expectResultSlugs(page, POST_SLUGS);
+ await expectFinished(page);
});
});