From 63a2b5ba1c618fdfb359a07507f8b7918b4269a0 Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 1 Jun 2026 21:48:43 +0000 Subject: [PATCH] Fix attachment preview test act helpers --- .../IssueAttachmentsSection.test.tsx | 35 ++++++++++++++++--- ui/src/pages/IssueDetail.test.tsx | 11 +++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/ui/src/components/IssueAttachmentsSection.test.tsx b/ui/src/components/IssueAttachmentsSection.test.tsx index 4e54e05f..49a770be 100644 --- a/ui/src/components/IssueAttachmentsSection.test.tsx +++ b/ui/src/components/IssueAttachmentsSection.test.tsx @@ -2,7 +2,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { IssueAttachment } from "@paperclipai/shared"; -import { act, type ComponentProps, type ReactNode } from "react"; +import type { ComponentProps, ReactNode } from "react"; +import { flushSync } from "react-dom"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { IssueAttachmentsSection } from "./IssueAttachmentsSection"; @@ -33,6 +34,14 @@ vi.mock("@/components/ui/button", () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +async function act(callback: () => void | Promise) { + let result: void | Promise = undefined; + flushSync(() => { + result = callback(); + }); + await result; +} + function makeAttachment(overrides: Partial = {}): IssueAttachment { return { id: "attachment-1", @@ -64,6 +73,20 @@ async function flushReact() { }); } +async function waitForAssertion(assertion: () => void, attempts = 20) { + let lastError: unknown; + for (let index = 0; index < attempts; index += 1) { + try { + assertion(); + return; + } catch (error) { + lastError = error; + await flushReact(); + } + } + throw lastError; +} + describe("IssueAttachmentsSection", () => { let container: HTMLDivElement; let root: Root; @@ -122,10 +145,12 @@ describe("IssueAttachmentsSection", () => { headers: expect.objectContaining({ Accept: expect.stringContaining("text/markdown") }), }), ); - expect(container.querySelector('[data-testid="fold-curtain"]')).toBeTruthy(); - const markdownBody = container.querySelector('[data-testid="markdown-body"]'); - expect(markdownBody?.textContent).toContain("Imported plan"); - expect(markdownBody?.className).toContain("paperclip-edit-in-place-content"); + await waitForAssertion(() => { + expect(container.querySelector('[data-testid="fold-curtain"]')).toBeTruthy(); + const markdownBody = container.querySelector('[data-testid="markdown-body"]'); + expect(markdownBody?.textContent).toContain("Imported plan"); + expect(markdownBody?.className).toContain("paperclip-edit-in-place-content"); + }); }); it("renders video attachments through the same player used for artifact outputs", async () => { diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index ef55a0d7..02e39002 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -2,7 +2,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { Agent, Issue, IssueTreeControlPreview, IssueTreeHold } from "@paperclipai/shared"; -import { act, type AnchorHTMLAttributes, type ButtonHTMLAttributes, type ReactNode } from "react"; +import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "react"; +import { flushSync } from "react-dom"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { canBoardResolveRecoveryAction, IssueDetail } from "./IssueDetail"; @@ -357,6 +358,14 @@ vi.mock("@/components/ui/textarea", () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).IS_REACT_ACT_ENVIRONMENT = true; +async function act(callback: () => void | Promise) { + let result: void | Promise = undefined; + flushSync(() => { + result = callback(); + }); + await result; +} + function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((innerResolve) => {