Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { describe, test, expect, afterEach } from 'vitest';
|
|
import type { NodeTree } from '@craftjs/core';
|
|
import { getClipboardTree, setClipboardTree } from './clipboard';
|
|
|
|
function makeTree(rootId: string, props: Record<string, unknown> = {}): 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<string, unknown>).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' });
|
|
});
|
|
});
|