ripple 0.2.124 → 0.2.125
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/package.json +1 -1
- package/src/compiler/index.js +1 -1
- package/src/compiler/phases/1-parse/index.js +1 -0
- package/src/compiler/phases/2-analyze/index.js +58 -32
- package/src/compiler/phases/3-transform/server/index.js +50 -37
- package/src/runtime/internal/server/index.js +20 -9
- package/src/utils/builders.js +2 -2
- package/tests/client/__snapshots__/for.test.ripple.snap +0 -80
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +0 -48
- package/tests/client/basic/basic.errors.test.ripple +16 -0
- package/tests/server/await.test.ripple +61 -0
- package/tests/server/for.test.ripple +44 -0
- package/tests/server/if.test.ripple +21 -1
- package/tests/utils/escaping.test.js +102 -0
- package/tests/utils/events.test.js +147 -0
- package/tests/utils/normalize_css_property_name.test.js +43 -0
- package/tests/utils/patterns.test.js +382 -0
- package/tests/utils/sanitize_template_string.test.js +51 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.125",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
package/src/compiler/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export function parse(source) {
|
|
|
24
24
|
*/
|
|
25
25
|
export function compile(source, filename, options = {}) {
|
|
26
26
|
const ast = parse_module(source);
|
|
27
|
-
const analysis = analyze(ast, filename);
|
|
27
|
+
const analysis = analyze(ast, filename, options);
|
|
28
28
|
const result = options.mode === 'server'
|
|
29
29
|
? transform_server(filename, source, analysis)
|
|
30
30
|
: transform_client(filename, source, analysis, false);
|
|
@@ -1392,6 +1392,7 @@ function RipplePlugin(config) {
|
|
|
1392
1392
|
this.next();
|
|
1393
1393
|
this.enterScope(0);
|
|
1394
1394
|
node.id = this.parseIdent();
|
|
1395
|
+
this.declareName(node.id.name, 'var', node.id.start);
|
|
1395
1396
|
this.parseFunctionParams(node);
|
|
1396
1397
|
this.eat(tt.braceL);
|
|
1397
1398
|
node.body = [];
|
|
@@ -111,7 +111,8 @@ const visitors = {
|
|
|
111
111
|
if (
|
|
112
112
|
is_reference(node, /** @type {Node} */ (parent)) &&
|
|
113
113
|
binding &&
|
|
114
|
-
context.state.inside_server_block
|
|
114
|
+
context.state.inside_server_block &&
|
|
115
|
+
context.state.scope.server_block
|
|
115
116
|
) {
|
|
116
117
|
let current_scope = binding.scope;
|
|
117
118
|
|
|
@@ -349,13 +350,14 @@ const visitors = {
|
|
|
349
350
|
node.metadata = {
|
|
350
351
|
...node.metadata,
|
|
351
352
|
has_template: false,
|
|
353
|
+
has_await: false,
|
|
352
354
|
};
|
|
353
355
|
|
|
354
356
|
context.visit(switch_case, context.state);
|
|
355
357
|
|
|
356
|
-
if (!node.metadata.has_template) {
|
|
358
|
+
if (!node.metadata.has_template && !node.metadata.has_await) {
|
|
357
359
|
error(
|
|
358
|
-
'Component switch statements must contain a template in each of their cases. Move the switch statement into an effect if it does not render anything.',
|
|
360
|
+
'Component switch statements must contain a template or an await expression in each of their cases. Move the switch statement into an effect if it does not render anything.',
|
|
359
361
|
context.state.analysis.module.filename,
|
|
360
362
|
node,
|
|
361
363
|
);
|
|
@@ -416,15 +418,16 @@ const visitors = {
|
|
|
416
418
|
node.metadata = {
|
|
417
419
|
...node.metadata,
|
|
418
420
|
has_template: false,
|
|
421
|
+
has_await: false,
|
|
419
422
|
};
|
|
420
423
|
context.next();
|
|
421
424
|
|
|
422
|
-
if (!node.metadata.has_template) {
|
|
425
|
+
if (!node.metadata.has_template && !node.metadata.has_await) {
|
|
423
426
|
error(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
'Component for...of loops must contain a template or an await expression in their body. Move the for loop into an effect if it does not render anything.',
|
|
428
|
+
context.state.analysis.module.filename,
|
|
429
|
+
node,
|
|
430
|
+
);
|
|
428
431
|
}
|
|
429
432
|
},
|
|
430
433
|
|
|
@@ -437,9 +440,14 @@ const visitors = {
|
|
|
437
440
|
|
|
438
441
|
if (declaration && declaration.type === 'FunctionDeclaration') {
|
|
439
442
|
server_block.metadata.exports.push(declaration.id.name);
|
|
443
|
+
} else if (declaration && declaration.type === 'Component') {
|
|
444
|
+
// Handle exported components in server blocks
|
|
445
|
+
if (server_block) {
|
|
446
|
+
server_block.metadata.exports.push(declaration.id.name);
|
|
447
|
+
}
|
|
440
448
|
} else {
|
|
441
449
|
// TODO
|
|
442
|
-
throw new Error('Not implemented');
|
|
450
|
+
throw new Error('Not implemented: Exported declaration type not supported in server blocks.');
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
return context.next();
|
|
@@ -462,35 +470,39 @@ const visitors = {
|
|
|
462
470
|
node.metadata = {
|
|
463
471
|
...node.metadata,
|
|
464
472
|
has_template: false,
|
|
473
|
+
has_await: false,
|
|
465
474
|
};
|
|
466
475
|
|
|
467
476
|
context.visit(node.consequent, context.state);
|
|
468
477
|
|
|
469
|
-
if (!node.metadata.has_template) {
|
|
478
|
+
if (!node.metadata.has_template && !node.metadata.has_await) {
|
|
470
479
|
error(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
480
|
+
'Component if statements must contain a template or an await expression in their "then" body. Move the if statement into an effect if it does not render anything.',
|
|
481
|
+
context.state.analysis.module.filename,
|
|
482
|
+
node,
|
|
483
|
+
);
|
|
475
484
|
}
|
|
476
485
|
|
|
477
486
|
if (node.alternate) {
|
|
478
|
-
node.metadata =
|
|
479
|
-
|
|
480
|
-
has_template: false,
|
|
481
|
-
};
|
|
487
|
+
node.metadata.has_template = false;
|
|
488
|
+
node.metadata.has_await = false;
|
|
482
489
|
context.visit(node.alternate, context.state);
|
|
483
490
|
|
|
484
|
-
if (!node.metadata.has_template) {
|
|
491
|
+
if (!node.metadata.has_template && !node.metadata.has_await) {
|
|
485
492
|
error(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
493
|
+
'Component if statements must contain a template or an await expression in their "else" body. Move the if statement into an effect if it does not render anything.',
|
|
494
|
+
context.state.analysis.module.filename,
|
|
495
|
+
node,
|
|
496
|
+
);
|
|
490
497
|
}
|
|
491
498
|
}
|
|
492
499
|
},
|
|
493
|
-
|
|
500
|
+
/**
|
|
501
|
+
*
|
|
502
|
+
* @param {any} node
|
|
503
|
+
* @param {any} context
|
|
504
|
+
* @returns
|
|
505
|
+
*/
|
|
494
506
|
TryStatement(node, context) {
|
|
495
507
|
if (!is_inside_component(context)) {
|
|
496
508
|
return context.next();
|
|
@@ -744,7 +756,12 @@ const visitors = {
|
|
|
744
756
|
mark_control_flow_has_template(context.path);
|
|
745
757
|
context.next();
|
|
746
758
|
},
|
|
747
|
-
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
*
|
|
762
|
+
* @param {any} node
|
|
763
|
+
* @param {any} context
|
|
764
|
+
*/
|
|
748
765
|
AwaitExpression(node, context) {
|
|
749
766
|
if (is_inside_component(context)) {
|
|
750
767
|
if (context.state.metadata?.await === false) {
|
|
@@ -754,18 +771,27 @@ const visitors = {
|
|
|
754
771
|
const parent_block = get_parent_block_node(context);
|
|
755
772
|
|
|
756
773
|
if (parent_block !== null && parent_block.type !== 'Component') {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
774
|
+
if (context.state.inside_server_block === false) {
|
|
775
|
+
error(
|
|
776
|
+
'`await` is not allowed in client-side control-flow statements',
|
|
777
|
+
context.state.analysis.module.filename,
|
|
778
|
+
node
|
|
779
|
+
);
|
|
780
|
+
}
|
|
762
781
|
}
|
|
763
782
|
|
|
783
|
+
if (parent_block) {
|
|
784
|
+
if (!parent_block.metadata) {
|
|
785
|
+
parent_block.metadata = {};
|
|
786
|
+
}
|
|
787
|
+
parent_block.metadata.has_await = true;
|
|
788
|
+
}
|
|
789
|
+
|
|
764
790
|
context.next();
|
|
765
791
|
},
|
|
766
792
|
};
|
|
767
793
|
|
|
768
|
-
export function analyze(ast, filename) {
|
|
794
|
+
export function analyze(ast, filename, options = {}) {
|
|
769
795
|
const scope_root = new ScopeRoot();
|
|
770
796
|
|
|
771
797
|
const { scope, scopes } = create_scopes(ast, scope_root, null);
|
|
@@ -785,7 +811,7 @@ export function analyze(ast, filename) {
|
|
|
785
811
|
scopes,
|
|
786
812
|
analysis,
|
|
787
813
|
inside_head: false,
|
|
788
|
-
inside_server_block:
|
|
814
|
+
inside_server_block: options.mode === 'server',
|
|
789
815
|
},
|
|
790
816
|
visitors,
|
|
791
817
|
);
|
|
@@ -120,7 +120,18 @@ const visitors = {
|
|
|
120
120
|
component_fn = b.async(component_fn);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
const declaration = b.function_declaration(node.id, component_fn.params, component_fn.body, component_fn.async);
|
|
124
|
+
|
|
125
|
+
if (metadata.await) {
|
|
126
|
+
const parent = context.path.at(-1);
|
|
127
|
+
if (parent.type === 'Program' || parent.type === 'BlockStatement') {
|
|
128
|
+
const body = parent.body;
|
|
129
|
+
const index = body.indexOf(node);
|
|
130
|
+
body.splice(index + 1, 0, b.stmt(b.assignment('=', b.member(node.id, b.id('async')), b.true)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return declaration;
|
|
124
135
|
},
|
|
125
136
|
|
|
126
137
|
CallExpression(node, context) {
|
|
@@ -270,11 +281,10 @@ const visitors = {
|
|
|
270
281
|
let class_attribute = null;
|
|
271
282
|
|
|
272
283
|
const handle_static_attr = (name, value) => {
|
|
273
|
-
const attr_str = ` ${name}${
|
|
274
|
-
is_boolean_attribute(name) && value === true
|
|
284
|
+
const attr_str = ` ${name}${is_boolean_attribute(name) && value === true
|
|
275
285
|
? ''
|
|
276
286
|
: `="${value === true ? '' : escape_html(value, true)}"`
|
|
277
|
-
|
|
287
|
+
}`;
|
|
278
288
|
|
|
279
289
|
if (is_spreading) {
|
|
280
290
|
// For spread attributes, store just the actual value, not the full attribute string
|
|
@@ -549,15 +559,22 @@ const visitors = {
|
|
|
549
559
|
context.state.init.push(b.if(context.visit(node.test), consequent, alternate));
|
|
550
560
|
},
|
|
551
561
|
|
|
552
|
-
|
|
553
|
-
const
|
|
562
|
+
AssignmentExpression(node, context) {
|
|
563
|
+
const left = node.left;
|
|
554
564
|
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
565
|
+
if (left.type === 'Identifier' && left.tracked) {
|
|
566
|
+
return b.call(
|
|
567
|
+
'set',
|
|
568
|
+
context.visit(left, { ...context.state, metadata: { tracking: false } }),
|
|
569
|
+
context.visit(node.right)
|
|
570
|
+
);
|
|
558
571
|
}
|
|
572
|
+
|
|
573
|
+
return context.next();
|
|
559
574
|
},
|
|
560
575
|
|
|
576
|
+
|
|
577
|
+
|
|
561
578
|
ServerIdentifier(node, context) {
|
|
562
579
|
return b.id('_$_server_$_');
|
|
563
580
|
},
|
|
@@ -605,22 +622,22 @@ const visitors = {
|
|
|
605
622
|
const try_statements =
|
|
606
623
|
node.handler !== null
|
|
607
624
|
? [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
),
|
|
625
|
+
b.try(
|
|
626
|
+
b.block(body),
|
|
627
|
+
b.catch_clause(
|
|
628
|
+
node.handler.param || b.id('error'),
|
|
629
|
+
b.block(
|
|
630
|
+
transform_body(node.handler.body.body, {
|
|
631
|
+
...context,
|
|
632
|
+
state: {
|
|
633
|
+
...context.state,
|
|
634
|
+
scope: context.state.scopes.get(node.handler.body),
|
|
635
|
+
},
|
|
636
|
+
}),
|
|
621
637
|
),
|
|
622
638
|
),
|
|
623
|
-
|
|
639
|
+
),
|
|
640
|
+
]
|
|
624
641
|
: body;
|
|
625
642
|
|
|
626
643
|
context.state.init.push(
|
|
@@ -647,6 +664,8 @@ const visitors = {
|
|
|
647
664
|
},
|
|
648
665
|
|
|
649
666
|
AwaitExpression(node, context) {
|
|
667
|
+
context.state.scope.server_block = true
|
|
668
|
+
context.inside_server_block = true
|
|
650
669
|
if (context.state.to_ts) {
|
|
651
670
|
return context.next();
|
|
652
671
|
}
|
|
@@ -672,10 +691,8 @@ const visitors = {
|
|
|
672
691
|
const parent = context.path.at(-1);
|
|
673
692
|
|
|
674
693
|
if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
|
|
675
|
-
add_ripple_internal_import(context);
|
|
676
|
-
|
|
677
694
|
return b.call(
|
|
678
|
-
'
|
|
695
|
+
'get',
|
|
679
696
|
b.member(
|
|
680
697
|
context.visit(node.object),
|
|
681
698
|
node.computed ? context.visit(node.property) : node.property,
|
|
@@ -690,13 +707,15 @@ const visitors = {
|
|
|
690
707
|
|
|
691
708
|
Text(node, { visit, state }) {
|
|
692
709
|
const metadata = { await: false };
|
|
693
|
-
|
|
710
|
+
let expression = visit(node.expression, { ...state, metadata });
|
|
711
|
+
|
|
712
|
+
if (expression.type === 'Identifier' && expression.tracked) {
|
|
713
|
+
expression = b.call('get', expression);
|
|
714
|
+
}
|
|
694
715
|
|
|
695
716
|
if (expression.type === 'Literal') {
|
|
696
717
|
state.init.push(
|
|
697
|
-
b.stmt(
|
|
698
|
-
b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value))),
|
|
699
|
-
),
|
|
718
|
+
b.stmt(b.call(b.member(b.id('__output'), b.id('push')), b.literal(escape(expression.value)))),
|
|
700
719
|
);
|
|
701
720
|
} else {
|
|
702
721
|
state.init.push(
|
|
@@ -791,13 +810,7 @@ export function transform_server(filename, source, analysis) {
|
|
|
791
810
|
}
|
|
792
811
|
|
|
793
812
|
// Add async property to component functions
|
|
794
|
-
|
|
795
|
-
if (metadata.async) {
|
|
796
|
-
program.body.push(
|
|
797
|
-
b.stmt(b.assignment('=', b.member(b.id(metadata.id), b.id('async')), b.true)),
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
813
|
+
|
|
801
814
|
|
|
802
815
|
for (const import_node of state.imports) {
|
|
803
816
|
program.body.unshift(b.stmt(b.id(import_node)));
|
|
@@ -30,6 +30,8 @@ class Output {
|
|
|
30
30
|
body = '';
|
|
31
31
|
/** @type {Set<string>} */
|
|
32
32
|
css = new Set();
|
|
33
|
+
/** @type {Promise<any>[]} */
|
|
34
|
+
promises = [];
|
|
33
35
|
/** @type {Output | null} */
|
|
34
36
|
#parent = null;
|
|
35
37
|
|
|
@@ -60,18 +62,27 @@ class Output {
|
|
|
60
62
|
/** @type {render} */
|
|
61
63
|
export async function render(component) {
|
|
62
64
|
const output = new Output(null);
|
|
65
|
+
let head, body, css;
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
try {
|
|
68
|
+
if (component.async) {
|
|
69
|
+
await component(output, {});
|
|
70
|
+
} else {
|
|
71
|
+
component(output, {});
|
|
72
|
+
}
|
|
73
|
+
if (output.promises.length > 0) {
|
|
74
|
+
await Promise.all(output.promises);
|
|
75
|
+
}
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
head = output.head
|
|
78
|
+
body = output.body
|
|
79
|
+
css = output.css
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.log(error)
|
|
83
|
+
}
|
|
84
|
+
return { head, body, css }
|
|
73
85
|
}
|
|
74
|
-
|
|
75
86
|
/**
|
|
76
87
|
* @returns {void}
|
|
77
88
|
*/
|
package/src/utils/builders.js
CHANGED
|
@@ -237,14 +237,14 @@ export function export_builder(declaration, specifiers = [], attributes = [], so
|
|
|
237
237
|
* @param {ESTree.BlockStatement} body
|
|
238
238
|
* @returns {ESTree.FunctionDeclaration}
|
|
239
239
|
*/
|
|
240
|
-
export function function_declaration(id, params, body) {
|
|
240
|
+
export function function_declaration(id, params, body, async = false) {
|
|
241
241
|
return {
|
|
242
242
|
type: 'FunctionDeclaration',
|
|
243
243
|
id,
|
|
244
244
|
params,
|
|
245
245
|
body,
|
|
246
246
|
generator: false,
|
|
247
|
-
async
|
|
247
|
+
async,
|
|
248
248
|
metadata: /** @type {any} */ (null), // should not be used by codegen
|
|
249
249
|
};
|
|
250
250
|
}
|
|
@@ -238,86 +238,6 @@ exports[`for statements > handles updating with new objects with same key 2`] =
|
|
|
238
238
|
</div>
|
|
239
239
|
`;
|
|
240
240
|
|
|
241
|
-
exports[`for statements > render a simple dynamic array 1`] = `
|
|
242
|
-
<div>
|
|
243
|
-
<!---->
|
|
244
|
-
<div
|
|
245
|
-
class="Item 1"
|
|
246
|
-
>
|
|
247
|
-
Item 1
|
|
248
|
-
</div>
|
|
249
|
-
<div
|
|
250
|
-
class="Item 2"
|
|
251
|
-
>
|
|
252
|
-
Item 2
|
|
253
|
-
</div>
|
|
254
|
-
<div
|
|
255
|
-
class="Item 3"
|
|
256
|
-
>
|
|
257
|
-
Item 3
|
|
258
|
-
</div>
|
|
259
|
-
<!---->
|
|
260
|
-
<button>
|
|
261
|
-
Add Item
|
|
262
|
-
</button>
|
|
263
|
-
|
|
264
|
-
</div>
|
|
265
|
-
`;
|
|
266
|
-
|
|
267
|
-
exports[`for statements > render a simple dynamic array 2`] = `
|
|
268
|
-
<div>
|
|
269
|
-
<!---->
|
|
270
|
-
<div
|
|
271
|
-
class="Item 1"
|
|
272
|
-
>
|
|
273
|
-
Item 1
|
|
274
|
-
</div>
|
|
275
|
-
<div
|
|
276
|
-
class="Item 2"
|
|
277
|
-
>
|
|
278
|
-
Item 2
|
|
279
|
-
</div>
|
|
280
|
-
<div
|
|
281
|
-
class="Item 3"
|
|
282
|
-
>
|
|
283
|
-
Item 3
|
|
284
|
-
</div>
|
|
285
|
-
<div
|
|
286
|
-
class="Item 4"
|
|
287
|
-
>
|
|
288
|
-
Item 4
|
|
289
|
-
</div>
|
|
290
|
-
<!---->
|
|
291
|
-
<button>
|
|
292
|
-
Add Item
|
|
293
|
-
</button>
|
|
294
|
-
|
|
295
|
-
</div>
|
|
296
|
-
`;
|
|
297
|
-
|
|
298
|
-
exports[`for statements > render a simple static array 1`] = `
|
|
299
|
-
<div>
|
|
300
|
-
<!---->
|
|
301
|
-
<div
|
|
302
|
-
class="Item 1"
|
|
303
|
-
>
|
|
304
|
-
Item 1
|
|
305
|
-
</div>
|
|
306
|
-
<div
|
|
307
|
-
class="Item 2"
|
|
308
|
-
>
|
|
309
|
-
Item 2
|
|
310
|
-
</div>
|
|
311
|
-
<div
|
|
312
|
-
class="Item 3"
|
|
313
|
-
>
|
|
314
|
-
Item 3
|
|
315
|
-
</div>
|
|
316
|
-
<!---->
|
|
317
|
-
|
|
318
|
-
</div>
|
|
319
|
-
`;
|
|
320
|
-
|
|
321
241
|
exports[`for statements > renders a simple dynamic array 1`] = `
|
|
322
242
|
<div>
|
|
323
243
|
<!---->
|
|
@@ -56,51 +56,3 @@ exports[`basic client > rendering & text > should handle lexical scopes correctl
|
|
|
56
56
|
|
|
57
57
|
</div>
|
|
58
58
|
`;
|
|
59
|
-
|
|
60
|
-
exports[`basic client > text rendering > basic operations 1`] = `
|
|
61
|
-
<div>
|
|
62
|
-
<div>
|
|
63
|
-
0
|
|
64
|
-
</div>
|
|
65
|
-
<div>
|
|
66
|
-
2
|
|
67
|
-
</div>
|
|
68
|
-
<div>
|
|
69
|
-
5
|
|
70
|
-
</div>
|
|
71
|
-
<div>
|
|
72
|
-
2
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
</div>
|
|
76
|
-
`;
|
|
77
|
-
|
|
78
|
-
exports[`basic client > text rendering > renders semi-dynamic text 1`] = `
|
|
79
|
-
<div>
|
|
80
|
-
<div>
|
|
81
|
-
Hello World
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
</div>
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
exports[`basic client > text rendering > renders simple JS expression logic correctly 1`] = `
|
|
88
|
-
<div>
|
|
89
|
-
<div>
|
|
90
|
-
{"0":"Test"}
|
|
91
|
-
</div>
|
|
92
|
-
<div>
|
|
93
|
-
1
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
</div>
|
|
97
|
-
`;
|
|
98
|
-
|
|
99
|
-
exports[`basic client > text rendering > renders static text 1`] = `
|
|
100
|
-
<div>
|
|
101
|
-
<div>
|
|
102
|
-
Hello World
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
</div>
|
|
106
|
-
`;
|
|
@@ -123,4 +123,20 @@ describe('basic client > errors', () => {
|
|
|
123
123
|
|
|
124
124
|
expect(error).toBe('Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation');
|
|
125
125
|
});
|
|
126
|
+
|
|
127
|
+
it('should throw error for await in client-side control-flow statements', () => {
|
|
128
|
+
const code = `
|
|
129
|
+
export default component App() {
|
|
130
|
+
let data = 'initial';
|
|
131
|
+
if (true) {
|
|
132
|
+
await new Promise(r => setTimeout(r, 100));
|
|
133
|
+
data = 'loaded';
|
|
134
|
+
}
|
|
135
|
+
<div>{data}</div>
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
expect(() => {
|
|
139
|
+
compile(code, 'test.ripple', { mode: 'client' });
|
|
140
|
+
}).toThrow('`await` is not allowed in client-side control-flow statements');
|
|
141
|
+
});
|
|
126
142
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ripple/server';
|
|
3
|
+
import { track, set, get } from 'ripple';
|
|
4
|
+
|
|
5
|
+
describe('await in control flow', () => {
|
|
6
|
+
it('should handle await inside if statement', async () => {
|
|
7
|
+
component App() {
|
|
8
|
+
let condition = true;
|
|
9
|
+
let data = track('loading');
|
|
10
|
+
|
|
11
|
+
if (condition) {
|
|
12
|
+
await new Promise(resolve => setTimeout(() => {
|
|
13
|
+
@data = 'loaded';
|
|
14
|
+
resolve();
|
|
15
|
+
}, 10));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
<div>{@data}</div>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { body } = await render(App);
|
|
22
|
+
expect(body).toBe('<div>loaded</div>');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should handle await inside for...of loop', async () => {
|
|
26
|
+
component App() {
|
|
27
|
+
const items = [1, 2, 3];
|
|
28
|
+
let result = '';
|
|
29
|
+
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
32
|
+
result += item;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<div>{result}</div>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { body } = await render(App);
|
|
39
|
+
expect(body).toBe('<div>123</div>');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle await inside switch statement', async () => {
|
|
43
|
+
component App() {
|
|
44
|
+
let value = 'b';
|
|
45
|
+
|
|
46
|
+
switch (value) {
|
|
47
|
+
case 'a':
|
|
48
|
+
<div>{'Case A'}</div>
|
|
49
|
+
break;
|
|
50
|
+
case 'b':
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
52
|
+
<div>{'Case B'}</div>
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
<div>{'Default Case'}</div>
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { body } = await render(App);
|
|
60
|
+
expect(body).toBe('<div>Case B</div>');
|
|
61
|
+
});});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ripple/server';
|
|
3
|
+
|
|
4
|
+
describe('for statements in SSR', () => {
|
|
5
|
+
it('renders a simple static array', async () => {
|
|
6
|
+
component App() {
|
|
7
|
+
const items = ['Item 1', 'Item 2', 'Item 3'];
|
|
8
|
+
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
<div class={item}>{item}</div>
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { body } = await render(App);
|
|
15
|
+
expect(body).toBe('<div class="Item 1">Item 1</div><div class="Item 2">Item 2</div><div class="Item 3">Item 3</div>');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders nested for...of loops', async () => {
|
|
19
|
+
component App() {
|
|
20
|
+
const groups = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Group 1',
|
|
23
|
+
items: ['Item 1.1', 'Item 1.2']
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'Group 2',
|
|
27
|
+
items: ['Item 2.1', 'Item 2.2']
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const group of groups) {
|
|
32
|
+
<h1>{group.name}</h1>
|
|
33
|
+
<ul>
|
|
34
|
+
for (const item of group.items) {
|
|
35
|
+
<li>{item}</li>
|
|
36
|
+
}
|
|
37
|
+
</ul>
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { body } = await render(App);
|
|
42
|
+
expect(body).toBe('<h1>Group 1</h1><ul><li>Item 1.1</li><li>Item 1.2</li></ul><h1>Group 2</h1><ul><li>Item 2.1</li><li>Item 2.2</li></ul>');
|
|
43
|
+
});
|
|
44
|
+
});
|