@zseven-w/pen-core 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +18 -9
  2. package/package.json +5 -5
  3. package/src/__tests__/arc-path.test.ts +23 -23
  4. package/src/__tests__/codegen-utils.test.ts +50 -0
  5. package/src/__tests__/design-md-parser.test.ts +49 -0
  6. package/src/__tests__/font-utils.test.ts +15 -15
  7. package/src/__tests__/layout-engine.test.ts +169 -83
  8. package/src/__tests__/merge-helpers.test.ts +143 -0
  9. package/src/__tests__/node-diff.test.ts +139 -0
  10. package/src/__tests__/node-helpers.test.ts +19 -19
  11. package/src/__tests__/node-merge.test.ts +425 -0
  12. package/src/__tests__/normalize-stroke-fill-schema.test.ts +416 -0
  13. package/src/__tests__/normalize-tree-layout.test.ts +294 -0
  14. package/src/__tests__/normalize.test.ts +119 -80
  15. package/src/__tests__/path-anchors.test.ts +98 -0
  16. package/src/__tests__/resolve-variables-recursive.test.ts +109 -54
  17. package/src/__tests__/strip-redundant-section-fills.test.ts +278 -0
  18. package/src/__tests__/text-measure.test.ts +84 -79
  19. package/src/__tests__/tree-utils.test.ts +133 -102
  20. package/src/__tests__/unwrap-fake-phone-mockup.test.ts +322 -0
  21. package/src/__tests__/variables.test.ts +68 -65
  22. package/src/arc-path.ts +35 -35
  23. package/src/boolean-ops.ts +131 -142
  24. package/src/constants.ts +36 -36
  25. package/src/design-md-parser.ts +363 -0
  26. package/src/font-utils.ts +30 -15
  27. package/src/id.ts +1 -1
  28. package/src/index.ts +47 -13
  29. package/src/layout/engine.ts +255 -224
  30. package/src/layout/normalize-tree.ts +140 -0
  31. package/src/layout/strip-redundant-section-fills.ts +155 -0
  32. package/src/layout/text-measure.ts +130 -106
  33. package/src/layout/unwrap-fake-phone-mockup.ts +147 -0
  34. package/src/merge/index.ts +16 -0
  35. package/src/merge/merge-helpers.ts +113 -0
  36. package/src/merge/node-diff.ts +123 -0
  37. package/src/merge/node-merge.ts +651 -0
  38. package/src/node-helpers.ts +18 -5
  39. package/src/normalize/normalize-stroke-fill-schema.ts +297 -0
  40. package/src/normalize.ts +75 -81
  41. package/src/path-anchors.ts +331 -0
  42. package/src/sync-lock.ts +3 -3
  43. package/src/tree-utils.ts +180 -158
  44. package/src/variables/replace-refs.ts +63 -60
  45. package/src/variables/resolve.ts +98 -106
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from 'vitest'
2
- import type { PenNode } from '@zseven-w/pen-types'
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { PenNode } from '@zseven-w/pen-types';
3
3
  import {
4
4
  parseSizing,
5
5
  defaultLineHeight,
@@ -11,137 +11,142 @@ import {
11
11
  resolveTextContent,
12
12
  countExplicitTextLines,
13
13
  estimateTextHeight,
14
- } from '../layout/text-measure'
14
+ } from '../layout/text-measure';
15
15
 
16
16
  describe('text-measure', () => {
17
17
  describe('parseSizing', () => {
18
18
  it('returns number for number input', () => {
19
- expect(parseSizing(100)).toBe(100)
20
- })
19
+ expect(parseSizing(100)).toBe(100);
20
+ });
21
21
 
22
22
  it('returns "fill" for fill_container', () => {
23
- expect(parseSizing('fill_container')).toBe('fill')
24
- })
23
+ expect(parseSizing('fill_container')).toBe('fill');
24
+ });
25
25
 
26
26
  it('returns "fit" for fit_content', () => {
27
- expect(parseSizing('fit_content')).toBe('fit')
28
- })
27
+ expect(parseSizing('fit_content')).toBe('fit');
28
+ });
29
+
30
+ it('uses numeric hints from fit_content(N)', () => {
31
+ expect(parseSizing('fit_content(600)')).toBe(600);
32
+ });
29
33
 
30
34
  it('parses numeric strings', () => {
31
- expect(parseSizing('200')).toBe(200)
32
- })
35
+ expect(parseSizing('200')).toBe(200);
36
+ });
33
37
 
34
38
  it('returns 0 for non-parseable', () => {
35
- expect(parseSizing(undefined)).toBe(0)
36
- expect(parseSizing('abc')).toBe(0)
37
- })
38
- })
39
+ expect(parseSizing(undefined)).toBe(0);
40
+ expect(parseSizing('abc')).toBe(0);
41
+ });
42
+ });
39
43
 
40
44
  describe('defaultLineHeight', () => {
41
45
  it('returns tight leading for display text', () => {
42
- expect(defaultLineHeight(48)).toBe(1.0)
43
- })
46
+ expect(defaultLineHeight(48)).toBe(1.0);
47
+ });
44
48
 
45
49
  it('returns comfortable leading for body text', () => {
46
- expect(defaultLineHeight(14)).toBe(1.5)
47
- })
48
- })
50
+ expect(defaultLineHeight(14)).toBe(1.5);
51
+ });
52
+ });
49
53
 
50
54
  describe('CJK detection', () => {
51
55
  it('detects CJK code points', () => {
52
- expect(isCjkCodePoint('中'.codePointAt(0)!)).toBe(true)
53
- expect(isCjkCodePoint('あ'.codePointAt(0)!)).toBe(true)
54
- expect(isCjkCodePoint('A'.codePointAt(0)!)).toBe(false)
55
- })
56
+ expect(isCjkCodePoint('中'.codePointAt(0)!)).toBe(true);
57
+ expect(isCjkCodePoint('あ'.codePointAt(0)!)).toBe(true);
58
+ expect(isCjkCodePoint('A'.codePointAt(0)!)).toBe(false);
59
+ });
56
60
 
57
61
  it('hasCjkText detects CJK in strings', () => {
58
- expect(hasCjkText('Hello 世界')).toBe(true)
59
- expect(hasCjkText('Hello World')).toBe(false)
60
- })
61
- })
62
+ expect(hasCjkText('Hello 世界')).toBe(true);
63
+ expect(hasCjkText('Hello World')).toBe(false);
64
+ });
65
+ });
62
66
 
63
67
  describe('estimateGlyphWidth', () => {
64
68
  it('returns 0 for newline', () => {
65
- expect(estimateGlyphWidth('\n', 16)).toBe(0)
66
- })
69
+ expect(estimateGlyphWidth('\n', 16)).toBe(0);
70
+ });
67
71
 
68
72
  it('estimates CJK wider than Latin', () => {
69
- const cjk = estimateGlyphWidth('中', 16)
70
- const latin = estimateGlyphWidth('a', 16)
71
- expect(cjk).toBeGreaterThan(latin)
72
- })
73
+ const cjk = estimateGlyphWidth('中', 16);
74
+ const latin = estimateGlyphWidth('a', 16);
75
+ expect(cjk).toBeGreaterThan(latin);
76
+ });
73
77
 
74
78
  it('estimates uppercase wider than lowercase', () => {
75
- const upper = estimateGlyphWidth('A', 16)
76
- const lower = estimateGlyphWidth('a', 16)
77
- expect(upper).toBeGreaterThan(lower)
78
- })
79
- })
79
+ const upper = estimateGlyphWidth('A', 16);
80
+ const lower = estimateGlyphWidth('a', 16);
81
+ expect(upper).toBeGreaterThan(lower);
82
+ });
83
+ });
80
84
 
81
85
  describe('estimateLineWidth', () => {
82
86
  it('estimates width of a line', () => {
83
- const width = estimateLineWidth('Hello', 16)
84
- expect(width).toBeGreaterThan(0)
85
- })
87
+ const width = estimateLineWidth('Hello', 16);
88
+ expect(width).toBeGreaterThan(0);
89
+ });
86
90
 
87
91
  it('adds letter spacing', () => {
88
- const base = estimateLineWidth('AB', 16, 0)
89
- const spaced = estimateLineWidth('AB', 16, 5)
90
- expect(spaced).toBeGreaterThan(base)
91
- })
92
- })
92
+ const base = estimateLineWidth('AB', 16, 0);
93
+ const spaced = estimateLineWidth('AB', 16, 5);
94
+ expect(spaced).toBeGreaterThan(base);
95
+ });
96
+ });
93
97
 
94
98
  describe('estimateTextWidth', () => {
95
99
  it('returns the widest line', () => {
96
- const width = estimateTextWidth('short\nmuch longer line', 16)
97
- const singleWidth = estimateTextWidth('much longer line', 16)
100
+ const width = estimateTextWidth('short\nmuch longer line', 16);
101
+ const singleWidth = estimateTextWidth('much longer line', 16);
98
102
  // Multi-line should return width of longest line
99
- expect(width).toBeCloseTo(singleWidth, 0)
100
- })
101
- })
103
+ expect(width).toBeCloseTo(singleWidth, 0);
104
+ });
105
+ });
102
106
 
103
107
  describe('resolveTextContent', () => {
104
108
  it('resolves string content', () => {
105
- const node: PenNode = { id: '1', type: 'text', content: 'Hello' }
106
- expect(resolveTextContent(node)).toBe('Hello')
107
- })
109
+ const node: PenNode = { id: '1', type: 'text', content: 'Hello' };
110
+ expect(resolveTextContent(node)).toBe('Hello');
111
+ });
108
112
 
109
113
  it('resolves styled segment content', () => {
110
114
  const node: PenNode = {
111
- id: '1', type: 'text',
115
+ id: '1',
116
+ type: 'text',
112
117
  content: [{ text: 'Hello ' }, { text: 'World' }],
113
- }
114
- expect(resolveTextContent(node)).toBe('Hello World')
115
- })
118
+ };
119
+ expect(resolveTextContent(node)).toBe('Hello World');
120
+ });
116
121
 
117
122
  it('returns empty for non-text nodes', () => {
118
- const node: PenNode = { id: '1', type: 'rectangle' }
119
- expect(resolveTextContent(node)).toBe('')
120
- })
121
- })
123
+ const node: PenNode = { id: '1', type: 'rectangle' };
124
+ expect(resolveTextContent(node)).toBe('');
125
+ });
126
+ });
122
127
 
123
128
  describe('countExplicitTextLines', () => {
124
129
  it('counts newlines', () => {
125
- expect(countExplicitTextLines('a\nb\nc')).toBe(3)
126
- })
130
+ expect(countExplicitTextLines('a\nb\nc')).toBe(3);
131
+ });
127
132
 
128
133
  it('returns 1 for empty string', () => {
129
- expect(countExplicitTextLines('')).toBe(1)
130
- })
131
- })
134
+ expect(countExplicitTextLines('')).toBe(1);
135
+ });
136
+ });
132
137
 
133
138
  describe('estimateTextHeight', () => {
134
139
  it('estimates height for single-line text', () => {
135
- const node: PenNode = { id: '1', type: 'text', content: 'Hello', fontSize: 16 }
136
- const height = estimateTextHeight(node)
137
- expect(height).toBeGreaterThan(0)
138
- expect(height).toBeLessThan(50)
139
- })
140
+ const node: PenNode = { id: '1', type: 'text', content: 'Hello', fontSize: 16 };
141
+ const height = estimateTextHeight(node);
142
+ expect(height).toBeGreaterThan(0);
143
+ expect(height).toBeLessThan(50);
144
+ });
140
145
 
141
146
  it('estimates taller for multi-line text', () => {
142
- const single: PenNode = { id: '1', type: 'text', content: 'Hello', fontSize: 16 }
143
- const multi: PenNode = { id: '2', type: 'text', content: 'Hello\nWorld', fontSize: 16 }
144
- expect(estimateTextHeight(multi)).toBeGreaterThan(estimateTextHeight(single))
145
- })
146
- })
147
- })
147
+ const single: PenNode = { id: '1', type: 'text', content: 'Hello', fontSize: 16 };
148
+ const multi: PenNode = { id: '2', type: 'text', content: 'Hello\nWorld', fontSize: 16 };
149
+ expect(estimateTextHeight(multi)).toBeGreaterThan(estimateTextHeight(single));
150
+ });
151
+ });
152
+ });
@@ -1,5 +1,5 @@
1
- import { describe, it, expect } from 'vitest'
2
- import type { PenNode, PenDocument } from '@zseven-w/pen-types'
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { PenNode, PenDocument } from '@zseven-w/pen-types';
3
3
  import {
4
4
  createEmptyDocument,
5
5
  findNodeInTree,
@@ -14,157 +14,188 @@ import {
14
14
  migrateToPages,
15
15
  DEFAULT_FRAME_ID,
16
16
  DEFAULT_PAGE_ID,
17
- } from '../tree-utils'
17
+ } from '../tree-utils';
18
18
 
19
19
  const frame = (id: string, children: PenNode[] = []): PenNode => ({
20
- id, type: 'frame', name: id, x: 0, y: 0, width: 100, height: 100,
21
- fill: [{ type: 'solid', color: '#fff' }], children,
22
- })
20
+ id,
21
+ type: 'frame',
22
+ name: id,
23
+ x: 0,
24
+ y: 0,
25
+ width: 100,
26
+ height: 100,
27
+ fill: [{ type: 'solid', color: '#fff' }],
28
+ children,
29
+ });
23
30
 
24
31
  const rect = (id: string): PenNode => ({
25
- id, type: 'rectangle', x: 0, y: 0, width: 50, height: 50,
26
- })
32
+ id,
33
+ type: 'rectangle',
34
+ x: 0,
35
+ y: 0,
36
+ width: 50,
37
+ height: 50,
38
+ });
27
39
 
28
40
  describe('tree-utils', () => {
29
41
  describe('createEmptyDocument', () => {
30
42
  it('creates a document with a default page and root frame', () => {
31
- const doc = createEmptyDocument()
32
- expect(doc.version).toBe('1.0.0')
33
- expect(doc.pages).toHaveLength(1)
34
- expect(doc.pages![0].id).toBe(DEFAULT_PAGE_ID)
35
- expect(doc.pages![0].children).toHaveLength(1)
36
- expect(doc.pages![0].children[0].id).toBe(DEFAULT_FRAME_ID)
37
- })
38
- })
43
+ const doc = createEmptyDocument();
44
+ expect(doc.version).toBe('1.0.0');
45
+ expect(doc.pages).toHaveLength(1);
46
+ expect(doc.pages![0].id).toBe(DEFAULT_PAGE_ID);
47
+ expect(doc.pages![0].children).toHaveLength(1);
48
+ expect(doc.pages![0].children[0].id).toBe(DEFAULT_FRAME_ID);
49
+ });
50
+ });
39
51
 
40
52
  describe('findNodeInTree', () => {
41
53
  it('finds a node by id at root level', () => {
42
- const nodes = [rect('a'), rect('b')]
43
- expect(findNodeInTree(nodes, 'b')?.id).toBe('b')
44
- })
54
+ const nodes = [rect('a'), rect('b')];
55
+ expect(findNodeInTree(nodes, 'b')?.id).toBe('b');
56
+ });
45
57
 
46
58
  it('finds a nested node', () => {
47
- const nodes = [frame('parent', [rect('child')])]
48
- expect(findNodeInTree(nodes, 'child')?.id).toBe('child')
49
- })
59
+ const nodes = [frame('parent', [rect('child')])];
60
+ expect(findNodeInTree(nodes, 'child')?.id).toBe('child');
61
+ });
50
62
 
51
63
  it('returns undefined for missing node', () => {
52
- expect(findNodeInTree([rect('a')], 'missing')).toBeUndefined()
53
- })
54
- })
64
+ expect(findNodeInTree([rect('a')], 'missing')).toBeUndefined();
65
+ });
66
+ });
55
67
 
56
68
  describe('findParentInTree', () => {
57
69
  it('finds the parent of a child node', () => {
58
- const nodes = [frame('parent', [rect('child')])]
59
- expect(findParentInTree(nodes, 'child')?.id).toBe('parent')
60
- })
70
+ const nodes = [frame('parent', [rect('child')])];
71
+ expect(findParentInTree(nodes, 'child')?.id).toBe('parent');
72
+ });
61
73
 
62
74
  it('returns undefined for root nodes', () => {
63
- expect(findParentInTree([rect('root')], 'root')).toBeUndefined()
64
- })
65
- })
75
+ expect(findParentInTree([rect('root')], 'root')).toBeUndefined();
76
+ });
77
+ });
66
78
 
67
79
  describe('removeNodeFromTree', () => {
68
80
  it('removes a root node', () => {
69
- const result = removeNodeFromTree([rect('a'), rect('b')], 'a')
70
- expect(result).toHaveLength(1)
71
- expect(result[0].id).toBe('b')
72
- })
81
+ const result = removeNodeFromTree([rect('a'), rect('b')], 'a');
82
+ expect(result).toHaveLength(1);
83
+ expect(result[0].id).toBe('b');
84
+ });
73
85
 
74
86
  it('removes a nested node', () => {
75
- const nodes = [frame('parent', [rect('child1'), rect('child2')])]
76
- const result = removeNodeFromTree(nodes, 'child1')
77
- const parent = result[0] as PenNode & { children: PenNode[] }
78
- expect(parent.children).toHaveLength(1)
79
- expect(parent.children[0].id).toBe('child2')
80
- })
81
- })
87
+ const nodes = [frame('parent', [rect('child1'), rect('child2')])];
88
+ const result = removeNodeFromTree(nodes, 'child1');
89
+ const parent = result[0] as PenNode & { children: PenNode[] };
90
+ expect(parent.children).toHaveLength(1);
91
+ expect(parent.children[0].id).toBe('child2');
92
+ });
93
+ });
82
94
 
83
95
  describe('updateNodeInTree', () => {
84
96
  it('updates a node by id', () => {
85
- const nodes = [rect('a')]
86
- const result = updateNodeInTree(nodes, 'a', { name: 'updated' })
87
- expect(result[0].name).toBe('updated')
88
- })
97
+ const nodes = [rect('a')];
98
+ const result = updateNodeInTree(nodes, 'a', { name: 'updated' });
99
+ expect(result[0].name).toBe('updated');
100
+ });
89
101
 
90
102
  it('updates a nested node', () => {
91
- const nodes = [frame('parent', [rect('child')])]
92
- const result = updateNodeInTree(nodes, 'child', { name: 'updated' })
93
- const parent = result[0] as PenNode & { children: PenNode[] }
94
- expect(parent.children[0].name).toBe('updated')
95
- })
96
- })
103
+ const nodes = [frame('parent', [rect('child')])];
104
+ const result = updateNodeInTree(nodes, 'child', { name: 'updated' });
105
+ const parent = result[0] as PenNode & { children: PenNode[] };
106
+ expect(parent.children[0].name).toBe('updated');
107
+ });
108
+
109
+ it('reuses untouched branches when updating a nested node', () => {
110
+ const sibling = frame('sibling', [rect('sibling-child')]);
111
+ const parent = frame('parent', [rect('child')]);
112
+ const nodes = [parent, sibling];
113
+
114
+ const result = updateNodeInTree(nodes, 'child', { name: 'updated' });
115
+
116
+ expect(result).not.toBe(nodes);
117
+ expect(result[0]).not.toBe(parent);
118
+ expect((result[0] as PenNode & { children: PenNode[] }).children[0].name).toBe('updated');
119
+ expect(result[1]).toBe(sibling);
120
+ });
121
+
122
+ it('returns the original tree for no-op updates', () => {
123
+ const nodes = [rect('a')];
124
+ const result = updateNodeInTree(nodes, 'a', { x: 0 });
125
+ expect(result).toBe(nodes);
126
+ });
127
+ });
97
128
 
98
129
  describe('flattenNodes', () => {
99
130
  it('flattens a nested tree', () => {
100
- const nodes = [frame('a', [rect('b'), frame('c', [rect('d')])])]
101
- const flat = flattenNodes(nodes)
102
- expect(flat.map(n => n.id)).toEqual(['a', 'b', 'c', 'd'])
103
- })
104
- })
131
+ const nodes = [frame('a', [rect('b'), frame('c', [rect('d')])])];
132
+ const flat = flattenNodes(nodes);
133
+ expect(flat.map((n) => n.id)).toEqual(['a', 'b', 'c', 'd']);
134
+ });
135
+ });
105
136
 
106
137
  describe('insertNodeInTree', () => {
107
138
  it('inserts at root level', () => {
108
- const result = insertNodeInTree([rect('a')], null, rect('b'))
109
- expect(result).toHaveLength(2)
110
- expect(result[1].id).toBe('b')
111
- })
139
+ const result = insertNodeInTree([rect('a')], null, rect('b'));
140
+ expect(result).toHaveLength(2);
141
+ expect(result[1].id).toBe('b');
142
+ });
112
143
 
113
144
  it('inserts into a parent', () => {
114
- const nodes = [frame('parent', [rect('existing')])]
115
- const result = insertNodeInTree(nodes, 'parent', rect('new'))
116
- const parent = result[0] as PenNode & { children: PenNode[] }
117
- expect(parent.children).toHaveLength(2)
118
- expect(parent.children[1].id).toBe('new')
119
- })
145
+ const nodes = [frame('parent', [rect('existing')])];
146
+ const result = insertNodeInTree(nodes, 'parent', rect('new'));
147
+ const parent = result[0] as PenNode & { children: PenNode[] };
148
+ expect(parent.children).toHaveLength(2);
149
+ expect(parent.children[1].id).toBe('new');
150
+ });
120
151
 
121
152
  it('inserts at a specific index', () => {
122
- const nodes = [frame('parent', [rect('a'), rect('c')])]
123
- const result = insertNodeInTree(nodes, 'parent', rect('b'), 1)
124
- const parent = result[0] as PenNode & { children: PenNode[] }
125
- expect(parent.children.map(n => n.id)).toEqual(['a', 'b', 'c'])
126
- })
127
- })
153
+ const nodes = [frame('parent', [rect('a'), rect('c')])];
154
+ const result = insertNodeInTree(nodes, 'parent', rect('b'), 1);
155
+ const parent = result[0] as PenNode & { children: PenNode[] };
156
+ expect(parent.children.map((n) => n.id)).toEqual(['a', 'b', 'c']);
157
+ });
158
+ });
128
159
 
129
160
  describe('isDescendantOf', () => {
130
161
  it('returns true for a descendant', () => {
131
- const nodes = [frame('a', [frame('b', [rect('c')])])]
132
- expect(isDescendantOf(nodes, 'c', 'a')).toBe(true)
133
- })
162
+ const nodes = [frame('a', [frame('b', [rect('c')])])];
163
+ expect(isDescendantOf(nodes, 'c', 'a')).toBe(true);
164
+ });
134
165
 
135
166
  it('returns false for non-descendant', () => {
136
- const nodes = [frame('a', [rect('b')]), rect('c')]
137
- expect(isDescendantOf(nodes, 'c', 'a')).toBe(false)
138
- })
139
- })
167
+ const nodes = [frame('a', [rect('b')]), rect('c')];
168
+ expect(isDescendantOf(nodes, 'c', 'a')).toBe(false);
169
+ });
170
+ });
140
171
 
141
172
  describe('page helpers', () => {
142
173
  it('getActivePageChildren returns page children', () => {
143
- const doc = createEmptyDocument()
144
- const children = getActivePageChildren(doc, DEFAULT_PAGE_ID)
145
- expect(children).toHaveLength(1)
146
- expect(children[0].id).toBe(DEFAULT_FRAME_ID)
147
- })
174
+ const doc = createEmptyDocument();
175
+ const children = getActivePageChildren(doc, DEFAULT_PAGE_ID);
176
+ expect(children).toHaveLength(1);
177
+ expect(children[0].id).toBe(DEFAULT_FRAME_ID);
178
+ });
148
179
 
149
180
  it('setActivePageChildren replaces page children', () => {
150
- const doc = createEmptyDocument()
151
- const newChildren = [rect('new')]
152
- const updated = setActivePageChildren(doc, DEFAULT_PAGE_ID, newChildren)
153
- expect(updated.pages![0].children).toHaveLength(1)
154
- expect(updated.pages![0].children[0].id).toBe('new')
155
- })
181
+ const doc = createEmptyDocument();
182
+ const newChildren = [rect('new')];
183
+ const updated = setActivePageChildren(doc, DEFAULT_PAGE_ID, newChildren);
184
+ expect(updated.pages![0].children).toHaveLength(1);
185
+ expect(updated.pages![0].children[0].id).toBe('new');
186
+ });
156
187
 
157
188
  it('migrateToPages wraps legacy doc', () => {
158
- const legacy: PenDocument = { version: '1.0.0', children: [rect('a')] }
159
- const migrated = migrateToPages(legacy)
160
- expect(migrated.pages).toHaveLength(1)
161
- expect(migrated.pages![0].children[0].id).toBe('a')
162
- expect(migrated.children).toEqual([])
163
- })
189
+ const legacy: PenDocument = { version: '1.0.0', children: [rect('a')] };
190
+ const migrated = migrateToPages(legacy);
191
+ expect(migrated.pages).toHaveLength(1);
192
+ expect(migrated.pages![0].children[0].id).toBe('a');
193
+ expect(migrated.children).toEqual([]);
194
+ });
164
195
 
165
196
  it('migrateToPages preserves existing pages', () => {
166
- const doc = createEmptyDocument()
167
- expect(migrateToPages(doc)).toBe(doc)
168
- })
169
- })
170
- })
197
+ const doc = createEmptyDocument();
198
+ expect(migrateToPages(doc)).toBe(doc);
199
+ });
200
+ });
201
+ });