Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
115 lines
4.6 KiB
TypeScript
115 lines
4.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import fs from 'node:fs';
|
|
|
|
import type { TerminalTheme } from '../../domain/models.ts';
|
|
import {
|
|
applyCurrentProviderResponse,
|
|
isPluginTerminalProviderRefreshCurrent,
|
|
mergePluginTerminalThemeColors,
|
|
resolvePluginTerminalTheme,
|
|
waitForProviderResponse,
|
|
} from './usePluginTerminalProviders.ts';
|
|
|
|
const baseTheme = {
|
|
id: 'base',
|
|
name: 'Base',
|
|
type: 'dark',
|
|
colors: {
|
|
background: '#000000', foreground: '#ffffff', cursor: '#ffffff', selection: '#333333',
|
|
black: '#000000', red: '#ff0000', green: '#00ff00', yellow: '#ffff00', blue: '#0000ff',
|
|
magenta: '#ff00ff', cyan: '#00ffff', white: '#ffffff', brightBlack: '#777777',
|
|
brightRed: '#ff7777', brightGreen: '#77ff77', brightYellow: '#ffff77', brightBlue: '#7777ff',
|
|
brightMagenta: '#ff77ff', brightCyan: '#77ffff', brightWhite: '#ffffff',
|
|
},
|
|
} as const satisfies TerminalTheme;
|
|
|
|
test('terminal theme Providers merge deterministically over the host theme', () => {
|
|
const merged = mergePluginTerminalThemeColors([
|
|
{ background: '#102030', cursor: '#abcdef' },
|
|
{ background: '#ffffff', foreground: '#f0f0f0' },
|
|
]);
|
|
assert.deepEqual(merged, {
|
|
background: '#102030',
|
|
cursor: '#abcdef',
|
|
foreground: '#f0f0f0',
|
|
});
|
|
assert.deepEqual(resolvePluginTerminalTheme(baseTheme, merged).colors, {
|
|
...baseTheme.colors,
|
|
...merged,
|
|
});
|
|
assert.equal(resolvePluginTerminalTheme(baseTheme, {}).id, baseTheme.id);
|
|
});
|
|
|
|
test('terminal components delegate Provider state to the application hook', () => {
|
|
const terminal = fs.readFileSync(new URL('../../components/Terminal.tsx', import.meta.url), 'utf8');
|
|
const effects = fs.readFileSync(new URL('../../components/terminal/useTerminalEffects.ts', import.meta.url), 'utf8');
|
|
assert.match(terminal, /usePluginTerminalProviders\(/);
|
|
assert.doesNotMatch(terminal, /getWindowPluginTerminalProviderRegistry|PluginTerminalProviderAvailability/);
|
|
assert.doesNotMatch(effects, /getWindowPluginTerminalProviderRegistry|PluginTerminalProviderAvailability/);
|
|
});
|
|
|
|
test('application-owned Provider timeout aborts and returns a stale response', async () => {
|
|
const controller = new AbortController();
|
|
const result = await waitForProviderResponse(new Promise(() => {}), controller, 1);
|
|
assert.equal(controller.signal.aborted, true);
|
|
assert.deepEqual(result, { requestId: '', stale: true, results: [] });
|
|
});
|
|
|
|
test('a deferred Provider response cannot update state after the live session disconnects', async () => {
|
|
let resolveResponse!: (value: { requestId: string; stale: boolean; results: [] }) => void;
|
|
const response = new Promise<{ requestId: string; stale: boolean; results: [] }>((resolve) => {
|
|
resolveResponse = resolve;
|
|
});
|
|
const initial = {
|
|
sessionId: 'session-a',
|
|
hostId: 'host-a',
|
|
workspaceId: 'workspace-a',
|
|
protocol: 'ssh',
|
|
status: 'connected',
|
|
shellType: 'posix',
|
|
baseTheme,
|
|
} as const;
|
|
let current = { ...initial } as Parameters<typeof isPluginTerminalProviderRefreshCurrent>[1];
|
|
let applied = false;
|
|
const pending = applyCurrentProviderResponse(
|
|
response,
|
|
() => isPluginTerminalProviderRefreshCurrent(initial, current),
|
|
() => { applied = true; },
|
|
() => { applied = true; },
|
|
);
|
|
current = { ...current, status: 'disconnected' };
|
|
resolveResponse({ requestId: 'late', stale: false, results: [] });
|
|
await pending;
|
|
assert.equal(applied, false);
|
|
});
|
|
|
|
test('session identity and shell discovery invalidate Provider output metadata', () => {
|
|
const initial = {
|
|
sessionId: 'session-a',
|
|
hostId: 'host-a',
|
|
workspaceId: 'workspace-a',
|
|
protocol: 'ssh',
|
|
status: 'connected',
|
|
shellType: undefined,
|
|
baseTheme,
|
|
} as const;
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, { ...initial, shellType: 'posix' }), false);
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, { ...initial, sessionId: 'session-b' }), false);
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, { ...initial, hostId: 'host-b' }), false);
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, { ...initial, workspaceId: 'workspace-b' }), false);
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, { ...initial, protocol: 'telnet' }), false);
|
|
assert.equal(isPluginTerminalProviderRefreshCurrent(initial, initial), true);
|
|
});
|
|
|
|
test('a current stale Provider response clears the previous output', async () => {
|
|
let cleared = false;
|
|
await applyCurrentProviderResponse(
|
|
Promise.resolve({ requestId: 'timeout', stale: true, results: [] }),
|
|
() => true,
|
|
() => assert.fail('stale response must not be applied'),
|
|
() => { cleared = true; },
|
|
);
|
|
assert.equal(cleared, true);
|
|
});
|