displayArtifact.test.tsx3.0 KBView on GitHub /**
* Phase 1 (design: chat-display-artifact) — the display artifact is per-thread.
* `setSelectedArtifact` writes the ACTIVE thread's slot; `getDisplayArtifact` reads it and is
* null when nothing is open; switching threads switches the resolved window.
*/
import { DEFAULT_THREAD_ID } from '@/store/messages/MessageTypes';
import { useCedarStore } from '@/store/CedarStore';
const T1 = DEFAULT_THREAD_ID;
const T2 = 'thread-2';
function seedTwoThreads() {
useCedarStore.setState((state) => ({
...state,
threadMap: {
[T1]: { id: T1, messages: [], selectedArtifact: null },
[T2]: { id: T2, messages: [], selectedArtifact: null },
},
mainThreadId: T1,
activeThreadId: T1,
messages: [],
}));
}
describe('per-thread display artifact', () => {
beforeEach(seedTwoThreads);
it('setSelectedArtifact writes only the active thread', () => {
useCedarStore.getState().setSelectedArtifact({ kind: 'conversation', id: 'conv_a' });
const { threadMap } = useCedarStore.getState();
expect(threadMap[T1].selectedArtifact).toEqual({ kind: 'conversation', id: 'conv_a' });
expect(threadMap[T2].selectedArtifact).toBeNull(); // the other thread is untouched
});
it('getDisplayArtifact resolves the active thread, and is null when nothing is open', () => {
const store = useCedarStore.getState();
// Nothing open on the active thread → null. There is no default artifact.
expect(store.getDisplayArtifact()).toBeNull();
store.setSelectedArtifact({ kind: 'file', id: 'doc_1' });
expect(useCedarStore.getState().getDisplayArtifact()).toEqual({ kind: 'file', id: 'doc_1' });
});
it('switching the active thread switches the resolved display artifact', () => {
const store = useCedarStore.getState();
store.setSelectedArtifact({ kind: 'conversation', id: 'conv_a' }); // on T1
// Point the active thread at T2 (which has nothing open) → null.
useCedarStore.setState((s) => ({ ...s, mainThreadId: T2, activeThreadId: T2 }));
expect(useCedarStore.getState().getDisplayArtifact()).toBeNull();
// Back to T1 → its conversation is restored (per-thread persistence).
useCedarStore.setState((s) => ({ ...s, mainThreadId: T1, activeThreadId: T1 }));
expect(useCedarStore.getState().getDisplayArtifact()).toEqual({
kind: 'conversation',
id: 'conv_a',
});
});
// With no active thread there is nowhere to write the pointer, and the write used to be
// dropped — which is exactly the state a deep-link opened in a fresh tab arrives in (nothing
// has seeded a chat yet), so the link opened nothing. Opening now seeds the chat it needs.
it('seeds a chat thread when there is no active one, instead of dropping the open', () => {
useCedarStore.setState((s) => ({ ...s, threadMap: {}, mainThreadId: '', activeThreadId: '' }));
useCedarStore.getState().setSelectedArtifact({ kind: 'file', id: 'doc_1' });
expect(useCedarStore.getState().getDisplayArtifact()).toEqual({ kind: 'file', id: 'doc_1' });
});
});