Files
NetMesh/application/state/useAIState.newChat.test.tsx
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

131 lines
3.9 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { JSDOM } from 'jsdom';
import { resolveInheritedAIActiveSessionId } from '../../domain/aiWorkspaceScopeInherit.ts';
import { resolveDisplayedPanelView } from '../../components/ai/aiPanelViewState.ts';
import type { AISession } from '../../infrastructure/ai/types.ts';
import { useAISessionsStore } from './aiSessionsStore.ts';
import { useAIState } from './useAIState.ts';
const SCOPE_KEY = 'workspace:workspace-old';
const RESTORED_SESSION: AISession = {
id: 'chat-old',
title: 'Restored chat',
agentId: 'catty',
scope: {
type: 'terminal',
targetId: 'terminal-a',
hostIds: ['host-a'],
},
messages: [
{ id: 'user-1', role: 'user', content: 'old question', timestamp: 1 },
{ id: 'assistant-1', role: 'assistant', content: 'old answer', timestamp: 2 },
],
createdAt: 1,
updatedAt: 2,
};
function NewChatHarness() {
const ai = useAIState();
const state = useAISessionsStore();
const visibleSessionIds = new Set(state.sessions.map((session) => session.id));
const inheritedSessionId = resolveInheritedAIActiveSessionId({
scopeType: 'workspace',
scopeTargetId: 'workspace-old',
activeSessionIdMap: state.activeSessionIdMap,
memberTerminalIds: ['terminal-a', 'terminal-b'],
preferredTerminalId: 'terminal-a',
visibleSessionIds,
});
const view = resolveDisplayedPanelView(
state.panelViewByScope[SCOPE_KEY],
state.draftsByScope[SCOPE_KEY] != null,
state.sessions as AISession[],
inheritedSessionId,
'workspace',
);
return (
<button
type="button"
data-view={view.mode}
data-session={view.mode === 'session' ? view.sessionId : ''}
onClick={() => {
ai.clearDraftForScope(SCOPE_KEY);
ai.updateDraft(SCOPE_KEY, 'catty', () => ({
text: '',
agentId: 'catty',
attachments: [],
selectedUserSkillSlugs: [],
updatedAt: Date.now(),
}));
ai.showDraftView(SCOPE_KEY);
}}
>
New Chat
</button>
);
}
test('restored workspace enters a blank draft when New Chat is clicked', async () => {
const dom = new JSDOM('<!doctype html><html><body></body></html>', {
url: 'http://localhost',
});
Object.defineProperty(globalThis, 'window', {
configurable: true,
value: dom.window,
});
Object.defineProperty(globalThis, 'document', {
configurable: true,
value: dom.window.document,
});
Object.defineProperty(globalThis, 'localStorage', {
configurable: true,
value: dom.window.localStorage,
});
Object.defineProperty(globalThis, 'CustomEvent', {
configurable: true,
value: dom.window.CustomEvent,
});
Object.defineProperty(globalThis, 'Event', {
configurable: true,
value: dom.window.Event,
});
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
configurable: true,
value: true,
});
dom.window.localStorage.setItem('netcatty_ai_sessions_v1', JSON.stringify([RESTORED_SESSION]));
dom.window.localStorage.setItem('netcatty_ai_active_session_map_v1', JSON.stringify({
'terminal:terminal-a': RESTORED_SESSION.id,
[SCOPE_KEY]: RESTORED_SESSION.id,
}));
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
await act(async () => {
root.render(<NewChatHarness />);
});
const button = container.querySelector('button');
assert.ok(button);
assert.equal(button.dataset.view, 'session');
assert.equal(button.dataset.session, RESTORED_SESSION.id);
await act(async () => {
button.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true }));
});
assert.equal(button.dataset.view, 'draft');
assert.equal(button.dataset.session, '');
await act(async () => root.unmount());
container.remove();
dom.window.close();
});