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(() => {
|
afterEach(async () => {
|
||||||
vi.runOnlyPendingTimers();
|
await act(async () => {
|
||||||
|
vi.runOnlyPendingTimers();
|
||||||
|
});
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -208,6 +208,66 @@ describe("SettingsModal", () => {
|
|||||||
expect(screen.queryByLabelText("Max Concurrent Tasks")).toBeNull();
|
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 () => {
|
it("all settings fields are present across all sections", async () => {
|
||||||
render(<SettingsModal onClose={onClose} addToast={addToast} />);
|
render(<SettingsModal onClose={onClose} addToast={addToast} />);
|
||||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||||
|
|||||||
@@ -1157,7 +1157,9 @@ describe("TaskCard inline editing", () => {
|
|||||||
await user.tab();
|
await user.tab();
|
||||||
|
|
||||||
// Wait for the blur handler to execute
|
// 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
|
// Should have exited edit mode without saving
|
||||||
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
||||||
@@ -1410,7 +1412,9 @@ describe("TaskCard inline editing", () => {
|
|||||||
await user.tab();
|
await user.tab();
|
||||||
|
|
||||||
// Wait for the blur handler to execute
|
// 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
|
// Should exit edit mode without calling update
|
||||||
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
expect(screen.queryByPlaceholderText(/Task description/i)).toBeNull();
|
||||||
@@ -3368,7 +3372,7 @@ describe("TaskCard mission badge", () => {
|
|||||||
...overrides,
|
...overrides,
|
||||||
} as Task);
|
} 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" });
|
const task = createTask({ missionId: "MSN-001" });
|
||||||
render(<TaskCard task={task} onOpenDetail={vi.fn()} addToast={vi.fn()} />);
|
render(<TaskCard task={task} onOpenDetail={vi.fn()} addToast={vi.fn()} />);
|
||||||
|
|
||||||
@@ -3376,6 +3380,10 @@ describe("TaskCard mission badge", () => {
|
|||||||
expect(badge).toBeInTheDocument();
|
expect(badge).toBeInTheDocument();
|
||||||
expect(badge).toHaveClass("card-mission-badge");
|
expect(badge).toHaveClass("card-mission-badge");
|
||||||
expect(badge).toHaveTextContent("MSN-001");
|
expect(badge).toHaveTextContent("MSN-001");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render mission badge when task has no missionId", () => {
|
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");
|
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", () => {
|
it("write-through calls updateGlobalSettings on setThemeMode", () => {
|
||||||
const { result } = renderHook(() => useTheme());
|
const { result } = renderHook(() => useTheme());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user