@zenithbuild/core 1.2.2 → 1.2.3
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/README.md +20 -19
- package/cli/commands/add.ts +2 -2
- package/cli/commands/build.ts +2 -3
- package/cli/commands/dev.ts +93 -73
- package/cli/commands/index.ts +1 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/remove.ts +2 -2
- package/cli/index.ts +1 -1
- package/cli/main.ts +1 -1
- package/cli/utils/logger.ts +1 -1
- package/cli/utils/plugin-manager.ts +1 -1
- package/cli/utils/project.ts +4 -4
- package/core/components/ErrorPage.zen +218 -0
- package/core/components/index.ts +15 -0
- package/core/config.ts +1 -0
- package/core/index.ts +29 -0
- package/dist/compiler-native-frej59m4.node +0 -0
- package/dist/core/compiler-native-frej59m4.node +0 -0
- package/dist/core/index.js +6293 -0
- package/dist/runtime/lifecycle/index.js +1 -0
- package/dist/runtime/reactivity/index.js +1 -0
- package/dist/zen-build.js +1 -20118
- package/dist/zen-dev.js +1 -20118
- package/dist/zen-preview.js +1 -20118
- package/dist/zenith.js +1 -20118
- package/package.json +11 -20
- package/compiler/README.md +0 -380
- package/compiler/build-analyzer.ts +0 -122
- package/compiler/css/index.ts +0 -317
- package/compiler/discovery/componentDiscovery.ts +0 -242
- package/compiler/discovery/layouts.ts +0 -70
- package/compiler/errors/compilerError.ts +0 -56
- package/compiler/finalize/finalizeOutput.ts +0 -192
- package/compiler/finalize/generateFinalBundle.ts +0 -82
- package/compiler/index.ts +0 -83
- package/compiler/ir/types.ts +0 -174
- package/compiler/output/types.ts +0 -48
- package/compiler/parse/detectMapExpressions.ts +0 -102
- package/compiler/parse/importTypes.ts +0 -78
- package/compiler/parse/parseImports.ts +0 -309
- package/compiler/parse/parseScript.ts +0 -46
- package/compiler/parse/parseTemplate.ts +0 -628
- package/compiler/parse/parseZenFile.ts +0 -66
- package/compiler/parse/scriptAnalysis.ts +0 -91
- package/compiler/parse/trackLoopContext.ts +0 -82
- package/compiler/runtime/dataExposure.ts +0 -332
- package/compiler/runtime/generateDOM.ts +0 -255
- package/compiler/runtime/generateHydrationBundle.ts +0 -407
- package/compiler/runtime/hydration.ts +0 -309
- package/compiler/runtime/navigation.ts +0 -432
- package/compiler/runtime/thinRuntime.ts +0 -160
- package/compiler/runtime/transformIR.ts +0 -406
- package/compiler/runtime/wrapExpression.ts +0 -114
- package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
- package/compiler/spa-build.ts +0 -917
- package/compiler/ssg-build.ts +0 -486
- package/compiler/test/component-stacking.test.ts +0 -365
- package/compiler/test/map-lowering.test.ts +0 -130
- package/compiler/test/validate-test.ts +0 -104
- package/compiler/transform/classifyExpression.ts +0 -444
- package/compiler/transform/componentResolver.ts +0 -350
- package/compiler/transform/componentScriptTransformer.ts +0 -303
- package/compiler/transform/expressionTransformer.ts +0 -385
- package/compiler/transform/fragmentLowering.ts +0 -819
- package/compiler/transform/generateBindings.ts +0 -68
- package/compiler/transform/generateHTML.ts +0 -28
- package/compiler/transform/layoutProcessor.ts +0 -132
- package/compiler/transform/slotResolver.ts +0 -292
- package/compiler/transform/transformNode.ts +0 -314
- package/compiler/transform/transformTemplate.ts +0 -38
- package/compiler/validate/invariants.ts +0 -292
- package/compiler/validate/validateExpressions.ts +0 -168
- package/core/config/index.ts +0 -18
- package/core/config/loader.ts +0 -69
- package/core/config/types.ts +0 -119
- package/core/plugins/bridge.ts +0 -193
- package/core/plugins/index.ts +0 -7
- package/core/plugins/registry.ts +0 -126
- package/dist/cli.js +0 -11675
- package/runtime/build.ts +0 -17
- package/runtime/bundle-generator.ts +0 -1266
- package/runtime/client-runtime.ts +0 -891
- package/runtime/serve.ts +0 -93
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Stacking Test
|
|
3
|
-
*
|
|
4
|
-
* Tests that multiple sibling components and multiple instances
|
|
5
|
-
* of the same component render correctly.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { compileZenSource } from '../index'
|
|
9
|
-
import { discoverComponents, type ComponentMetadata } from '../discovery/componentDiscovery'
|
|
10
|
-
import { resolveComponentsInIR } from '../transform/componentResolver'
|
|
11
|
-
import { parseTemplate } from '../parse/parseTemplate'
|
|
12
|
-
import * as fs from 'fs'
|
|
13
|
-
import * as path from 'path'
|
|
14
|
-
|
|
15
|
-
// Test 1: Multiple sibling components
|
|
16
|
-
async function testMultipleSiblingComponents() {
|
|
17
|
-
console.log('\n=== Test 1: Multiple Sibling Components ===\n')
|
|
18
|
-
|
|
19
|
-
// Create mock components
|
|
20
|
-
const mockComponents = new Map<string, ComponentMetadata>()
|
|
21
|
-
|
|
22
|
-
mockComponents.set('Header', {
|
|
23
|
-
name: 'Header',
|
|
24
|
-
path: '/mock/Header.zen',
|
|
25
|
-
template: '<header class="test-header">Header Content</header>',
|
|
26
|
-
nodes: [{
|
|
27
|
-
type: 'element',
|
|
28
|
-
tag: 'header',
|
|
29
|
-
attributes: [{ name: 'class', value: 'test-header', location: { line: 1, column: 1 } }],
|
|
30
|
-
children: [{ type: 'text', value: 'Header Content', location: { line: 1, column: 1 } }],
|
|
31
|
-
location: { line: 1, column: 1 }
|
|
32
|
-
}],
|
|
33
|
-
slots: [],
|
|
34
|
-
props: [],
|
|
35
|
-
styles: [],
|
|
36
|
-
script: null,
|
|
37
|
-
scriptAttributes: null,
|
|
38
|
-
hasScript: false,
|
|
39
|
-
hasStyles: false
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
mockComponents.set('Hero', {
|
|
43
|
-
name: 'Hero',
|
|
44
|
-
path: '/mock/Hero.zen',
|
|
45
|
-
template: '<section class="test-hero">Hero Content</section>',
|
|
46
|
-
nodes: [{
|
|
47
|
-
type: 'element',
|
|
48
|
-
tag: 'section',
|
|
49
|
-
attributes: [{ name: 'class', value: 'test-hero', location: { line: 1, column: 1 } }],
|
|
50
|
-
children: [{ type: 'text', value: 'Hero Content', location: { line: 1, column: 1 } }],
|
|
51
|
-
location: { line: 1, column: 1 }
|
|
52
|
-
}],
|
|
53
|
-
slots: [],
|
|
54
|
-
props: [],
|
|
55
|
-
styles: [],
|
|
56
|
-
script: null,
|
|
57
|
-
scriptAttributes: null,
|
|
58
|
-
hasScript: false,
|
|
59
|
-
hasStyles: false
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
mockComponents.set('Footer', {
|
|
63
|
-
name: 'Footer',
|
|
64
|
-
path: '/mock/Footer.zen',
|
|
65
|
-
template: '<footer class="test-footer">Footer Content</footer>',
|
|
66
|
-
nodes: [{
|
|
67
|
-
type: 'element',
|
|
68
|
-
tag: 'footer',
|
|
69
|
-
attributes: [{ name: 'class', value: 'test-footer', location: { line: 1, column: 1 } }],
|
|
70
|
-
children: [{ type: 'text', value: 'Footer Content', location: { line: 1, column: 1 } }],
|
|
71
|
-
location: { line: 1, column: 1 }
|
|
72
|
-
}],
|
|
73
|
-
slots: [],
|
|
74
|
-
props: [],
|
|
75
|
-
styles: [],
|
|
76
|
-
script: null,
|
|
77
|
-
scriptAttributes: null,
|
|
78
|
-
hasScript: false,
|
|
79
|
-
hasStyles: false
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// Test source with multiple sibling components
|
|
83
|
-
const testSource = `
|
|
84
|
-
<script setup="ts">
|
|
85
|
-
</script>
|
|
86
|
-
<div class="page">
|
|
87
|
-
<Header />
|
|
88
|
-
<Hero />
|
|
89
|
-
<Footer />
|
|
90
|
-
</div>
|
|
91
|
-
`
|
|
92
|
-
|
|
93
|
-
// Parse the template
|
|
94
|
-
const template = parseTemplate(testSource, 'test.zen')
|
|
95
|
-
|
|
96
|
-
console.log('Parsed nodes:', JSON.stringify(template.nodes, null, 2))
|
|
97
|
-
console.log('\nComponent nodes found:')
|
|
98
|
-
|
|
99
|
-
// Find all component nodes
|
|
100
|
-
function findComponents(nodes: any[], depth = 0): void {
|
|
101
|
-
for (const node of nodes) {
|
|
102
|
-
if (node.type === 'component') {
|
|
103
|
-
console.log(`${' '.repeat(depth)}Component: ${node.name}`)
|
|
104
|
-
} else if (node.type === 'element') {
|
|
105
|
-
console.log(`${' '.repeat(depth)}Element: <${node.tag}>`)
|
|
106
|
-
if (node.children) {
|
|
107
|
-
findComponents(node.children, depth + 1)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
findComponents(template.nodes)
|
|
114
|
-
|
|
115
|
-
// Create IR
|
|
116
|
-
const ir = {
|
|
117
|
-
filePath: 'test.zen',
|
|
118
|
-
template,
|
|
119
|
-
script: { raw: '', attributes: {} },
|
|
120
|
-
styles: []
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Resolve components
|
|
124
|
-
const resolvedIR = resolveComponentsInIR(ir, mockComponents)
|
|
125
|
-
|
|
126
|
-
console.log('\nResolved nodes:', JSON.stringify(resolvedIR.template.nodes, null, 2))
|
|
127
|
-
|
|
128
|
-
// Count how many test-* classes appear in the resolved HTML
|
|
129
|
-
const jsonStr = JSON.stringify(resolvedIR.template.nodes)
|
|
130
|
-
const countHeaders = (jsonStr.match(/"value":"test-header"/g) || []).length
|
|
131
|
-
const countHeros = (jsonStr.match(/"value":"test-hero"/g) || []).length
|
|
132
|
-
const countFooters = (jsonStr.match(/"value":"test-footer"/g) || []).length
|
|
133
|
-
|
|
134
|
-
console.log(`\nResults:`)
|
|
135
|
-
console.log(` Headers: ${countHeaders} (expected: 1)`)
|
|
136
|
-
console.log(` Heros: ${countHeros} (expected: 1)`)
|
|
137
|
-
console.log(` Footers: ${countFooters} (expected: 1)`)
|
|
138
|
-
|
|
139
|
-
const passed = countHeaders === 1 && countHeros === 1 && countFooters === 1
|
|
140
|
-
console.log(`\n${passed ? '✅ PASSED' : '❌ FAILED'}: Multiple sibling components`)
|
|
141
|
-
|
|
142
|
-
return passed
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Test 2: Multiple instances of the same component
|
|
146
|
-
async function testMultipleInstances() {
|
|
147
|
-
console.log('\n=== Test 2: Multiple Instances of Same Component ===\n')
|
|
148
|
-
|
|
149
|
-
const mockComponents = new Map<string, ComponentMetadata>()
|
|
150
|
-
|
|
151
|
-
mockComponents.set('Card', {
|
|
152
|
-
name: 'Card',
|
|
153
|
-
path: '/mock/Card.zen',
|
|
154
|
-
template: '<div class="card">Card Content</div>',
|
|
155
|
-
nodes: [{
|
|
156
|
-
type: 'element',
|
|
157
|
-
tag: 'div',
|
|
158
|
-
attributes: [{ name: 'class', value: 'card', location: { line: 1, column: 1 } }],
|
|
159
|
-
children: [{ type: 'text', value: 'Card Content', location: { line: 1, column: 1 } }],
|
|
160
|
-
location: { line: 1, column: 1 }
|
|
161
|
-
}],
|
|
162
|
-
slots: [],
|
|
163
|
-
props: [],
|
|
164
|
-
styles: [],
|
|
165
|
-
script: null,
|
|
166
|
-
scriptAttributes: null,
|
|
167
|
-
hasScript: false,
|
|
168
|
-
hasStyles: false
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
const testSource = `
|
|
172
|
-
<script setup="ts">
|
|
173
|
-
</script>
|
|
174
|
-
<div class="grid">
|
|
175
|
-
<Card />
|
|
176
|
-
<Card />
|
|
177
|
-
<Card />
|
|
178
|
-
</div>
|
|
179
|
-
`
|
|
180
|
-
|
|
181
|
-
const template = parseTemplate(testSource, 'test.zen')
|
|
182
|
-
|
|
183
|
-
console.log('Component nodes found:')
|
|
184
|
-
|
|
185
|
-
function countComponents(nodes: any[]): number {
|
|
186
|
-
let count = 0
|
|
187
|
-
for (const node of nodes) {
|
|
188
|
-
if (node.type === 'component') {
|
|
189
|
-
console.log(` Found component: ${node.name}`)
|
|
190
|
-
count++
|
|
191
|
-
} else if (node.type === 'element' && node.children) {
|
|
192
|
-
count += countComponents(node.children)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return count
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const componentCount = countComponents(template.nodes)
|
|
199
|
-
console.log(`Total component nodes before resolution: ${componentCount}`)
|
|
200
|
-
|
|
201
|
-
const ir = {
|
|
202
|
-
filePath: 'test.zen',
|
|
203
|
-
template,
|
|
204
|
-
script: { raw: '', attributes: {} },
|
|
205
|
-
styles: []
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const resolvedIR = resolveComponentsInIR(ir, mockComponents)
|
|
209
|
-
|
|
210
|
-
// Count card divs in resolved output - search for the attribute value
|
|
211
|
-
const jsonStr = JSON.stringify(resolvedIR.template.nodes)
|
|
212
|
-
const cardCount = (jsonStr.match(/"value":"card"/g) || []).length
|
|
213
|
-
|
|
214
|
-
console.log(`\nResolved JSON (first 500 chars): ${jsonStr.substring(0, 500)}...`)
|
|
215
|
-
console.log(`\nCard divs in resolved output: ${cardCount} (expected: 3)`)
|
|
216
|
-
|
|
217
|
-
const passed = cardCount === 3
|
|
218
|
-
console.log(`\n${passed ? '✅ PASSED' : '❌ FAILED'}: Multiple instances of same component`)
|
|
219
|
-
|
|
220
|
-
return passed
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Test 3: Nested components
|
|
224
|
-
async function testNestedComponents() {
|
|
225
|
-
console.log('\n=== Test 3: Nested Components ===\n')
|
|
226
|
-
|
|
227
|
-
const mockComponents = new Map<string, ComponentMetadata>()
|
|
228
|
-
|
|
229
|
-
mockComponents.set('Outer', {
|
|
230
|
-
name: 'Outer',
|
|
231
|
-
path: '/mock/Outer.zen',
|
|
232
|
-
template: '<div class="outer"><slot /></div>',
|
|
233
|
-
nodes: [{
|
|
234
|
-
type: 'element',
|
|
235
|
-
tag: 'div',
|
|
236
|
-
attributes: [{ name: 'class', value: 'outer', location: { line: 1, column: 1 } }],
|
|
237
|
-
children: [{
|
|
238
|
-
type: 'element',
|
|
239
|
-
tag: 'slot',
|
|
240
|
-
attributes: [],
|
|
241
|
-
children: [],
|
|
242
|
-
location: { line: 1, column: 1 }
|
|
243
|
-
}],
|
|
244
|
-
location: { line: 1, column: 1 }
|
|
245
|
-
}],
|
|
246
|
-
slots: [{ name: null, location: { line: 1, column: 1 } }],
|
|
247
|
-
props: [],
|
|
248
|
-
styles: [],
|
|
249
|
-
script: null,
|
|
250
|
-
scriptAttributes: null,
|
|
251
|
-
hasScript: false,
|
|
252
|
-
hasStyles: false
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
mockComponents.set('Inner', {
|
|
256
|
-
name: 'Inner',
|
|
257
|
-
path: '/mock/Inner.zen',
|
|
258
|
-
template: '<span class="inner">Inner Content</span>',
|
|
259
|
-
nodes: [{
|
|
260
|
-
type: 'element',
|
|
261
|
-
tag: 'span',
|
|
262
|
-
attributes: [{ name: 'class', value: 'inner', location: { line: 1, column: 1 } }],
|
|
263
|
-
children: [{ type: 'text', value: 'Inner Content', location: { line: 1, column: 1 } }],
|
|
264
|
-
location: { line: 1, column: 1 }
|
|
265
|
-
}],
|
|
266
|
-
slots: [],
|
|
267
|
-
props: [],
|
|
268
|
-
styles: [],
|
|
269
|
-
script: null,
|
|
270
|
-
scriptAttributes: null,
|
|
271
|
-
hasScript: false,
|
|
272
|
-
hasStyles: false
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
const testSource = `
|
|
276
|
-
<script setup="ts">
|
|
277
|
-
</script>
|
|
278
|
-
<Outer>
|
|
279
|
-
<Inner />
|
|
280
|
-
</Outer>
|
|
281
|
-
`
|
|
282
|
-
|
|
283
|
-
const template = parseTemplate(testSource, 'test.zen')
|
|
284
|
-
|
|
285
|
-
const ir = {
|
|
286
|
-
filePath: 'test.zen',
|
|
287
|
-
template,
|
|
288
|
-
script: { raw: '', attributes: {} },
|
|
289
|
-
styles: []
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const resolvedIR = resolveComponentsInIR(ir, mockComponents)
|
|
293
|
-
|
|
294
|
-
const jsonStr = JSON.stringify(resolvedIR.template.nodes)
|
|
295
|
-
const hasOuter = jsonStr.includes('"value":"outer"')
|
|
296
|
-
const hasInner = jsonStr.includes('"value":"inner"')
|
|
297
|
-
|
|
298
|
-
console.log(`Resolved JSON (first 500 chars): ${jsonStr.substring(0, 500)}...`)
|
|
299
|
-
console.log(`Outer div present: ${hasOuter}`)
|
|
300
|
-
console.log(`Inner span present: ${hasInner}`)
|
|
301
|
-
|
|
302
|
-
const passed = hasOuter && hasInner
|
|
303
|
-
console.log(`\n${passed ? '✅ PASSED' : '❌ FAILED'}: Nested components`)
|
|
304
|
-
|
|
305
|
-
return passed
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Test 4: Auto-import naming (filename-based)
|
|
309
|
-
async function testAutoImportNaming() {
|
|
310
|
-
console.log('\n=== Test 4: Auto-Import Naming ===\n')
|
|
311
|
-
|
|
312
|
-
// Test the naming algorithm directly
|
|
313
|
-
// With the new convention, component name = filename (subdirectories are ignored)
|
|
314
|
-
const testCases = [
|
|
315
|
-
{ input: 'components/Header.zen', expected: 'Header' },
|
|
316
|
-
{ input: 'components/globals/Header.zen', expected: 'Header' }, // Filename only!
|
|
317
|
-
{ input: 'components/ui/buttons/Primary.zen', expected: 'Primary' },
|
|
318
|
-
{ input: 'components/sections/HeroSection.zen', expected: 'HeroSection' },
|
|
319
|
-
{ input: 'components/ui-kit/Button.zen', expected: 'Button' },
|
|
320
|
-
]
|
|
321
|
-
|
|
322
|
-
let passed = true
|
|
323
|
-
|
|
324
|
-
for (const tc of testCases) {
|
|
325
|
-
// Component name is just the filename without .zen extension
|
|
326
|
-
const result = path.basename(tc.input, '.zen')
|
|
327
|
-
|
|
328
|
-
const match = result === tc.expected
|
|
329
|
-
console.log(` ${match ? '✓' : '✗'} "${tc.input}" → "${result}" (expected: "${tc.expected}")`)
|
|
330
|
-
|
|
331
|
-
if (!match) passed = false
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
console.log(`\n${passed ? '✅ PASSED' : '❌ FAILED'}: Auto-import naming`)
|
|
335
|
-
|
|
336
|
-
return passed
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Run all tests
|
|
340
|
-
async function runTests() {
|
|
341
|
-
console.log('╔════════════════════════════════════════════╗')
|
|
342
|
-
console.log('║ Component Stacking Tests ║')
|
|
343
|
-
console.log('╚════════════════════════════════════════════╝')
|
|
344
|
-
|
|
345
|
-
const results = []
|
|
346
|
-
|
|
347
|
-
results.push(await testMultipleSiblingComponents())
|
|
348
|
-
results.push(await testMultipleInstances())
|
|
349
|
-
results.push(await testNestedComponents())
|
|
350
|
-
results.push(await testAutoImportNaming())
|
|
351
|
-
|
|
352
|
-
console.log('\n════════════════════════════════════════════')
|
|
353
|
-
console.log('Summary:')
|
|
354
|
-
console.log(` Total: ${results.length}`)
|
|
355
|
-
console.log(` Passed: ${results.filter(r => r).length}`)
|
|
356
|
-
console.log(` Failed: ${results.filter(r => !r).length}`)
|
|
357
|
-
console.log('════════════════════════════════════════════\n')
|
|
358
|
-
|
|
359
|
-
process.exit(results.every(r => r) ? 0 : 1)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
runTests().catch(err => {
|
|
363
|
-
console.error('Test error:', err)
|
|
364
|
-
process.exit(1)
|
|
365
|
-
})
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { parseTemplate } from '../parse/parseTemplate'
|
|
2
|
-
import { transformTemplate } from '../transform/transformTemplate'
|
|
3
|
-
import { lowerFragments } from '../transform/fragmentLowering'
|
|
4
|
-
import { transformNode } from '../transform/transformNode'
|
|
5
|
-
import type { ZenIR, TemplateNode, ExpressionNode, LoopFragmentNode } from '../ir/types'
|
|
6
|
-
|
|
7
|
-
async function testMapLowering() {
|
|
8
|
-
console.log('--- Testing JSX .map() Lowering ---')
|
|
9
|
-
|
|
10
|
-
const source = `
|
|
11
|
-
<div>
|
|
12
|
-
{items.map((item, index) => (
|
|
13
|
-
<li key={index} class={item.active ? 'active' : ''}>
|
|
14
|
-
{item.text}
|
|
15
|
-
</li>
|
|
16
|
-
))}
|
|
17
|
-
</div>
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const template = parseTemplate(source, 'test.zen')
|
|
22
|
-
|
|
23
|
-
// Recursive search for LoopFragmentNode
|
|
24
|
-
function findNode(nodes: TemplateNode[], type: string): any {
|
|
25
|
-
for (const node of nodes) {
|
|
26
|
-
if (node.type === type) return node
|
|
27
|
-
if ('children' in node) {
|
|
28
|
-
const found = findNode(node.children, type)
|
|
29
|
-
if (found) return found
|
|
30
|
-
}
|
|
31
|
-
if (node.type === 'loop-fragment') {
|
|
32
|
-
const found = findNode(node.body, type)
|
|
33
|
-
if (found) return found
|
|
34
|
-
}
|
|
35
|
-
if (node.type === 'conditional-fragment') {
|
|
36
|
-
const foundC = findNode(node.consequent, type)
|
|
37
|
-
if (foundC) return foundC
|
|
38
|
-
const foundA = findNode(node.alternate, type)
|
|
39
|
-
if (foundA) return foundA
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const loopNode = findNode(template.nodes, 'loop-fragment') as LoopFragmentNode
|
|
46
|
-
|
|
47
|
-
if (!loopNode) {
|
|
48
|
-
console.log('Template structure:', JSON.stringify(template.nodes, null, 2))
|
|
49
|
-
throw new Error('LoopFragmentNode not found in template')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
console.assert(loopNode.source.startsWith('expr_'), 'Loop source should be an expression ID')
|
|
53
|
-
const sourceExpr = template.expressions.find(e => e.id === loopNode.source)
|
|
54
|
-
console.assert(sourceExpr?.code === 'items', `Expected source code "items", got "${sourceExpr?.code}"`)
|
|
55
|
-
|
|
56
|
-
// Check if inner expressions are registered
|
|
57
|
-
const li = loopNode.body.find(n => n.type === 'element' && n.tag === 'li') as any
|
|
58
|
-
const classAttr = li?.attributes.find((a: any) => a.name === 'class')
|
|
59
|
-
console.assert(classAttr?.value.id.startsWith('expr_'), 'Inner attribute expression should be an ID')
|
|
60
|
-
|
|
61
|
-
const textExpr = li?.children.find((n: any) => n.type === 'expression') as ExpressionNode
|
|
62
|
-
console.assert(textExpr?.expression.startsWith('expr_'), 'Inner text expression should be an ID')
|
|
63
|
-
|
|
64
|
-
// Final transformation
|
|
65
|
-
const ir: ZenIR = {
|
|
66
|
-
filePath: 'test.zen',
|
|
67
|
-
template,
|
|
68
|
-
script: null,
|
|
69
|
-
styles: []
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const compiled = transformTemplate(ir)
|
|
73
|
-
console.assert(compiled.html.includes('data-zen-loop="loop_0"'), 'HTML should contain loop marker')
|
|
74
|
-
console.assert(compiled.html.includes(`data-zen-source="${loopNode.source}"`), 'HTML should contain source expression ID')
|
|
75
|
-
|
|
76
|
-
console.log('✅ JSX .map() lowering test passed')
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.error('❌ JSX .map() lowering test failed:', e)
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function testInvariantEnforcement() {
|
|
84
|
-
console.log('--- Testing INV-EXPR-REG-001 Invariant Enforcement ---')
|
|
85
|
-
|
|
86
|
-
const expressions: any[] = []
|
|
87
|
-
const badNode: ExpressionNode = {
|
|
88
|
-
type: 'expression',
|
|
89
|
-
expression: 'raw.code.here', // Should be expr_N
|
|
90
|
-
location: { line: 1, column: 1 }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
// We need to call lowerFragments but it calls assertNoRawExpressions
|
|
95
|
-
lowerFragments([badNode], 'test.zen', expressions)
|
|
96
|
-
console.error('❌ Invariant enforcement failed to catch raw expression')
|
|
97
|
-
process.exit(1)
|
|
98
|
-
} catch (e: any) {
|
|
99
|
-
console.assert(e.name === 'InvariantError', 'Expected InvariantError')
|
|
100
|
-
console.assert(e.message.includes('INV-EXPR-REG-001'), 'Expected INV-EXPR-REG-001 in error message')
|
|
101
|
-
console.log('✅ Invariant enforcement caught raw expression')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Test transformNode safety
|
|
105
|
-
try {
|
|
106
|
-
const expressionsList: any[] = []
|
|
107
|
-
const badLoopNode: any = {
|
|
108
|
-
type: 'loop-fragment',
|
|
109
|
-
source: 'raw_source',
|
|
110
|
-
itemVar: 'item',
|
|
111
|
-
body: [],
|
|
112
|
-
location: { line: 1, column: 1 }
|
|
113
|
-
}
|
|
114
|
-
transformNode(badLoopNode, expressionsList)
|
|
115
|
-
console.error('❌ transformNode failed to catch raw loop source')
|
|
116
|
-
process.exit(1)
|
|
117
|
-
} catch (e: any) {
|
|
118
|
-
console.assert(e.name === 'InvariantError', 'Expected InvariantError in transformNode')
|
|
119
|
-
console.assert(e.message.includes('Raw loop source found'), 'Expected Loop source error')
|
|
120
|
-
console.log('✅ transformNode caught raw loop source')
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function runTests() {
|
|
125
|
-
await testMapLowering()
|
|
126
|
-
await testInvariantEnforcement()
|
|
127
|
-
console.log('--- All tests completed successfully ---')
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
runTests()
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Cases for Expression Validation
|
|
3
|
-
*
|
|
4
|
-
* Phase 8/9/10: Tests that invalid expressions fail the build
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { validateExpressions, validateExpressionsOrThrow } from '../validate/validateExpressions'
|
|
8
|
-
import type { ExpressionIR } from '../ir/types'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Test valid expressions
|
|
12
|
-
*/
|
|
13
|
-
function testValidExpressions() {
|
|
14
|
-
const validExpressions: ExpressionIR[] = [
|
|
15
|
-
{
|
|
16
|
-
id: 'expr_0',
|
|
17
|
-
code: 'user.name',
|
|
18
|
-
location: { line: 10, column: 5 }
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: 'expr_1',
|
|
22
|
-
code: 'count + 1',
|
|
23
|
-
location: { line: 11, column: 8 }
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'expr_2',
|
|
27
|
-
code: 'isActive ? "on" : "off"',
|
|
28
|
-
location: { line: 12, column: 12 }
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
const result = validateExpressions(validExpressions, 'test.zen')
|
|
33
|
-
console.assert(result.valid === true, 'Valid expressions should pass validation')
|
|
34
|
-
console.assert(result.errors.length === 0, 'Valid expressions should have no errors')
|
|
35
|
-
console.log('✅ Valid expressions test passed')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Test invalid expressions
|
|
40
|
-
*/
|
|
41
|
-
function testInvalidExpressions() {
|
|
42
|
-
const invalidExpressions: ExpressionIR[] = [
|
|
43
|
-
{
|
|
44
|
-
id: 'expr_0',
|
|
45
|
-
code: 'user.name}', // Mismatched brace
|
|
46
|
-
location: { line: 10, column: 5 }
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
const result = validateExpressions(invalidExpressions, 'test.zen')
|
|
51
|
-
console.assert(result.valid === false, 'Invalid expressions should fail validation')
|
|
52
|
-
console.assert(result.errors.length > 0, 'Invalid expressions should have errors')
|
|
53
|
-
console.log('✅ Invalid expressions test passed')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Test unsafe code detection
|
|
58
|
-
*/
|
|
59
|
-
function testUnsafeCode() {
|
|
60
|
-
const unsafeExpressions: ExpressionIR[] = [
|
|
61
|
-
{
|
|
62
|
-
id: 'expr_0',
|
|
63
|
-
code: 'eval("alert(1)")',
|
|
64
|
-
location: { line: 10, column: 5 }
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
const result = validateExpressions(unsafeExpressions, 'test.zen')
|
|
69
|
-
console.assert(result.valid === false, 'Unsafe code should fail validation')
|
|
70
|
-
console.assert(result.errors.length > 0, 'Unsafe code should have errors')
|
|
71
|
-
console.log('✅ Unsafe code detection test passed')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Test validateExpressionsOrThrow
|
|
76
|
-
*/
|
|
77
|
-
function testThrowOnInvalid() {
|
|
78
|
-
const invalidExpressions: ExpressionIR[] = [
|
|
79
|
-
{
|
|
80
|
-
id: 'expr_0',
|
|
81
|
-
code: 'user.name}', // Mismatched brace
|
|
82
|
-
location: { line: 10, column: 5 }
|
|
83
|
-
}
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
validateExpressionsOrThrow(invalidExpressions, 'test.zen')
|
|
88
|
-
console.assert(false, 'Should have thrown on invalid expressions')
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.assert(error instanceof Error, 'Should throw Error')
|
|
91
|
-
console.log('✅ Throw on invalid expressions test passed')
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Run tests
|
|
96
|
-
if (require.main === module) {
|
|
97
|
-
console.log('Running validation tests...')
|
|
98
|
-
testValidExpressions()
|
|
99
|
-
testInvalidExpressions()
|
|
100
|
-
testUnsafeCode()
|
|
101
|
-
testThrowOnInvalid()
|
|
102
|
-
console.log('✅ All validation tests passed!')
|
|
103
|
-
}
|
|
104
|
-
|