@mosaic/mosaic is now the single package providing both: - 'mosaic' binary (CLI: yolo, coord, prdy, tui, gateway, etc.) - 'mosaic-wizard' binary (installation wizard) Changes: - Move packages/cli/src/* into packages/mosaic/src/ - Convert dynamic @mosaic/mosaic imports to static relative imports - Add CLI deps (ink, react, socket.io-client, @mosaic/config) to mosaic - Add jsx: react-jsx to mosaic's tsconfig - Exclude packages/cli from workspace (pnpm-workspace.yaml) - Update install.sh to install @mosaic/mosaic instead of @mosaic/cli - Bump version to 0.0.17 This eliminates the circular dependency between @mosaic/cli and @mosaic/mosaic that was blocking the build graph.
127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
/**
|
|
* Tests for input history logic extracted from useInputHistory.
|
|
* We test the pure state transitions directly rather than using
|
|
* React testing utilities to avoid react-dom version conflicts.
|
|
*/
|
|
|
|
const MAX_HISTORY = 50;
|
|
|
|
function createHistoryState() {
|
|
let history: string[] = [];
|
|
let historyIndex = -1;
|
|
let savedInput = '';
|
|
|
|
function addToHistory(input: string): void {
|
|
if (!input.trim()) return;
|
|
if (history[0] === input) return;
|
|
history = [input, ...history].slice(0, MAX_HISTORY);
|
|
historyIndex = -1;
|
|
}
|
|
|
|
function navigateUp(currentInput: string): string | null {
|
|
if (history.length === 0) return null;
|
|
if (historyIndex === -1) {
|
|
savedInput = currentInput;
|
|
}
|
|
const nextIndex = Math.min(historyIndex + 1, history.length - 1);
|
|
historyIndex = nextIndex;
|
|
return history[nextIndex] ?? null;
|
|
}
|
|
|
|
function navigateDown(): string | null {
|
|
if (historyIndex <= 0) {
|
|
historyIndex = -1;
|
|
return savedInput;
|
|
}
|
|
const nextIndex = historyIndex - 1;
|
|
historyIndex = nextIndex;
|
|
return history[nextIndex] ?? null;
|
|
}
|
|
|
|
function resetNavigation(): void {
|
|
historyIndex = -1;
|
|
}
|
|
|
|
function getHistoryLength(): number {
|
|
return history.length;
|
|
}
|
|
|
|
return { addToHistory, navigateUp, navigateDown, resetNavigation, getHistoryLength };
|
|
}
|
|
|
|
describe('useInputHistory (logic)', () => {
|
|
let h: ReturnType<typeof createHistoryState>;
|
|
|
|
beforeEach(() => {
|
|
h = createHistoryState();
|
|
});
|
|
|
|
it('adds to history on submit', () => {
|
|
h.addToHistory('hello');
|
|
h.addToHistory('world');
|
|
// navigateUp should return 'world' first (most recent)
|
|
const val = h.navigateUp('');
|
|
expect(val).toBe('world');
|
|
});
|
|
|
|
it('does not add empty strings to history', () => {
|
|
h.addToHistory('');
|
|
h.addToHistory(' ');
|
|
const val = h.navigateUp('');
|
|
expect(val).toBeNull();
|
|
});
|
|
|
|
it('navigateDown after up returns saved input', () => {
|
|
h.addToHistory('first');
|
|
const up = h.navigateUp('current');
|
|
expect(up).toBe('first');
|
|
const down = h.navigateDown();
|
|
expect(down).toBe('current');
|
|
});
|
|
|
|
it('does not add duplicate consecutive entries', () => {
|
|
h.addToHistory('same');
|
|
h.addToHistory('same');
|
|
expect(h.getHistoryLength()).toBe(1);
|
|
});
|
|
|
|
it('caps history at MAX_HISTORY entries', () => {
|
|
for (let i = 0; i < 55; i++) {
|
|
h.addToHistory(`entry-${i}`);
|
|
}
|
|
expect(h.getHistoryLength()).toBe(50);
|
|
// Navigate to the oldest entry
|
|
let val: string | null = null;
|
|
for (let i = 0; i < 60; i++) {
|
|
val = h.navigateUp('');
|
|
}
|
|
// Oldest entry at index 49 = entry-5 (entries 54 down to 5, 50 total)
|
|
expect(val).toBe('entry-5');
|
|
});
|
|
|
|
it('navigateUp returns null when history is empty', () => {
|
|
const val = h.navigateUp('something');
|
|
expect(val).toBeNull();
|
|
});
|
|
|
|
it('navigateUp cycles through multiple entries', () => {
|
|
h.addToHistory('a');
|
|
h.addToHistory('b');
|
|
h.addToHistory('c');
|
|
expect(h.navigateUp('')).toBe('c');
|
|
expect(h.navigateUp('c')).toBe('b');
|
|
expect(h.navigateUp('b')).toBe('a');
|
|
});
|
|
|
|
it('resetNavigation resets index to -1', () => {
|
|
h.addToHistory('test');
|
|
h.navigateUp('');
|
|
h.resetNavigation();
|
|
// After reset, navigateUp from index -1 returns most recent again
|
|
const val = h.navigateUp('');
|
|
expect(val).toBe('test');
|
|
});
|
|
});
|