mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
test(ui): guard issue selection through polling
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
02e2dd271b
commit
769700ddb1
1 file changed
+132
-1
@@ -2,7 +2,8 @@
|
||||
|
||||
import { flushSync } from "react-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { act as reactAct } from "react";
|
||||
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { ThemeProvider } from "../context/ThemeContext";
|
||||
import { MarkdownBody } from "./MarkdownBody";
|
||||
@@ -30,6 +31,7 @@ let root: ReturnType<typeof createRoot> | null = null;
|
||||
let container: HTMLDivElement | null = null;
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
if (root) {
|
||||
flushSync(() => root?.unmount());
|
||||
}
|
||||
@@ -38,6 +40,21 @@ afterEach(() => {
|
||||
container = null;
|
||||
});
|
||||
|
||||
async function act<T>(callback: () => T | Promise<T>): Promise<T> {
|
||||
if (typeof reactAct === "function") {
|
||||
return await (reactAct(callback) as T | Promise<T>);
|
||||
}
|
||||
|
||||
let result: T | Promise<T> | undefined;
|
||||
flushSync(() => {
|
||||
result = callback();
|
||||
});
|
||||
const resolved = await result;
|
||||
await Promise.resolve();
|
||||
flushSync(() => {});
|
||||
return resolved as T;
|
||||
}
|
||||
|
||||
const SAMPLE = "Some text\n\n```ts\nconst answer = 42;\n```\n\nAnd a [link](https://example.com).";
|
||||
|
||||
function tree(children: string, queryClient: QueryClient) {
|
||||
@@ -50,6 +67,51 @@ function tree(children: string, queryClient: QueryClient) {
|
||||
);
|
||||
}
|
||||
|
||||
function IssueDetailPollingHarness({
|
||||
loadContent,
|
||||
loadRuns,
|
||||
}: {
|
||||
loadContent: () => Promise<{
|
||||
description: string;
|
||||
comment: string;
|
||||
document: string;
|
||||
refresh: number;
|
||||
}>;
|
||||
loadRuns: () => Promise<{ refresh: number }>;
|
||||
}) {
|
||||
const { data: content } = useQuery({
|
||||
queryKey: ["selection-stability", "content"],
|
||||
queryFn: loadContent,
|
||||
});
|
||||
useQuery({
|
||||
queryKey: ["selection-stability", "runs"],
|
||||
queryFn: loadRuns,
|
||||
refetchInterval: 5_000,
|
||||
});
|
||||
|
||||
if (!content) return null;
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section data-testid="issue-description">
|
||||
<MarkdownBody>{content.description}</MarkdownBody>
|
||||
</section>
|
||||
<section data-testid="issue-comment">
|
||||
<MarkdownBody>{content.comment}</MarkdownBody>
|
||||
</section>
|
||||
<section data-testid="issue-document">
|
||||
<MarkdownBody>{content.document}</MarkdownBody>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
async function flushQueries() {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
describe("MarkdownBody re-render stability (PAP-10767)", () => {
|
||||
it("preserves rendered DOM nodes across a parent re-render with unchanged props", () => {
|
||||
container = document.createElement("div");
|
||||
@@ -111,4 +173,73 @@ describe("MarkdownBody re-render stability (PAP-10767)", () => {
|
||||
|
||||
expect(window.getSelection()?.toString()).toBe("Some text");
|
||||
});
|
||||
|
||||
it("preserves issue text nodes through polling cycles and query invalidation", async () => {
|
||||
vi.useFakeTimers();
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
let contentRefresh = 0;
|
||||
let runsRefresh = 0;
|
||||
const loadContent = vi.fn(async () => ({
|
||||
description: "Stable issue description",
|
||||
comment: "Stable issue comment",
|
||||
document: "Stable issue document",
|
||||
refresh: contentRefresh++,
|
||||
}));
|
||||
const loadRuns = vi.fn(async () => ({ refresh: runsRefresh++ }));
|
||||
queryClient.setQueryData(["selection-stability", "content"], {
|
||||
description: "Stable issue description",
|
||||
comment: "Stable issue comment",
|
||||
document: "Stable issue document",
|
||||
refresh: -1,
|
||||
});
|
||||
queryClient.setQueryData(["selection-stability", "runs"], { refresh: -1 });
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider>
|
||||
<IssueDetailPollingHarness loadContent={loadContent} loadRuns={loadRuns} />
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushQueries();
|
||||
|
||||
const descriptionNode = container.querySelector('[data-testid="issue-description"] p')?.firstChild;
|
||||
const commentNode = container.querySelector('[data-testid="issue-comment"] p')?.firstChild;
|
||||
const documentNode = container.querySelector('[data-testid="issue-document"] p')?.firstChild;
|
||||
expect(descriptionNode?.nodeType).toBe(Node.TEXT_NODE);
|
||||
expect(commentNode?.nodeType).toBe(Node.TEXT_NODE);
|
||||
expect(documentNode?.nodeType).toBe(Node.TEXT_NODE);
|
||||
|
||||
const range = document.createRange();
|
||||
range.setStart(documentNode!, 0);
|
||||
range.setEnd(documentNode!, "Stable issue document".length);
|
||||
const selection = window.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(30_000);
|
||||
});
|
||||
expect(loadRuns).toHaveBeenCalledTimes(7);
|
||||
expect(selection?.toString()).toBe("Stable issue document");
|
||||
|
||||
await act(async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["selection-stability", "content"] });
|
||||
});
|
||||
await flushQueries();
|
||||
|
||||
expect(container.querySelector('[data-testid="issue-description"] p')?.firstChild).toBe(descriptionNode);
|
||||
expect(container.querySelector('[data-testid="issue-comment"] p')?.firstChild).toBe(commentNode);
|
||||
expect(container.querySelector('[data-testid="issue-document"] p')?.firstChild).toBe(documentNode);
|
||||
expect(selection?.toString()).toBe("Stable issue document");
|
||||
expect(loadContent).toHaveBeenCalledTimes(2);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user