Snippet naming: content-derived on publish, frozen on explicit rename

This commit is contained in:
2026-06-13 10:13:57 +03:00
parent 92bfe888b5
commit 4e5108f434
14 changed files with 508 additions and 16 deletions
@@ -89,6 +89,66 @@ describe('SnippetLibrary metadata panel (spec §02)', () => {
expect(useSnippetStore.getState().snippets[0].name).toBe('Renamed');
});
test('a publish-derived rename is adopted by the panel, not reverted by its auto-save', () => {
// Regression: the panel's local name state lagged a publish rename, so its
// debounced auto-save wrote the stale default back — the rename flickered
// for ~400ms in the list and then undid itself.
vi.useFakeTimers();
const s = createSnippet({ id: 'a', now: new Date('2026-01-01T00:00:00Z') }); // auto name
useSnippetStore.getState().hydrate([s], 'a');
act(() => {
root.render(<SnippetLibrary />);
});
act(() => {
useSnippetStore
.getState()
.updateDraft(
JSON.stringify({
mark: 'bar',
encoding: { x: { field: 'Region' }, y: { aggregate: 'count' } },
}),
);
useSnippetStore.getState().publish(new Date('2026-02-01T00:00:00Z'));
});
expect(nameInput().value).toBe('Bar chart of count by Region');
act(() => {
vi.advanceTimersByTime(1000); // any pending auto-save settles
});
expect(useSnippetStore.getState().snippets[0].name).toBe('Bar chart of count by Region');
});
test('a name edit in progress survives a publish rename (user text wins)', () => {
vi.useFakeTimers();
const s = createSnippet({ id: 'a', now: new Date('2026-01-01T00:00:00Z') });
useSnippetStore.getState().hydrate([s], 'a');
act(() => {
root.render(<SnippetLibrary />);
});
act(() => typeInto(nameInput(), 'My Chart')); // diverged, debounce pending
act(() => {
useSnippetStore
.getState()
.updateDraft(
JSON.stringify({
mark: 'bar',
encoding: { x: { field: 'Region' }, y: { aggregate: 'count' } },
}),
);
useSnippetStore.getState().publish();
});
expect(nameInput().value).toBe('My Chart'); // not clobbered by the derived name
act(() => {
vi.advanceTimersByTime(1000);
});
expect(useSnippetStore.getState().snippets[0].name).toBe('My Chart');
});
test('does not loop on a search/sort state change (render-loop guard)', async () => {
// A selector that returned a fresh filtered array would re-render forever
// (MEMORY → "Zustand stable selectors"); the component derives via useMemo.