test: speed up slow dashboard and tui tests
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import React from "react";
|
||||
import { render } from "ink";
|
||||
import { Writable } from "node:stream";
|
||||
import { detectProjectDir } from "../project-detect";
|
||||
import { FusionProvider, useFusion, FusionContext } from "../fusion-context";
|
||||
import { TaskStore } from "@fusion/core";
|
||||
@@ -14,6 +15,27 @@ import { join } from "node:path";
|
||||
// Track temp directories for cleanup
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createSinkStream(): NodeJS.WriteStream {
|
||||
const stream = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
}) as NodeJS.WriteStream;
|
||||
stream.columns = 80;
|
||||
stream.rows = 24;
|
||||
return stream;
|
||||
}
|
||||
|
||||
function renderTest(node: React.ReactNode) {
|
||||
return render(node, {
|
||||
stdout: createSinkStream(),
|
||||
stderr: createSinkStream(),
|
||||
patchConsole: false,
|
||||
exitOnCtrlC: false,
|
||||
maxFps: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up temp directories
|
||||
for (const dir of tempDirs) {
|
||||
@@ -132,7 +154,7 @@ describe("FusionProvider", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(
|
||||
const instance = renderTest(
|
||||
<FusionProvider projectDir={projectDir}>
|
||||
<TestComponent />
|
||||
</FusionProvider>
|
||||
@@ -156,7 +178,7 @@ describe("FusionProvider", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(
|
||||
const instance = renderTest(
|
||||
<FusionProvider projectDir={nonExistentDir}>
|
||||
<TestComponent />
|
||||
</FusionProvider>
|
||||
@@ -196,7 +218,7 @@ describe("FusionProvider", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(
|
||||
const instance = renderTest(
|
||||
<FusionProvider projectDir={projectDir}>
|
||||
<TestComponent />
|
||||
</FusionProvider>
|
||||
@@ -228,7 +250,7 @@ describe("FusionProvider", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(
|
||||
const instance = renderTest(
|
||||
<FusionProvider projectDir={explicitDir}>
|
||||
<TestComponent />
|
||||
</FusionProvider>
|
||||
@@ -281,7 +303,7 @@ describe("useFusion hook", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(
|
||||
const instance = renderTest(
|
||||
<FusionProvider projectDir={projectDir}>
|
||||
<GoodComponent />
|
||||
</FusionProvider>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import React from "react";
|
||||
import { render, Box, Text } from "ink";
|
||||
import { Writable } from "node:stream";
|
||||
import { useGlobalShortcuts, HelpOverlay, FocusGuardRef, type ScreenId } from "../hooks/use-global-shortcuts";
|
||||
import type { Key } from "ink";
|
||||
|
||||
@@ -12,6 +13,27 @@ import type { Key } from "ink";
|
||||
let capturedUseInputHandlers: ((input: string, key: Key) => void)[] = [];
|
||||
let capturedExitFn: (() => void) | undefined;
|
||||
|
||||
function createSinkStream(): NodeJS.WriteStream {
|
||||
const stream = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
}) as NodeJS.WriteStream;
|
||||
stream.columns = 80;
|
||||
stream.rows = 24;
|
||||
return stream;
|
||||
}
|
||||
|
||||
function renderTest(node: React.ReactNode) {
|
||||
return render(node, {
|
||||
stdout: createSinkStream(),
|
||||
stderr: createSinkStream(),
|
||||
patchConsole: false,
|
||||
exitOnCtrlC: false,
|
||||
maxFps: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
// Mock ink hooks to avoid raw mode errors in tests
|
||||
vi.mock("ink", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("ink")>();
|
||||
@@ -51,7 +73,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(capturedHelpVisible).toBe(false);
|
||||
@@ -67,7 +89,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(toggleHelpFn).toBeDefined();
|
||||
@@ -84,7 +106,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(hideHelpFn).toBeDefined();
|
||||
@@ -105,7 +127,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Initial state should be false
|
||||
@@ -133,7 +155,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// First toggle
|
||||
@@ -160,7 +182,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(onScreenChange).toBeDefined();
|
||||
@@ -175,7 +197,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Get the registered input handler
|
||||
@@ -199,7 +221,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const expectedScreens: ScreenId[] = ["board", "detail", "activity", "agents", "settings"];
|
||||
@@ -230,7 +252,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -256,7 +278,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -285,7 +307,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -313,7 +335,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -339,7 +361,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -364,7 +386,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -390,7 +412,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -417,7 +439,7 @@ describe("useGlobalShortcuts", () => {
|
||||
return <Text>Test</Text>;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[0];
|
||||
@@ -439,7 +461,7 @@ describe("HelpOverlay", () => {
|
||||
it("renders without crashing", async () => {
|
||||
const onClose = vi.fn();
|
||||
|
||||
const instance = render(<HelpOverlay onClose={onClose} />);
|
||||
const instance = renderTest(<HelpOverlay onClose={onClose} />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(() => instance.unmount()).not.toThrow();
|
||||
@@ -452,7 +474,7 @@ describe("HelpOverlay", () => {
|
||||
return <HelpOverlay onClose={onClose} />;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[capturedUseInputHandlers.length - 1];
|
||||
@@ -472,7 +494,7 @@ describe("HelpOverlay", () => {
|
||||
return <HelpOverlay onClose={onClose} />;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const handler = capturedUseInputHandlers[capturedUseInputHandlers.length - 1];
|
||||
@@ -488,7 +510,7 @@ describe("HelpOverlay", () => {
|
||||
it("displays keyboard shortcuts", async () => {
|
||||
const onClose = vi.fn();
|
||||
|
||||
const instance = render(<HelpOverlay onClose={onClose} />);
|
||||
const instance = renderTest(<HelpOverlay onClose={onClose} />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// The component should render without error
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import React from "react";
|
||||
import { Writable } from "node:stream";
|
||||
import { computeColumnLayout, MIN_TERMINAL_COLUMNS, MIN_TERMINAL_ROWS, type ColumnDefinition } from "../utils/terminal";
|
||||
import { truncateText, truncateWithOptions, padText, fitText } from "../utils/truncate";
|
||||
|
||||
@@ -14,6 +15,27 @@ const mockStdout = {
|
||||
write: vi.fn(),
|
||||
};
|
||||
|
||||
function createSinkStream(): NodeJS.WriteStream {
|
||||
const stream = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
}) as NodeJS.WriteStream;
|
||||
stream.columns = 80;
|
||||
stream.rows = 24;
|
||||
return stream;
|
||||
}
|
||||
|
||||
function renderTest(node: React.ReactNode) {
|
||||
return render(node, {
|
||||
stdout: createSinkStream(),
|
||||
stderr: createSinkStream(),
|
||||
patchConsole: false,
|
||||
exitOnCtrlC: false,
|
||||
maxFps: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
// Mock Ink's useStdout for terminal dimension tests
|
||||
// The mock returns a function that reads from the mutable mockStdout object
|
||||
vi.mock("ink", async (importOriginal) => {
|
||||
@@ -52,7 +74,7 @@ describe("terminal.ts", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
instance.unmount();
|
||||
|
||||
expect(dimensions).not.toBeNull();
|
||||
@@ -69,7 +91,7 @@ describe("terminal.ts", () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = render(<TestComponent />);
|
||||
const instance = renderTest(<TestComponent />);
|
||||
expect(() => instance.unmount()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import React, { useState } from "react";
|
||||
import { render, Box, Text } from "ink";
|
||||
import { Writable } from "node:stream";
|
||||
import { mkdir, writeFile, remove } from "fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { ScreenRouter, SCREENS, type ScreenId } from "../components/screen-router";
|
||||
@@ -12,6 +13,27 @@ import { ScreenRouter, SCREENS, type ScreenId } from "../components/screen-route
|
||||
// Track temp directories for cleanup
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createSinkStream(): NodeJS.WriteStream {
|
||||
const stream = new Writable({
|
||||
write(_chunk, _encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
}) as NodeJS.WriteStream;
|
||||
stream.columns = 80;
|
||||
stream.rows = 24;
|
||||
return stream;
|
||||
}
|
||||
|
||||
function renderTest(node: React.ReactNode) {
|
||||
return render(node, {
|
||||
stdout: createSinkStream(),
|
||||
stderr: createSinkStream(),
|
||||
patchConsole: false,
|
||||
exitOnCtrlC: false,
|
||||
maxFps: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up temp directories
|
||||
for (const dir of tempDirs) {
|
||||
@@ -68,7 +90,7 @@ describe("SCREENS constant", () => {
|
||||
describe("ScreenRouter", () => {
|
||||
describe("rendering", () => {
|
||||
it("renders without crashing", async () => {
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderTest(
|
||||
<ScreenRouter>
|
||||
{({ activeScreen }) => (
|
||||
<Box>
|
||||
@@ -85,7 +107,7 @@ describe("ScreenRouter", () => {
|
||||
});
|
||||
|
||||
it("renders all five tab markers with shortcut numbers", async () => {
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderTest(
|
||||
<ScreenRouter>
|
||||
{({ activeScreen }) => (
|
||||
<Box>
|
||||
@@ -106,7 +128,7 @@ describe("ScreenRouter", () => {
|
||||
it("passes activeScreen prop to children function", async () => {
|
||||
let capturedActiveScreen: ScreenId | undefined;
|
||||
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderTest(
|
||||
<ScreenRouter>
|
||||
{({ activeScreen }) => {
|
||||
capturedActiveScreen = activeScreen;
|
||||
@@ -126,7 +148,7 @@ describe("ScreenRouter", () => {
|
||||
});
|
||||
|
||||
it("renders screen content below tab bar", async () => {
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderTest(
|
||||
<ScreenRouter>
|
||||
{({ activeScreen }) => (
|
||||
<Box>
|
||||
@@ -147,7 +169,7 @@ describe("ScreenRouter", () => {
|
||||
it("defaults to board screen", async () => {
|
||||
let activeScreen: ScreenId = "detail"; // Start with non-default
|
||||
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderTest(
|
||||
<ScreenRouter>
|
||||
{({ activeScreen: screen }) => {
|
||||
activeScreen = screen;
|
||||
@@ -188,7 +210,7 @@ describe("ScreenRouter", () => {
|
||||
);
|
||||
};
|
||||
|
||||
const { unmount } = render(<TestApp />);
|
||||
const { unmount } = renderTest(<TestApp />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Active screen is board
|
||||
|
||||
Reference in New Issue
Block a user