mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add snippet metadata panel, duplicate, and immediate-load preview (M4.5)
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest';
|
||||
import {
|
||||
CURRENT_SNIPPET_VERSION,
|
||||
createSnippet,
|
||||
duplicateSnippet,
|
||||
formatSnippetSize,
|
||||
generateSnippetName,
|
||||
hasUnpublishedChanges,
|
||||
@@ -67,6 +68,55 @@ describe('generateSnippetName', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('duplicateSnippet', () => {
|
||||
const source = {
|
||||
...createSnippet({
|
||||
id: 'src',
|
||||
name: 'My Chart',
|
||||
spec: '{"published":1}',
|
||||
now: new Date('2026-01-01T00:00:00Z'),
|
||||
}),
|
||||
draftSpec: '{"draft":1}',
|
||||
comment: 'a note',
|
||||
tags: ['imported'],
|
||||
datasetRefs: ['Sales'],
|
||||
meta: { createdWith: 'chart-builder' },
|
||||
};
|
||||
|
||||
test('suffixes the name with "(copy)" and takes a new identity + timestamps', () => {
|
||||
const now = new Date('2026-03-04T05:06:07Z');
|
||||
const copy = duplicateSnippet(source, { id: 'copy', now });
|
||||
|
||||
expect(copy.id).toBe('copy');
|
||||
expect(copy.id).not.toBe(source.id);
|
||||
expect(copy.name).toBe('My Chart (copy)');
|
||||
expect(copy.version).toBe(CURRENT_SNIPPET_VERSION);
|
||||
expect(copy.created).toBe(now.toISOString());
|
||||
expect(copy.modified).toBe(now.toISOString());
|
||||
});
|
||||
|
||||
test('carries over both spec versions, comment, tags, refs, and meta', () => {
|
||||
const copy = duplicateSnippet(source);
|
||||
expect(copy.spec).toBe('{"published":1}');
|
||||
expect(copy.draftSpec).toBe('{"draft":1}');
|
||||
expect(copy.comment).toBe('a note');
|
||||
expect(copy.tags).toEqual(['imported']);
|
||||
expect(copy.datasetRefs).toEqual(['Sales']);
|
||||
expect(copy.meta).toEqual({ createdWith: 'chart-builder' });
|
||||
});
|
||||
|
||||
test('clones mutable members so the copy shares no references with its source', () => {
|
||||
const copy = duplicateSnippet(source);
|
||||
expect(copy.tags).not.toBe(source.tags);
|
||||
expect(copy.datasetRefs).not.toBe(source.datasetRefs);
|
||||
expect(copy.meta).not.toBe(source.meta);
|
||||
});
|
||||
|
||||
test('generates a unique id by default', () => {
|
||||
expect(duplicateSnippet(source).id).not.toBe(duplicateSnippet(source).id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasUnpublishedChanges', () => {
|
||||
test('false when draft matches published, true once draft diverges', () => {
|
||||
const s = createSnippet({ now: new Date('2026-06-04T00:00:00Z') });
|
||||
|
||||
@@ -126,6 +126,35 @@ export function createSnippet(options: CreateSnippetOptions = {}): Snippet {
|
||||
};
|
||||
}
|
||||
|
||||
export interface DuplicateSnippetOptions {
|
||||
/** Clock injection for deterministic tests; defaults to the current time. */
|
||||
now?: Date;
|
||||
/** Id injection for deterministic tests; defaults to a random UUID. */
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an independent copy of a snippet (spec §02 → Duplicate). The copy carries
|
||||
* over the specification (both published and draft), comment, tags, and dataset
|
||||
* references, gets a new identity and fresh created/modified timestamps, and a name
|
||||
* suffixed "(copy)". Mutable members are cloned so the copy shares no references
|
||||
* with its source.
|
||||
*/
|
||||
export function duplicateSnippet(source: Snippet, options: DuplicateSnippetOptions = {}): Snippet {
|
||||
const iso = (options.now ?? new Date()).toISOString();
|
||||
return {
|
||||
...source,
|
||||
id: options.id ?? crypto.randomUUID(),
|
||||
version: CURRENT_SNIPPET_VERSION,
|
||||
name: `${source.name} (copy)`,
|
||||
created: iso,
|
||||
modified: iso,
|
||||
tags: [...source.tags],
|
||||
datasetRefs: [...source.datasetRefs],
|
||||
meta: { ...source.meta },
|
||||
};
|
||||
}
|
||||
|
||||
/** True when the snippet's draft differs from its published spec (§03D). */
|
||||
export function hasUnpublishedChanges(snippet: Snippet): boolean {
|
||||
return snippet.draftSpec !== snippet.spec;
|
||||
|
||||
Reference in New Issue
Block a user