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
131 lines
3.9 KiB
TypeScript
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();
|
|
});
|