import { describe, test, expect, afterEach } from 'vitest'; import type { NodeTree } from '@craftjs/core'; import { getClipboardTree, setClipboardTree } from './clipboard'; function makeTree(rootId: string, props: Record = {}): NodeTree { return { rootNodeId: rootId, nodes: { [rootId]: { id: rootId, data: { type: { resolvedName: 'Container' }, name: 'Container', displayName: 'Container', props, custom: {}, isCanvas: false, parent: 'wherever-it-originally-lived', nodes: [], linkedNodes: {}, hidden: false, }, info: {}, events: { selected: false, dragged: false, hovered: false }, dom: null, related: {}, rules: {}, _hydrationTimestamp: 0, } as unknown as NodeTree['nodes'][string], }, }; } describe('clipboard', () => { afterEach(() => { setClipboardTree(null); }); test('starts empty', () => { expect(getClipboardTree()).toBeNull(); }); test('set then get returns a tree with the same root id and shape', () => { const tree = makeTree('node-123', { text: 'hello' }); setClipboardTree(tree); const got = getClipboardTree(); expect(got).not.toBeNull(); expect(got!.rootNodeId).toBe('node-123'); expect(got!.nodes['node-123'].data.props).toEqual({ text: 'hello' }); }); test('is a shared module-level store -- overwriting replaces the previous value', () => { setClipboardTree(makeTree('first')); setClipboardTree(makeTree('second')); expect(getClipboardTree()!.rootNodeId).toBe('second'); }); test('can be cleared back to null', () => { setClipboardTree(makeTree('node-123')); setClipboardTree(null); expect(getClipboardTree()).toBeNull(); }); test('deep-clones on set: mutating the original tree after set does not affect the stored snapshot', () => { const original = makeTree('node-123', { text: 'original' }); setClipboardTree(original); // Mutate the original tree's props object directly (as if the source // node were edited, or the same live node got copied again). (original.nodes['node-123'].data.props as Record).text = 'mutated'; expect(getClipboardTree()!.nodes['node-123'].data.props).toEqual({ text: 'original' }); }); test('deep-clones nested props (arrays/objects), not just the top-level props object', () => { const original = makeTree('node-123', { links: [{ url: 'https://example.com' }] }); setClipboardTree(original); (original.nodes['node-123'].data.props as any).links[0].url = 'https://mutated.example.com'; expect((getClipboardTree()!.nodes['node-123'].data.props as any).links[0].url).toBe( 'https://example.com', ); }); test('survives the original tree object being discarded entirely (detached copy, not a live reference)', () => { let tree: NodeTree | null = makeTree('node-abc', { text: 'snapshot' }); setClipboardTree(tree); tree = null; // simulate the original page's node/tree going away entirely const got = getClipboardTree(); expect(got).not.toBeNull(); expect(got!.nodes['node-abc'].data.props).toEqual({ text: 'snapshot' }); }); });