From f0d4dc5808252ff94b3e95d40143d01fce5b1f62 Mon Sep 17 00:00:00 2001 From: matthewdias Date: Sun, 4 Oct 2026 15:33:54 -0500 Subject: [PATCH] Import app.tsx at the top of Follow Up's UI tests so they stop timing out The first test in app, picker and record-draft to load the app paid for a cold import of @hugeicons/core-free-icons, about 6,000 modules, inside its own 5s timeout. On a busy machine that took 5-11s, so one test per file failed, and which one moved with test order. Importing app.tsx at the top moves the cost to collection, as pill-removal already does with banner.tsx. Co-Authored-By: Claude Opus 5.5 --- plugins/follow-up/tests/ui/app.test.tsx | 7 +++++-- plugins/follow-up/tests/ui/picker.test.tsx | 7 +++++-- plugins/follow-up/tests/ui/record-draft.test.tsx | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugins/follow-up/tests/ui/app.test.tsx b/plugins/follow-up/tests/ui/app.test.tsx index df6526f..c359b8e 100644 --- a/plugins/follow-up/tests/ui/app.test.tsx +++ b/plugins/follow-up/tests/ui/app.test.tsx @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; import { loadPluginApp } from "@get-bb/plugin-sdk/testing/app"; +// Loaded at the top, not inside a test: the first load pulls in every hugeicons icon, +// which can take longer than a test's 5s timeout on a busy machine. +import pluginApp from "../../app.tsx"; import type { PluginComposerApi } from "@get-bb/plugin-sdk/app"; import { setCollapsed, setRows } from "../../src/store.ts"; import type { FollowUp } from "../../lib/followups.ts"; @@ -18,7 +21,7 @@ const composerFor = (threadId: string) => describe("app.tsx", () => { it("registers the palette commands", async () => { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); // The harness collects commands untyped; only the ids are read here. const registered = (app as unknown as { commandPaletteActions: { id: string }[] }) .commandPaletteActions.map((command) => command.id); @@ -32,7 +35,7 @@ describe("app.tsx", () => { }); it("offers the + menu row whenever the thread has open rows, banner open or not", async () => { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); const item = app.composerCustomizations[0]?.plusMenu?.find((entry) => entry.id === "show-followups"); const disabled = item?.disabled; expect(typeof disabled).toBe("function"); diff --git a/plugins/follow-up/tests/ui/picker.test.tsx b/plugins/follow-up/tests/ui/picker.test.tsx index 149a65a..d8601cc 100644 --- a/plugins/follow-up/tests/ui/picker.test.tsx +++ b/plugins/follow-up/tests/ui/picker.test.tsx @@ -2,6 +2,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, waitFor } from "@testing-library/react"; import { useComposer, type ComposerMention, type PluginComposerApi } from "@get-bb/plugin-sdk/app"; import { loadPluginApp, renderSlot } from "@get-bb/plugin-sdk/testing/app"; +// Loaded at the top, not inside a test: the first load pulls in every hugeicons icon, +// which can take longer than a test's 5s timeout on a busy machine. +import pluginApp from "../../app.tsx"; import { toast } from "sonner"; import { FollowUpPicker, @@ -180,7 +183,7 @@ describe("the picker", () => { describe("the + menu row", () => { async function plusRow() { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); const item = app.composerCustomizations[0]?.plusMenu?.find((entry) => entry.id === "show-followups"); expect(item).toBeDefined(); return item!; @@ -212,7 +215,7 @@ describe("the + menu row", () => { }); it("has the picker in its own thread-only customization", async () => { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); const picker = app.composerCustomizations.find((entry) => entry.id === "follow-up-picker"); expect(picker?.scopes).toEqual(["thread"]); expect(picker?.experimental_popups?.map((popup) => popup.id)).toEqual([PICKER_POPUP_ID]); diff --git a/plugins/follow-up/tests/ui/record-draft.test.tsx b/plugins/follow-up/tests/ui/record-draft.test.tsx index 77c6a9c..5234215 100644 --- a/plugins/follow-up/tests/ui/record-draft.test.tsx +++ b/plugins/follow-up/tests/ui/record-draft.test.tsx @@ -1,6 +1,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { useComposer, useRpc, type ComposerMention, type PluginComposerApi, type PluginComposerScope } from "@get-bb/plugin-sdk/app"; import { loadPluginApp, renderSlot } from "@get-bb/plugin-sdk/testing/app"; +// Loaded at the top, not inside a test: the first load pulls in every hugeicons icon, +// which can take longer than a test's 5s timeout on a busy machine. +import pluginApp from "../../app.tsx"; import { toast } from "sonner"; import { recordCommand, @@ -167,14 +170,14 @@ describe("the composer command", () => { describe("app.tsx", () => { it("puts the row in the send menu and has no action-row button", async () => { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); const customization = app.composerCustomizations[0]; expect(customization?.sendMenu?.map((item) => item.id)).toEqual(["record-as-follow-up"]); expect(customization?.actions ?? []).toEqual([]); }); it("reports a message action's refusal in a toast rather than opening the panel", async () => { - const app = await loadPluginApp(() => import("../../app.tsx")); + const app = await loadPluginApp(pluginApp); const action = app.messageActions.find((entry) => entry.id === "record-follow-up"); rememberRpc({ call: async () => ({ outcome: "dismissed", id: null, followUps: [], done: [] }),