@flighthq/scene-document 0.4.1-edge.28.7f8e221
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.
- package/LICENSE.md +9 -0
- package/README.md +17 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/sceneDocumentScene2DMaterialization.d.ts +8 -0
- package/dist/sceneDocumentScene2DMaterialization.d.ts.map +1 -0
- package/dist/sceneDocumentScene2DMaterialization.js +287 -0
- package/dist/sceneDocumentScene2DMaterialization.js.map +1 -0
- package/dist/sceneDocumentYamlSubset.d.ts +25 -0
- package/dist/sceneDocumentYamlSubset.d.ts.map +1 -0
- package/dist/sceneDocumentYamlSubset.js +647 -0
- package/dist/sceneDocumentYamlSubset.js.map +1 -0
- package/package.json +54 -0
- package/src/sceneDocumentScene2DMaterialization.test.ts +395 -0
- package/src/sceneDocumentYamlSubset.test.ts +229 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { parseSceneDocumentYamlSubset } from './sceneDocumentYamlSubset';
|
|
2
|
+
|
|
3
|
+
describe('parseSceneDocumentYamlSubset', () => {
|
|
4
|
+
it('parses block sequences together with block and flow mappings', () => {
|
|
5
|
+
expect(
|
|
6
|
+
parseSceneDocumentYamlSubset(`scene:
|
|
7
|
+
kind: Scene2D
|
|
8
|
+
position: { x: 1, y: -2, metadata: { visible: true } }
|
|
9
|
+
children:
|
|
10
|
+
- kind: Sprite
|
|
11
|
+
name: hero
|
|
12
|
+
- { kind: Shape, visible: false }
|
|
13
|
+
`),
|
|
14
|
+
).toEqual({
|
|
15
|
+
ok: true,
|
|
16
|
+
value: {
|
|
17
|
+
scene: {
|
|
18
|
+
kind: 'Scene2D',
|
|
19
|
+
position: { x: 1, y: -2, metadata: { visible: true } },
|
|
20
|
+
children: [
|
|
21
|
+
{ kind: 'Sprite', name: 'hero' },
|
|
22
|
+
{ kind: 'Shape', visible: false },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('applies explicit scalar rules without YAML 1.1 implicit typing', () => {
|
|
30
|
+
expect(
|
|
31
|
+
parseSceneDocumentYamlSubset(`nullValue: null
|
|
32
|
+
trueValue: true
|
|
33
|
+
falseValue: false
|
|
34
|
+
integer: -12
|
|
35
|
+
decimal: 1.25
|
|
36
|
+
exponent: -1.25e2
|
|
37
|
+
color: 0x3366ccff
|
|
38
|
+
legacyNo: no
|
|
39
|
+
legacyOff: off
|
|
40
|
+
leadingZero: 012
|
|
41
|
+
sexagesimal: 1:20
|
|
42
|
+
quoted: "line\\nvalue\\u0021"
|
|
43
|
+
singleQuoted: 'Flight''s subset'
|
|
44
|
+
`),
|
|
45
|
+
).toEqual({
|
|
46
|
+
ok: true,
|
|
47
|
+
value: {
|
|
48
|
+
nullValue: null,
|
|
49
|
+
trueValue: true,
|
|
50
|
+
falseValue: false,
|
|
51
|
+
integer: -12,
|
|
52
|
+
decimal: 1.25,
|
|
53
|
+
exponent: -125,
|
|
54
|
+
color: 0x3366ccff,
|
|
55
|
+
legacyNo: 'no',
|
|
56
|
+
legacyOff: 'off',
|
|
57
|
+
leadingZero: '012',
|
|
58
|
+
sexagesimal: '1:20',
|
|
59
|
+
quoted: 'line\nvalue!',
|
|
60
|
+
singleQuoted: "Flight's subset",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('uses one token-aware scan for quoted, plain, escaped, and commented indicators', () => {
|
|
66
|
+
expect(
|
|
67
|
+
parseSceneDocumentYamlSubset(`quoted: "&anchor *alias !!tag --- ... [ ] { } , # literal"
|
|
68
|
+
escaped: "escaped quote: \\" [still scalar]"
|
|
69
|
+
single: 'it''s [scalar] ¬-anchor'
|
|
70
|
+
ampersand: rock&roll
|
|
71
|
+
bang: bang!value
|
|
72
|
+
apostrophe: don't
|
|
73
|
+
url: https://host/a#fragment
|
|
74
|
+
# &anchor *alias !!tag --- ... [ ] { } ,
|
|
75
|
+
`),
|
|
76
|
+
).toEqual({
|
|
77
|
+
ok: true,
|
|
78
|
+
value: {
|
|
79
|
+
quoted: '&anchor *alias !!tag --- ... [ ] { } , # literal',
|
|
80
|
+
escaped: 'escaped quote: " [still scalar]',
|
|
81
|
+
single: "it's [scalar] ¬-anchor",
|
|
82
|
+
ampersand: 'rock&roll',
|
|
83
|
+
bang: 'bang!value',
|
|
84
|
+
apostrophe: "don't",
|
|
85
|
+
url: 'https://host/a#fragment',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it.each([
|
|
91
|
+
['value: &root { child: true }', 'flight-document.unsupported.anchor'],
|
|
92
|
+
['value: *root', 'flight-document.unsupported.alias'],
|
|
93
|
+
['value: !!str text', 'flight-document.unsupported.tag'],
|
|
94
|
+
['value: !local text', 'flight-document.unsupported.tag'],
|
|
95
|
+
['---\nvalue: true', 'flight-document.unsupported.document-separator'],
|
|
96
|
+
['...\n', 'flight-document.unsupported.document-separator'],
|
|
97
|
+
])('names the out-of-subset refusal for %s', (source, kind) => {
|
|
98
|
+
const refusal = expectRefusal(source, kind);
|
|
99
|
+
expect(refusal.limit).toBeNull();
|
|
100
|
+
expect(refusal.actual).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('recovers lexical state before refusing a real indicator after plain apostrophes and escapes', () => {
|
|
104
|
+
expectRefusal("value: don't &root", 'flight-document.unsupported.anchor');
|
|
105
|
+
expectRefusal('value: "quoted \\" value" &root', 'flight-document.unsupported.anchor');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('names malformed and ambiguous constructs instead of guessing', () => {
|
|
109
|
+
expectRefusal('items: [one, two]', 'flight-document.unsupported.flow-sequence');
|
|
110
|
+
expectRefusal('value: |\n text', 'flight-document.unsupported.block-scalar');
|
|
111
|
+
expectRefusal('value: 1\nvalue: 2', 'flight-document.syntax.duplicate-key');
|
|
112
|
+
expectRefusal('value: 1e999', 'flight-document.scalar.number-out-of-range');
|
|
113
|
+
expectRefusal('value: "bad\\q"', 'flight-document.syntax.invalid-escape');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('accepts exactly 4,194,304 document code units and refuses the next code unit first', () => {
|
|
117
|
+
const atLimit = `#${'x'.repeat(4_194_303)}`;
|
|
118
|
+
expect(atLimit.length).toBe(4_194_304);
|
|
119
|
+
expect(parseSceneDocumentYamlSubset(atLimit)).toEqual({ ok: true, value: null });
|
|
120
|
+
|
|
121
|
+
const refusal = expectRefusal(`${atLimit}x`, 'flight-document.limit.document-code-units');
|
|
122
|
+
expect(refusal).toMatchObject({ limit: 4_194_304, actual: 4_194_305, offset: 4_194_304 });
|
|
123
|
+
|
|
124
|
+
const crlfRefusal = expectRefusal('\r\n'.repeat(2_097_153), 'flight-document.limit.document-code-units');
|
|
125
|
+
expect(crlfRefusal).toMatchObject({
|
|
126
|
+
limit: 4_194_304,
|
|
127
|
+
actual: 4_194_306,
|
|
128
|
+
offset: 4_194_304,
|
|
129
|
+
line: 2_097_153,
|
|
130
|
+
column: 1,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const astralRefusal = expectRefusal('😀'.repeat(2_097_153), 'flight-document.limit.document-code-units');
|
|
134
|
+
expect(astralRefusal).toMatchObject({ limit: 4_194_304, actual: 4_194_306, offset: 4_194_304 });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('accepts exactly 64 nested collections and refuses collection 65', () => {
|
|
138
|
+
expect(parseSceneDocumentYamlSubset(createNestedFlowMapping(64)).ok).toBe(true);
|
|
139
|
+
|
|
140
|
+
const refusal = expectRefusal(createNestedFlowMapping(65), 'flight-document.limit.nesting-depth');
|
|
141
|
+
expect(refusal).toMatchObject({ limit: 64, actual: 65 });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('accepts exactly 65,536 sequence items and refuses item 65,537 before append', () => {
|
|
145
|
+
const atLimit = Array.from({ length: 65_536 }, () => '- 0').join('\n');
|
|
146
|
+
const result = parseSceneDocumentYamlSubset(atLimit);
|
|
147
|
+
expect(result.ok).toBe(true);
|
|
148
|
+
if (result.ok) expect(result.value).toHaveLength(65_536);
|
|
149
|
+
|
|
150
|
+
const refusal = expectRefusal(`${atLimit}\n- 0`, 'flight-document.limit.collection-entries');
|
|
151
|
+
expect(refusal).toMatchObject({ limit: 65_536, actual: 65_537 });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('accepts exactly 65,536 mapping pairs and refuses pair 65,537 before insertion', () => {
|
|
155
|
+
const atLimit = createBlockMapping(65_536);
|
|
156
|
+
expect(parseSceneDocumentYamlSubset(atLimit).ok).toBe(true);
|
|
157
|
+
|
|
158
|
+
const refusal = expectRefusal(`${atLimit}\nk65536: 0`, 'flight-document.limit.collection-entries');
|
|
159
|
+
expect(refusal).toMatchObject({ limit: 65_536, actual: 65_537 });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('accepts exactly 65,536 decoded scalar code units and refuses code unit 65,537', () => {
|
|
163
|
+
const atLimit = 'x'.repeat(65_536);
|
|
164
|
+
expect(parseSceneDocumentYamlSubset(atLimit)).toEqual({ ok: true, value: atLimit });
|
|
165
|
+
|
|
166
|
+
const refusal = expectRefusal(`${atLimit}x`, 'flight-document.limit.scalar-code-units');
|
|
167
|
+
expect(refusal).toMatchObject({ limit: 65_536, actual: 65_537 });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('counts escaped and astral scalar content by decoded UTF-16 code units', () => {
|
|
171
|
+
const escapedAtLimit = `"${'\\u0061'.repeat(65_536)}"`;
|
|
172
|
+
const escapedResult = parseSceneDocumentYamlSubset(escapedAtLimit);
|
|
173
|
+
expect(escapedResult.ok).toBe(true);
|
|
174
|
+
if (escapedResult.ok) expect((escapedResult.value as string).length).toBe(65_536);
|
|
175
|
+
const escapedRefusal = expectRefusal(`"${'\\u0061'.repeat(65_537)}"`, 'flight-document.limit.scalar-code-units');
|
|
176
|
+
expect(escapedRefusal).toMatchObject({ limit: 65_536, actual: 65_537 });
|
|
177
|
+
|
|
178
|
+
const astralAtLimit = '😀'.repeat(32_768);
|
|
179
|
+
expect(astralAtLimit.length).toBe(65_536);
|
|
180
|
+
expect(parseSceneDocumentYamlSubset(astralAtLimit).ok).toBe(true);
|
|
181
|
+
const astralRefusal = expectRefusal(`${astralAtLimit}😀`, 'flight-document.limit.scalar-code-units');
|
|
182
|
+
expect(astralRefusal).toMatchObject({ limit: 65_536, actual: 65_537 });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('accepts exactly 256 decoded key code units and refuses key code unit 257 before lookup', () => {
|
|
186
|
+
const plainAtLimit = 'k'.repeat(256);
|
|
187
|
+
expect(parseSceneDocumentYamlSubset(`${plainAtLimit}: true`).ok).toBe(true);
|
|
188
|
+
const plainRefusal = expectRefusal(`${plainAtLimit}k: true`, 'flight-document.limit.key-code-units');
|
|
189
|
+
expect(plainRefusal).toMatchObject({ limit: 256, actual: 257 });
|
|
190
|
+
|
|
191
|
+
const quotedAtLimit = `${'k'.repeat(255)}"`;
|
|
192
|
+
expect(parseSceneDocumentYamlSubset(`"${'k'.repeat(255)}\\"": true`).ok).toBe(true);
|
|
193
|
+
expect(quotedAtLimit.length).toBe(256);
|
|
194
|
+
const quotedRefusal = expectRefusal(`"${'k'.repeat(256)}\\"": true`, 'flight-document.limit.key-code-units');
|
|
195
|
+
expect(quotedRefusal).toMatchObject({ limit: 256, actual: 257 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('accepts exactly 262,144 total nodes and refuses node 262,145 before allocation', () => {
|
|
199
|
+
expect(parseSceneDocumentYamlSubset(createNodeBoundDocument(65_531)).ok).toBe(true);
|
|
200
|
+
|
|
201
|
+
const refusal = expectRefusal(createNodeBoundDocument(65_532), 'flight-document.limit.total-nodes');
|
|
202
|
+
expect(refusal).toMatchObject({ limit: 262_144, actual: 262_145 });
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
function createBlockMapping(size: number): string {
|
|
207
|
+
return Array.from({ length: size }, (_, index) => `k${index}: 0`).join('\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function createNestedFlowMapping(depth: number): string {
|
|
211
|
+
return `${'{ value: '.repeat(depth)}null${' }'.repeat(depth)}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createNodeBoundDocument(lastCollectionSize: number): string {
|
|
215
|
+
return [65_536, 65_536, 65_536, lastCollectionSize]
|
|
216
|
+
.map((size) => `-\n${Array.from({ length: size }, () => ' - 0').join('\n')}`)
|
|
217
|
+
.join('\n');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function expectRefusal(source: string, kind: string) {
|
|
221
|
+
const result = parseSceneDocumentYamlSubset(source);
|
|
222
|
+
expect(result.ok).toBe(false);
|
|
223
|
+
if (result.ok) throw new Error(`Expected ${kind}, received a parsed value`);
|
|
224
|
+
expect(result.kind).toBe(kind);
|
|
225
|
+
expect(result.offset).toBeGreaterThanOrEqual(0);
|
|
226
|
+
expect(result.line).toBeGreaterThan(0);
|
|
227
|
+
expect(result.column).toBeGreaterThan(0);
|
|
228
|
+
return result;
|
|
229
|
+
}
|