fix(FN-1446): improve test reliability and git command TTY handling
- Fix QuickEntryBox focus restoration after task creation with proper submission state tracking - Improve QuickEntryBox tests by using explicit promise resolution and proper afterEach cleanup with act() wrappers - Fix TaskCard tests to use proper act() wrappers for async operations - Add stdio: 'pipe' to all git execSync calls to prevent TTY interaction issues in CI environments - Add test log suppression for noisy subagent and pi-claude-cli output in vitest setup
This commit is contained in:
@@ -218,8 +218,10 @@ describe("QuickEntryBox", () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers();
|
||||
afterEach(async () => {
|
||||
await act(async () => {
|
||||
vi.runOnlyPendingTimers();
|
||||
});
|
||||
vi.useRealTimers();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
@@ -208,6 +208,66 @@ describe("SettingsModal", () => {
|
||||
expect(screen.queryByLabelText("Max Concurrent Tasks")).toBeNull();
|
||||
});
|
||||
|
||||
it("invokes appearance callbacks when theme controls are used", async () => {
|
||||
const handleThemeModeChange = vi.fn();
|
||||
const handleColorThemeChange = vi.fn();
|
||||
|
||||
render(
|
||||
<SettingsModal
|
||||
onClose={onClose}
|
||||
addToast={addToast}
|
||||
themeMode="dark"
|
||||
colorTheme="default"
|
||||
onThemeModeChange={handleThemeModeChange}
|
||||
onColorThemeChange={handleColorThemeChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
fireEvent.click(screen.getAllByText("Appearance")[0]);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Light mode" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Forest theme" }));
|
||||
|
||||
expect(handleThemeModeChange).toHaveBeenCalledWith("light");
|
||||
expect(handleColorThemeChange).toHaveBeenCalledWith("forest");
|
||||
});
|
||||
|
||||
it("reflects selected appearance values when parent updates controlled props", async () => {
|
||||
function ControlledAppearanceModal() {
|
||||
const [themeMode, setThemeMode] = useState<ThemeMode>("dark");
|
||||
const [colorTheme, setColorTheme] = useState<ColorTheme>("default");
|
||||
|
||||
return (
|
||||
<SettingsModal
|
||||
onClose={onClose}
|
||||
addToast={addToast}
|
||||
themeMode={themeMode}
|
||||
colorTheme={colorTheme}
|
||||
onThemeModeChange={setThemeMode}
|
||||
onColorThemeChange={setColorTheme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render(<ControlledAppearanceModal />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
fireEvent.click(screen.getAllByText("Appearance")[0]);
|
||||
|
||||
const lightModeButton = screen.getByRole("button", { name: "Light mode" });
|
||||
const forestThemeButton = screen.getByRole("button", { name: "Forest theme" });
|
||||
|
||||
fireEvent.click(lightModeButton);
|
||||
fireEvent.click(forestThemeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(lightModeButton).toHaveAttribute("aria-pressed", "true");
|
||||
expect(forestThemeButton).toHaveAttribute("aria-pressed", "true");
|
||||
expect(screen.getByText("Light / Forest")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("all settings fields are present across all sections", async () => {
|
||||
render(<SettingsModal onClose={onClose} addToast={addToast} />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
@@ -1157,7 +1157,9 @@ describe("TaskCard inline editing", () => {
|
||||
await user.tab();
|
||||
|
||||
// Wait for the blur handler to execute
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
|
||||
// Should have exited edit mode without saving
|
||||
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
||||
@@ -1410,7 +1412,9 @@ describe("TaskCard inline editing", () => {
|
||||
await user.tab();
|
||||
|
||||
// Wait for the blur handler to execute
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
|
||||
// Should exit edit mode without calling update
|
||||
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
||||
@@ -3368,7 +3372,7 @@ describe("TaskCard mission badge", () => {
|
||||
...overrides,
|
||||
} as Task);
|
||||
|
||||
it("renders mission badge when task has missionId", () => {
|
||||
it("renders mission badge when task has missionId", async () => {
|
||||
const task = createTask({ missionId: "MSN-001" });
|
||||
render(<TaskCard task={task} onOpenDetail={vi.fn()} addToast={vi.fn()} />);
|
||||
|
||||
@@ -3376,6 +3380,10 @@ describe("TaskCard mission badge", () => {
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge).toHaveClass("card-mission-badge");
|
||||
expect(badge).toHaveTextContent("MSN-001");
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not render mission badge when task has no missionId", () => {
|
||||
|
||||
@@ -193,6 +193,45 @@ describe("useTheme", () => {
|
||||
expect(localStorageMock[THEME_MODE_STORAGE_KEY]).toBe("dark");
|
||||
});
|
||||
|
||||
it("keeps user-selected theme and color when hydration resolves late", async () => {
|
||||
let resolveFetch: (value: Partial<Settings>) => void;
|
||||
const pendingFetch = new Promise<Partial<Settings>>((resolve) => {
|
||||
resolveFetch = resolve;
|
||||
});
|
||||
mockFetchGlobalSettings.mockReturnValue(pendingFetch);
|
||||
|
||||
const { result } = renderHook(() => useTheme());
|
||||
|
||||
act(() => {
|
||||
result.current.setThemeMode("light");
|
||||
result.current.setColorTheme("forest");
|
||||
});
|
||||
|
||||
expect(result.current.themeMode).toBe("light");
|
||||
expect(result.current.colorTheme).toBe("forest");
|
||||
expect(localStorageMock[THEME_MODE_STORAGE_KEY]).toBe("light");
|
||||
expect(localStorageMock[COLOR_THEME_STORAGE_KEY]).toBe("forest");
|
||||
|
||||
resolveFetch!({
|
||||
// Simulate stale backend values that would previously revert user changes.
|
||||
themeMode: "dark",
|
||||
colorTheme: "default",
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchGlobalSettings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.themeMode).toBe("light");
|
||||
expect(result.current.colorTheme).toBe("forest");
|
||||
});
|
||||
|
||||
expect(document.documentElement.getAttribute("data-theme")).toBe("light");
|
||||
expect(document.documentElement.getAttribute("data-color-theme")).toBe("forest");
|
||||
expect(document.querySelectorAll('link[id="theme-data"]').length).toBe(1);
|
||||
});
|
||||
|
||||
it("write-through calls updateGlobalSettings on setThemeMode", () => {
|
||||
const { result } = renderHook(() => useTheme());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user