mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Snippet naming: content-derived on publish, frozen on explicit rename
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -99,6 +99,19 @@ function SnippetMeta({
|
||||
const [name, setName] = useState(snippet.name);
|
||||
const [comment, setCommentLocal] = useState(snippet.comment);
|
||||
|
||||
// The store can rename underneath this panel (publish's content-derived
|
||||
// naming, spec §03D). Adopt the new store name unless the local field has
|
||||
// diverged — i.e. the user is mid-edit, and their text wins. Without this,
|
||||
// the stale local value differs from the store and the auto-save below
|
||||
// writes the old name right back, silently undoing the publish rename.
|
||||
const lastStoreName = useRef(snippet.name);
|
||||
useEffect(() => {
|
||||
if (snippet.name === lastStoreName.current) return;
|
||||
const previous = lastStoreName.current;
|
||||
lastStoreName.current = snippet.name;
|
||||
setName((local) => (local === previous ? snippet.name : local));
|
||||
}, [snippet.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (name === snippet.name) return;
|
||||
const t = setTimeout(() => renameSnippet(snippet.id, name), META_AUTOSAVE_MS);
|
||||
|
||||
Reference in New Issue
Block a user