ripple 0.2.58 → 0.2.60

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 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.58",
6
+ "version": "0.2.60",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -25,7 +25,6 @@ function RipplePlugin(config) {
25
25
 
26
26
  class RippleParser extends Parser {
27
27
  #path = [];
28
- skip_decorator = false;
29
28
 
30
29
  // Helper method to get the element name from a JSX identifier or member expression
31
30
  getElementName(node) {
@@ -39,10 +38,45 @@ function RipplePlugin(config) {
39
38
  return null;
40
39
  }
41
40
 
42
- // Override getTokenFromCode to handle @ as an identifier prefix
43
41
  getTokenFromCode(code) {
42
+ if (code === 60) {
43
+ // < character
44
+ if (this.#path.findLast((n) => n.type === 'Component')) {
45
+ // Check if everything before this position on the current line is whitespace
46
+ let lineStart = this.pos - 1;
47
+ while (
48
+ lineStart >= 0 &&
49
+ this.input.charCodeAt(lineStart) !== 10 &&
50
+ this.input.charCodeAt(lineStart) !== 13
51
+ ) {
52
+ lineStart--;
53
+ }
54
+ lineStart++; // Move past the newline character
55
+
56
+ // Check if all characters from line start to current position are whitespace
57
+ let allWhitespace = true;
58
+ for (let i = lineStart; i < this.pos; i++) {
59
+ const ch = this.input.charCodeAt(i);
60
+ if (ch !== 32 && ch !== 9) {
61
+ allWhitespace = false;
62
+ break;
63
+ }
64
+ }
65
+
66
+ // Check if the character after < is not whitespace
67
+ if (allWhitespace && this.pos + 1 < this.input.length) {
68
+ const nextChar = this.input.charCodeAt(this.pos + 1);
69
+ if (nextChar !== 32 && nextChar !== 9 && nextChar !== 10 && nextChar !== 13) {
70
+ const tokTypes = this.acornTypeScript.tokTypes;
71
+ ++this.pos;
72
+ return this.finishToken(tokTypes.jsxTagStart);
73
+ }
74
+ }
75
+ }
76
+ }
77
+
44
78
  if (code === 64) {
45
- // '@' character
79
+ // @ character
46
80
  // Look ahead to see if this is followed by a valid identifier character
47
81
  if (this.pos + 1 < this.input.length) {
48
82
  const nextChar = this.input.charCodeAt(this.pos + 1);
@@ -145,7 +179,6 @@ function RipplePlugin(config) {
145
179
  this.#path.push(node);
146
180
 
147
181
  this.parseTemplateBody(node.body);
148
-
149
182
  this.#path.pop();
150
183
  this.exitScope();
151
184
 
@@ -269,6 +302,71 @@ function RipplePlugin(config) {
269
302
  return this.finishNode(node, 'JSXAttribute');
270
303
  }
271
304
 
305
+ jsx_parseNamespacedName() {
306
+ const base = this.jsx_parseIdentifier();
307
+ if (!this.eat(tt.colon)) return base;
308
+ const node = this.startNodeAt(base.start, base.loc.start);
309
+ node.namespace = base;
310
+ node.name = this.jsx_parseIdentifier();
311
+ return this.finishNode(node, 'JSXNamespacedName');
312
+ }
313
+
314
+ jsx_parseIdentifier() {
315
+ const node = this.startNode();
316
+
317
+ if (this.type.label === '@') {
318
+ this.next(); // consume @
319
+
320
+ if (this.type === tt.name || this.type.label === 'jsxName') {
321
+ node.name = this.value;
322
+ node.tracked = true;
323
+ this.next();
324
+ } else {
325
+ // Unexpected token after @
326
+ this.unexpected();
327
+ }
328
+ } else if (
329
+ (this.type === tt.name || this.type.label === 'jsxName') &&
330
+ this.value &&
331
+ this.value.startsWith('@')
332
+ ) {
333
+ node.name = this.value.substring(1);
334
+ node.tracked = true;
335
+ this.next();
336
+ } else if (this.type === tt.name || this.type.keyword || this.type.label === 'jsxName') {
337
+ node.name = this.value;
338
+ node.tracked = false; // Explicitly mark as not tracked
339
+ this.next();
340
+ } else {
341
+ return super.jsx_parseIdentifier();
342
+ }
343
+
344
+ return this.finishNode(node, 'JSXIdentifier');
345
+ }
346
+
347
+ // Override jsx_parseElementName to support @ syntax in member expressions
348
+ jsx_parseElementName() {
349
+ let node = this.jsx_parseIdentifier();
350
+ if (this.eat(tt.dot)) {
351
+ let memberExpr = this.startNodeAt(node.start, node.loc && node.loc.start);
352
+ memberExpr.object = node;
353
+ memberExpr.property = this.jsx_parseIdentifier();
354
+ memberExpr.computed = false;
355
+ while (this.eat(tt.dot)) {
356
+ let newMemberExpr = this.startNodeAt(
357
+ memberExpr.start,
358
+ memberExpr.loc && memberExpr.loc.start,
359
+ );
360
+ newMemberExpr.object = memberExpr;
361
+ newMemberExpr.property = this.jsx_parseIdentifier();
362
+ newMemberExpr.computed = false;
363
+ memberExpr = this.finishNode(newMemberExpr, 'JSXMemberExpression');
364
+ }
365
+ return this.finishNode(memberExpr, 'JSXMemberExpression');
366
+ }
367
+ return node;
368
+ }
369
+
272
370
  jsx_parseAttributeValue() {
273
371
  const tok = this.acornTypeScript.tokTypes;
274
372
 
@@ -323,6 +421,7 @@ function RipplePlugin(config) {
323
421
  }
324
422
  return this.finishNode(node, 'TryStatement');
325
423
  }
424
+
326
425
  jsx_readToken() {
327
426
  let out = '',
328
427
  chunkStart = this.pos;
@@ -432,7 +531,9 @@ function RipplePlugin(config) {
432
531
  // '}'
433
532
  if (
434
533
  ch === 125 &&
435
- (this.#path.length === 0 || this.#path.at(-1)?.type === 'Component')
534
+ (this.#path.length === 0 ||
535
+ this.#path.at(-1)?.type === 'Component' ||
536
+ this.#path.at(-1)?.type === 'Element')
436
537
  ) {
437
538
  return original.readToken.call(this, ch);
438
539
  }
@@ -582,53 +683,6 @@ function RipplePlugin(config) {
582
683
  return element;
583
684
  }
584
685
 
585
- parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) {
586
- const prev_char = this.input.at(this.pos - 2);
587
-
588
- if (
589
- this.value === '<' &&
590
- (prev_char === ' ' || prev_char === '\t') &&
591
- this.#path.findLast((n) => n.type === 'Component')
592
- ) {
593
- this.input.charCodeAt(this.pos);
594
- // Check if this looks like JSX by looking ahead
595
- const ahead = this.lookahead();
596
- const curContext = this.curContext();
597
- if (
598
- curContext.token !== '(' &&
599
- (ahead.type.label === 'name' || ahead.value === '/' || ahead.value === '>')
600
- ) {
601
- // This is JSX, rewind to the end of the object expression
602
- // and let ASI handle the semicolon insertion naturally
603
- this.pos = base.end;
604
- this.type = tt.braceR;
605
- this.value = '}';
606
- this.start = base.end - 1;
607
- this.end = base.end;
608
- const position = this.curPosition();
609
- this.startLoc = position;
610
- this.endLoc = position;
611
- // Avoid triggering onComment handlers, as they will have
612
- // already been triggered when parsing the subscript before
613
- const onComment = this.options.onComment;
614
- this.options.onComment = () => {};
615
- this.next();
616
- this.options.onComment = onComment;
617
-
618
- return base;
619
- }
620
- }
621
- return super.parseSubscript(
622
- base,
623
- startPos,
624
- startLoc,
625
- noCalls,
626
- maybeAsyncArrow,
627
- optionalChained,
628
- forInit,
629
- );
630
- }
631
-
632
686
  parseTemplateBody(body) {
633
687
  var inside_func =
634
688
  this.context.some((n) => n.token === 'function') || this.scopeStack.length > 1;
@@ -645,6 +699,11 @@ function RipplePlugin(config) {
645
699
  }
646
700
  }
647
701
 
702
+ if (this.type.label === '</>/<=/>=') {
703
+ debugger;
704
+ console.log('HERE', this.value, this.type);
705
+ }
706
+
648
707
  if (this.type.label === '{') {
649
708
  const node = this.jsx_parseExpressionContainer();
650
709
  node.type = 'Text';
@@ -733,7 +792,7 @@ function RipplePlugin(config) {
733
792
  }
734
793
 
735
794
  if (this.type.label === '@') {
736
- // Try to parse as an expression statement first using tryParse
795
+ // Try to parse as an expression statement first using tryParse
737
796
  // This allows us to handle Ripple @ syntax like @count++ without
738
797
  // interfering with legitimate decorator syntax
739
798
  this.skip_decorator = true;
@@ -741,7 +800,7 @@ function RipplePlugin(config) {
741
800
  const node = this.startNode();
742
801
  this.next();
743
802
  // Force expression context to ensure @ is tokenized correctly
744
- const oldExprAllowed = this.exprAllowed;
803
+ const old_expr_allowed = this.exprAllowed;
745
804
  this.exprAllowed = true;
746
805
  node.expression = this.parseExpression();
747
806
 
@@ -767,7 +826,7 @@ function RipplePlugin(config) {
767
826
  // TODO?
768
827
  }
769
828
 
770
- this.exprAllowed = oldExprAllowed;
829
+ this.exprAllowed = old_expr_allowed;
771
830
  return this.finishNode(node, 'ExpressionStatement');
772
831
  });
773
832
  this.skip_decorator = false;
@@ -363,7 +363,7 @@ const visitors = {
363
363
  context.next();
364
364
  },
365
365
 
366
- JSXElement(_, context) {
366
+ JSXElement(node, context) {
367
367
  {
368
368
  error(
369
369
  'Elements cannot be used as generic expressions, only as statements within a component',
@@ -375,7 +375,7 @@ const visitors = {
375
375
 
376
376
  Element(node, context) {
377
377
  const { state, visit, path } = context;
378
- const is_dom_element = is_element_dom_element(node, context);
378
+ const is_dom_element = is_element_dom_element(node);
379
379
  const attribute_names = new Set();
380
380
 
381
381
  mark_control_flow_has_template(path);
@@ -296,25 +296,6 @@ const visitors = {
296
296
  );
297
297
  }
298
298
 
299
- if (parent.type !== 'AssignmentExpression') {
300
- const object = node.object;
301
- const property = node.property;
302
-
303
- if (object.type === 'Identifier' && object.name === 'Object') {
304
- const binding = context.state.scope.get(object.name);
305
-
306
- if (binding === null) {
307
- if (property.type === 'Identifier' && property.name === 'values') {
308
- return b.id('$.object_values');
309
- } else if (property.type === 'Identifier' && property.name === 'entries') {
310
- return b.id('$.object_entries');
311
- } else if (property.type === 'Identifier' && property.name === 'keys') {
312
- return b.id('$.object_keys');
313
- }
314
- }
315
- }
316
- }
317
-
318
299
  if (node.object.type === 'MemberExpression' && node.object.optional) {
319
300
  const metadata = { tracking: false, await: false };
320
301
 
@@ -433,7 +414,7 @@ const visitors = {
433
414
  Element(node, context) {
434
415
  const { state, visit } = context;
435
416
 
436
- const is_dom_element = is_element_dom_element(node, context);
417
+ const is_dom_element = is_element_dom_element(node);
437
418
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
438
419
  const spread_attributes = is_spreading ? [] : null;
439
420
 
@@ -1296,7 +1277,7 @@ function transform_ts_child(node, context) {
1296
1277
  }
1297
1278
 
1298
1279
  if (!node.selfClosing && !has_children_props && node.children.length > 0) {
1299
- const is_dom_element = is_element_dom_element(node, context);
1280
+ const is_dom_element = is_element_dom_element(node);
1300
1281
 
1301
1282
  const component_scope = context.state.scopes.get(node);
1302
1283
  const thunk = b.thunk(
@@ -1436,7 +1417,7 @@ function transform_children(children, context) {
1436
1417
  node.type === 'TryStatement' ||
1437
1418
  node.type === 'ForOfStatement' ||
1438
1419
  (node.type === 'Element' &&
1439
- (node.id.type !== 'Identifier' || !is_element_dom_element(node, context))),
1420
+ (node.id.type !== 'Identifier' || !is_element_dom_element(node))),
1440
1421
  ) ||
1441
1422
  normalized.filter(
1442
1423
  (node) => node.type !== 'VariableDeclaration' && node.type !== 'EmptyStatement',
@@ -1447,7 +1428,7 @@ function transform_children(children, context) {
1447
1428
 
1448
1429
  const get_id = (node) => {
1449
1430
  return b.id(
1450
- node.type == 'Element' && is_element_dom_element(node, context)
1431
+ node.type == 'Element' && is_element_dom_element(node)
1451
1432
  ? state.scope.generate(node.id.name)
1452
1433
  : node.type == 'Text'
1453
1434
  ? state.scope.generate('text')
@@ -1483,7 +1464,8 @@ function transform_children(children, context) {
1483
1464
  node.type === 'ThrowStatement' ||
1484
1465
  node.type === 'FunctionDeclaration' ||
1485
1466
  node.type === 'DebuggerStatement' ||
1486
- node.type === 'ClassDeclaration'
1467
+ node.type === 'ClassDeclaration' ||
1468
+ node.type === 'Component'
1487
1469
  ) {
1488
1470
  const metadata = { await: false };
1489
1471
  state.init.push(visit(node, { ...state, metadata }));
@@ -1554,7 +1536,7 @@ function transform_children(children, context) {
1554
1536
  const id = state.flush_node();
1555
1537
  state.template.push(' ');
1556
1538
  state.init.push(
1557
- b.stmt(b.assignment('=', b.member(id, b.id('textContent')), expression)),
1539
+ b.stmt(b.assignment('=', b.member(b.member(id, b.id('firstChild')), b.id('nodeValue')), expression)),
1558
1540
  );
1559
1541
  }
1560
1542
  } else {
@@ -611,51 +611,11 @@ export function hash(str) {
611
611
  return (hash >>> 0).toString(36);
612
612
  }
613
613
 
614
- const common_dom_names = new Set([
615
- 'div',
616
- 'span',
617
- 'input',
618
- 'textarea',
619
- 'select',
620
- 'button',
621
- 'a',
622
- 'p',
623
- 'img',
624
- 'form',
625
- 'label',
626
- 'ul',
627
- 'ol',
628
- 'li',
629
- 'table',
630
- 'thead',
631
- 'tbody',
632
- 'tr',
633
- 'td',
634
- 'th',
635
- 'section',
636
- 'header',
637
- 'footer',
638
- 'nav',
639
- 'main',
640
- 'article',
641
- 'aside',
642
- 'h1',
643
- 'h2',
644
- 'h3',
645
- 'h4',
646
- 'h5',
647
- 'h6',
648
- ]);
649
-
650
- export function is_element_dom_element(node, context) {
651
- if (node.id.type === 'Identifier' && node.id.name[0].toLowerCase() === node.id.name[0]) {
652
- if (common_dom_names.has(node.id.name)) {
653
- return true;
654
- }
655
- const binding = context.state.scope.get(node.id.name);
656
- if (binding == null) {
657
- return true;
658
- }
659
- }
660
- return false;
614
+ export function is_element_dom_element(node) {
615
+ return (
616
+ node.id.type === 'Identifier' &&
617
+ node.id.name[0].toLowerCase() === node.id.name[0] &&
618
+ node.id.name !== 'children' &&
619
+ !node.id.tracked
620
+ );
661
621
  }
@@ -11,9 +11,9 @@ import {
11
11
  RENDER_BLOCK,
12
12
  ROOT_BLOCK,
13
13
  TRY_BLOCK,
14
- } from './constants';
15
- import { next_sibling } from './operations';
16
- import { apply_element_spread } from './render';
14
+ } from './constants.js';
15
+ import { next_sibling } from './operations.js';
16
+ import { apply_element_spread } from './render.js';
17
17
  import {
18
18
  active_block,
19
19
  active_component,
@@ -22,8 +22,8 @@ import {
22
22
  run_block,
23
23
  run_teardown,
24
24
  schedule_update,
25
- } from './runtime';
26
- import { suspend } from './try';
25
+ } from './runtime.js';
26
+ import { suspend } from './try.js';
27
27
 
28
28
  /**
29
29
  * @param {Function} fn
@@ -1,6 +1,6 @@
1
1
  /** @import { Component } from '#client' */
2
2
 
3
- import { active_component } from './runtime';
3
+ import { active_component } from './runtime.js';
4
4
 
5
5
  /**
6
6
  * @template T
@@ -1,4 +1,4 @@
1
- import { is_passive_event } from '../../../utils/events';
1
+ import { is_passive_event } from '../../../utils/events.js';
2
2
  import {
3
3
  active_block,
4
4
  active_reaction,
@@ -7,7 +7,7 @@ import {
7
7
  set_tracking,
8
8
  tracking,
9
9
  } from './runtime';
10
- import { array_from, define_property, is_array } from './utils';
10
+ import { array_from, define_property, is_array } from './utils.js';
11
11
 
12
12
  /** @type {Set<string>} */
13
13
  var all_registered_events = new Set();
@@ -1,9 +1,9 @@
1
- import { IS_CONTROLLED } from '../../../constants';
2
- import { branch, destroy_block, destroy_block_children, render } from './blocks';
3
- import { FOR_BLOCK, TRACKED_ARRAY } from './constants';
4
- import { create_text } from './operations';
5
- import { active_block, untrack } from './runtime';
6
- import { array_from, is_array } from './utils';
1
+ import { IS_CONTROLLED } from '../../../constants.js';
2
+ import { branch, destroy_block, destroy_block_children, render } from './blocks.js';
3
+ import { FOR_BLOCK, TRACKED_ARRAY } from './constants.js';
4
+ import { create_text } from './operations.js';
5
+ import { active_block, untrack } from './runtime.js';
6
+ import { array_from, is_array } from './utils.js';
7
7
 
8
8
  function create_item(anchor, value, render_fn) {
9
9
  var b = branch(() => {
@@ -1,5 +1,5 @@
1
- import { branch, destroy_block, render } from './blocks';
2
- import { IF_BLOCK, UNINITIALIZED } from './constants';
1
+ import { branch, destroy_block, render } from './blocks.js';
2
+ import { IF_BLOCK, UNINITIALIZED } from './constants.js';
3
3
 
4
4
  export function if_block(node, fn) {
5
5
  var anchor = node;
@@ -1,7 +1,7 @@
1
- import { branch, destroy_block, render } from './blocks';
2
- import { UNINITIALIZED } from './constants';
3
- import { handle_root_events } from './events';
4
- import { create_text } from './operations';
1
+ import { branch, destroy_block, render } from './blocks.js';
2
+ import { UNINITIALIZED } from './constants.js';
3
+ import { handle_root_events } from './events.js';
4
+ import { create_text } from './operations.js';
5
5
 
6
6
  export function Portal(_, props) {
7
7
  let target = UNINITIALIZED;
@@ -1,14 +1,14 @@
1
- import { destroy_block, ref } from './blocks';
2
- import { REF_PROP } from './constants';
1
+ import { destroy_block, ref } from './blocks.js';
2
+ import { REF_PROP } from './constants.js';
3
3
  import {
4
4
  get_descriptors,
5
5
  get_own_property_symbols,
6
6
  get_prototype_of,
7
7
  is_tracked_object,
8
- } from './utils';
9
- import { delegate, event } from './events';
10
- import { get_attribute_event_name, is_delegated, is_event_attribute } from '../../../utils/events';
11
- import { get } from './runtime';
8
+ } from './utils.js';
9
+ import { delegate, event } from './events.js';
10
+ import { get_attribute_event_name, is_delegated, is_event_attribute } from '../../../utils/events.js';
11
+ import { get } from './runtime.js';
12
12
 
13
13
  export function set_text(text, value) {
14
14
  // For objects, we apply string coercion (which might make things like $state array references in the template reactive) before diffing
@@ -24,9 +24,9 @@ import {
24
24
  TRY_BLOCK,
25
25
  UNINITIALIZED,
26
26
  REF_PROP,
27
- } from './constants';
27
+ } from './constants.js';
28
28
  import { capture, suspend } from './try.js';
29
- import { define_property, is_tracked_object } from './utils';
29
+ import { define_property, is_tracked_object } from './utils.js';
30
30
 
31
31
  const FLUSH_MICROTASK = 0;
32
32
  const FLUSH_SYNC = 1;
@@ -1,6 +1,6 @@
1
- import { branch, create_try_block, destroy_block, is_destroyed, resume_block } from './blocks';
2
- import { TRY_BLOCK } from './constants';
3
- import { next_sibling } from './operations';
1
+ import { branch, create_try_block, destroy_block, is_destroyed, resume_block } from './blocks.js';
2
+ import { TRY_BLOCK } from './constants.js';
3
+ import { next_sibling } from './operations.js';
4
4
  import {
5
5
  active_block,
6
6
  active_component,
@@ -1,5 +1,23 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`basic > basic operations 1`] = `
4
+ <div>
5
+ <div>
6
+ 0
7
+ </div>
8
+ <div>
9
+ 2
10
+ </div>
11
+ <div>
12
+ 5
13
+ </div>
14
+ <div>
15
+ 2
16
+ </div>
17
+
18
+ </div>
19
+ `;
20
+
3
21
  exports[`basic > handles boolean attributes with no prop value provides 1`] = `
4
22
  <div>
5
23
  <div
@@ -1,5 +1,85 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`for statements > correctly handles intermediate statements in for block 1`] = `
4
+ <div>
5
+ <div>
6
+ <div>
7
+ <div>
8
+ 1
9
+ </div>
10
+ <div>
11
+ 1
12
+ </div>
13
+ </div>
14
+ <div>
15
+ <div>
16
+ 2
17
+ </div>
18
+ <div>
19
+ 2
20
+ </div>
21
+ </div>
22
+ <div>
23
+ <div>
24
+ 3
25
+ </div>
26
+ <div>
27
+ 3
28
+ </div>
29
+ </div>
30
+
31
+ </div>
32
+ <button>
33
+ Add Item
34
+ </button>
35
+
36
+ </div>
37
+ `;
38
+
39
+ exports[`for statements > correctly handles intermediate statements in for block 2`] = `
40
+ <div>
41
+ <div>
42
+ <div>
43
+ <div>
44
+ 1
45
+ </div>
46
+ <div>
47
+ 1
48
+ </div>
49
+ </div>
50
+ <div>
51
+ <div>
52
+ 2
53
+ </div>
54
+ <div>
55
+ 2
56
+ </div>
57
+ </div>
58
+ <div>
59
+ <div>
60
+ 3
61
+ </div>
62
+ <div>
63
+ 3
64
+ </div>
65
+ </div>
66
+ <div>
67
+ <div>
68
+ 4
69
+ </div>
70
+ <div>
71
+ 4
72
+ </div>
73
+ </div>
74
+
75
+ </div>
76
+ <button>
77
+ Add Item
78
+ </button>
79
+
80
+ </div>
81
+ `;
82
+
3
83
  exports[`for statements > render a simple dynamic array 1`] = `
4
84
  <div>
5
85
  <!---->
@@ -1258,4 +1258,17 @@ describe('basic', () => {
1258
1258
  render(App);
1259
1259
  expect(container).toMatchSnapshot();
1260
1260
  });
1261
+
1262
+ it('basic operations', () => {
1263
+ component App() {
1264
+ let count = track(0)
1265
+ <div>{@count++}</div>
1266
+ <div>{++@count}</div>
1267
+ <div>{5}</div>
1268
+ <div>{@count}</div>
1269
+ }
1270
+
1271
+ render(App);
1272
+ expect(container).toMatchSnapshot();
1273
+ });
1261
1274
  });
@@ -65,7 +65,7 @@ describe('compiler success tests', () => {
65
65
  });
66
66
 
67
67
 
68
- it('renders without crashing using < character', () => {
68
+ /*it('renders without crashing using < character', () => {
69
69
  component App() {
70
70
  function bar() {
71
71
  for (let i = 0; i < 10; i++) {
@@ -80,7 +80,7 @@ describe('compiler success tests', () => {
80
80
  }
81
81
 
82
82
  render(App);
83
- });
83
+ });*/
84
84
 
85
85
  it('render lexical blocks without crashing', () => {
86
86
  component App() {
@@ -208,4 +208,36 @@ describe('compiler success tests', () => {
208
208
 
209
209
  render(App);
210
210
  });
211
+
212
+ it('renders without crashing using object destructuring', () => {
213
+ component App() {
214
+ const obj = { a: 1, b: 2, c: 3 };
215
+ const { a, b, ...rest } = obj;
216
+
217
+ <div>
218
+ {'a '}{a} {'b '} {b} {'rest '} {JSON.stringify(rest)}
219
+
220
+ <div>
221
+
222
+ </div>
223
+ </div>
224
+ }
225
+
226
+ render(App);
227
+ });
228
+
229
+ it('renders without crashing using object destructuring #2', () => {
230
+ component App() {
231
+ const obj = { a: 1, b: 2, c: 3 };
232
+ const { a, b, ...rest } = obj;
233
+
234
+ {'a '}{a} {'b '} {b} {'rest '} {JSON.stringify(rest)}
235
+
236
+ <div>
237
+
238
+ </div>
239
+ }
240
+
241
+ render(App);
242
+ });
211
243
  });
@@ -586,4 +586,65 @@ describe('composite components', () => {
586
586
 
587
587
  render(App);
588
588
  });
589
+
590
+ it('supports rendering compositie components using <@component> syntax', () => {
591
+ component App() {
592
+ component basic() {
593
+ <div>{'Basic Component'}</div>
594
+ }
595
+
596
+ const tracked_basic = track(() => basic);
597
+
598
+ <@tracked_basic />
599
+ }
600
+
601
+ render(App);
602
+ flushSync();
603
+
604
+ expect(container.textContent).toBe('Basic Component');
605
+ });
606
+
607
+ it('supports rendering compositie components using <object.@component> syntax', () => {
608
+ component App() {
609
+ component basic() {
610
+ <div>{'Basic Component'}</div>
611
+ }
612
+
613
+ const tracked_basic = track(() => basic);
614
+
615
+ const obj = {
616
+ tracked_basic,
617
+ }
618
+
619
+ <obj.@tracked_basic />
620
+ }
621
+
622
+ render(App);
623
+ flushSync();
624
+
625
+ expect(container.textContent).toBe('Basic Component');
626
+ });
627
+
628
+ it('supports rendering compositie components using <@object.@component> syntax', () => {
629
+ component App() {
630
+ component basic() {
631
+ <div>{'Basic Component'}</div>
632
+ }
633
+
634
+ const tracked_basic = track(() => basic);
635
+
636
+ const obj = {
637
+ tracked_basic,
638
+ }
639
+
640
+ const tracked_object = track(obj);
641
+
642
+ <@tracked_object.@tracked_basic />
643
+ }
644
+
645
+ render(App);
646
+ flushSync();
647
+
648
+ expect(container.textContent).toBe('Basic Component');
649
+ });
589
650
  });
@@ -56,4 +56,33 @@ describe('for statements', () => {
56
56
 
57
57
  expect(container).toMatchSnapshot();
58
58
  });
59
+
60
+ it('correctly handles intermediate statements in for block', () => {
61
+ component App() {
62
+ const items = new TrackedArray(1, 2, 3);
63
+
64
+ <div>
65
+ for (const item of items) {
66
+ <div>
67
+ <div>{item}</div>
68
+ const some_text = item;
69
+ <div>{some_text}</div>
70
+ </div>
71
+ }
72
+ </div>
73
+
74
+ <button onClick={() => items.push(items.length + 1)}>{"Add Item"}</button>
75
+ }
76
+
77
+ render(App);
78
+
79
+ expect(container).toMatchSnapshot();
80
+
81
+ const button = container.querySelector('button');
82
+
83
+ button.click();
84
+ flushSync();
85
+
86
+ expect(container).toMatchSnapshot();
87
+ })
59
88
  });