ripple 0.3.128 → 0.4.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 (192) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/package.json +10 -4
  3. package/src/runtime/element.js +23 -6
  4. package/src/runtime/index-client.js +81 -41
  5. package/src/runtime/index-server.js +1 -0
  6. package/src/runtime/internal/client/blocks.js +173 -91
  7. package/src/runtime/internal/client/component.js +4 -30
  8. package/src/runtime/internal/client/composite.js +12 -13
  9. package/src/runtime/internal/client/constants.js +9 -0
  10. package/src/runtime/internal/client/context.js +12 -19
  11. package/src/runtime/internal/client/css.js +7 -9
  12. package/src/runtime/internal/client/events.js +178 -90
  13. package/src/runtime/internal/client/expression.js +45 -25
  14. package/src/runtime/internal/client/for.js +409 -129
  15. package/src/runtime/internal/client/head.js +10 -5
  16. package/src/runtime/internal/client/html.js +1 -0
  17. package/src/runtime/internal/client/hydration.js +152 -10
  18. package/src/runtime/internal/client/if.js +239 -50
  19. package/src/runtime/internal/client/index.js +4 -12
  20. package/src/runtime/internal/client/operations.js +40 -61
  21. package/src/runtime/internal/client/portal.js +163 -85
  22. package/src/runtime/internal/client/render.js +39 -39
  23. package/src/runtime/internal/client/rpc.js +3 -3
  24. package/src/runtime/internal/client/runtime.js +592 -433
  25. package/src/runtime/internal/client/selector.js +5 -2
  26. package/src/runtime/internal/client/template.js +133 -158
  27. package/src/runtime/internal/client/transport.js +38 -0
  28. package/src/runtime/internal/client/try.js +465 -426
  29. package/src/runtime/internal/client/types.d.ts +56 -23
  30. package/src/runtime/internal/client/utils.js +1 -3
  31. package/src/runtime/internal/server/dynamic.js +20 -21
  32. package/src/runtime/internal/server/index.js +325 -275
  33. package/src/runtime/internal/server/rpc.js +3 -2
  34. package/src/runtime/internal/server/transport.js +16 -0
  35. package/src/runtime/transport.js +21 -0
  36. package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
  37. package/tests/client/array/array.copy-within.test.tsrx +12 -12
  38. package/tests/client/array/array.derived.test.tsrx +46 -46
  39. package/tests/client/array/array.iteration.test.tsrx +10 -10
  40. package/tests/client/array/array.mutations.test.tsrx +20 -20
  41. package/tests/client/array/array.static.test.tsrx +11 -11
  42. package/tests/client/array/array.to-methods.test.tsrx +6 -6
  43. package/tests/client/async-suspend.test.tsrx +74 -75
  44. package/tests/client/basic/basic.attributes.test.tsrx +86 -83
  45. package/tests/client/basic/basic.collections.test.tsrx +12 -12
  46. package/tests/client/basic/basic.components.test.tsrx +99 -25
  47. package/tests/client/basic/basic.errors.test.tsrx +18 -18
  48. package/tests/client/basic/basic.events.test.tsrx +42 -42
  49. package/tests/client/basic/basic.get-set.test.tsrx +8 -8
  50. package/tests/client/basic/basic.reactivity.test.tsrx +73 -38
  51. package/tests/client/basic/basic.rendering.test.tsrx +25 -25
  52. package/tests/client/basic/basic.utilities.test.tsrx +3 -3
  53. package/tests/client/boundaries.test.tsrx +8 -8
  54. package/tests/client/boxed-locals.test.tsrx +452 -0
  55. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
  56. package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
  57. package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
  58. package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
  59. package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
  60. package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
  61. package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
  62. package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
  63. package/tests/client/composite/composite.props.test.tsrx +149 -12
  64. package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
  65. package/tests/client/composite/composite.render.test.tsrx +26 -9
  66. package/tests/client/css/scoped-styles.test.tsrx +8 -8
  67. package/tests/client/date.test.tsrx +42 -42
  68. package/tests/client/derived-release.test.tsrx +370 -0
  69. package/tests/client/dynamic-elements.test.tsrx +45 -45
  70. package/tests/client/events.test.tsrx +97 -71
  71. package/tests/client/for-selector.test.tsrx +14 -14
  72. package/tests/client/for-single-root-items.test.tsrx +118 -3
  73. package/tests/client/for.test.tsrx +332 -31
  74. package/tests/client/head.test.tsrx +19 -19
  75. package/tests/client/html.test.tsrx +11 -9
  76. package/tests/client/if-block.test.tsrx +80 -0
  77. package/tests/client/map.test.tsrx +16 -16
  78. package/tests/client/media-query.test.tsrx +7 -7
  79. package/tests/client/portal.test.tsrx +224 -26
  80. package/tests/client/primitive-text.test.tsrx +50 -0
  81. package/tests/client/prop-kind-switch.test.tsrx +141 -0
  82. package/tests/client/ref.test.tsrx +17 -17
  83. package/tests/client/return.test.tsrx +26 -3
  84. package/tests/client/root-boundary.test.tsrx +42 -0
  85. package/tests/client/scoped-flush.test.tsrx +8 -8
  86. package/tests/client/set.test.tsrx +6 -6
  87. package/tests/client/svg.test.tsrx +11 -11
  88. package/tests/client/switch.test.tsrx +44 -44
  89. package/tests/client/track-async-hydration.test.tsrx +4 -4
  90. package/tests/client/try.test.tsrx +151 -83
  91. package/tests/client/tsx.test.tsrx +43 -40
  92. package/tests/client/url/url.derived.test.tsrx +6 -6
  93. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
  95. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
  96. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
  97. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
  98. package/tests/hydration/basic.test.js +74 -1
  99. package/tests/hydration/build-components.js +22 -1
  100. package/tests/hydration/compiled/client/basic.js +1142 -713
  101. package/tests/hydration/compiled/client/composite.js +151 -100
  102. package/tests/hydration/compiled/client/events.js +184 -171
  103. package/tests/hydration/compiled/client/for.js +1472 -1132
  104. package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
  105. package/tests/hydration/compiled/client/head.js +254 -201
  106. package/tests/hydration/compiled/client/hmr.js +54 -41
  107. package/tests/hydration/compiled/client/html-in-template.js +54 -39
  108. package/tests/hydration/compiled/client/html.js +1633 -1263
  109. package/tests/hydration/compiled/client/if-children.js +420 -270
  110. package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
  111. package/tests/hydration/compiled/client/if.js +229 -219
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +420 -404
  113. package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1277
  114. package/tests/hydration/compiled/client/portal.js +87 -95
  115. package/tests/hydration/compiled/client/reactivity.js +135 -101
  116. package/tests/hydration/compiled/client/return.js +28 -19
  117. package/tests/hydration/compiled/client/streaming.js +390 -306
  118. package/tests/hydration/compiled/client/switch.js +258 -266
  119. package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
  120. package/tests/hydration/compiled/client/try.js +173 -129
  121. package/tests/hydration/compiled/client/typed-text.js +71 -0
  122. package/tests/hydration/compiled/fixtures/money.js +10 -0
  123. package/tests/hydration/compiled/server/basic.js +123 -6
  124. package/tests/hydration/compiled/server/composite.js +16 -30
  125. package/tests/hydration/compiled/server/events.js +66 -18
  126. package/tests/hydration/compiled/server/for.js +117 -41
  127. package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
  128. package/tests/hydration/compiled/server/head.js +20 -20
  129. package/tests/hydration/compiled/server/hmr.js +2 -2
  130. package/tests/hydration/compiled/server/html.js +70 -238
  131. package/tests/hydration/compiled/server/if-children.js +107 -15
  132. package/tests/hydration/compiled/server/if.js +11 -15
  133. package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
  134. package/tests/hydration/compiled/server/portal.js +14 -22
  135. package/tests/hydration/compiled/server/reactivity.js +14 -14
  136. package/tests/hydration/compiled/server/streaming.js +19 -19
  137. package/tests/hydration/compiled/server/switch.js +8 -8
  138. package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
  139. package/tests/hydration/compiled/server/try.js +8 -8
  140. package/tests/hydration/compiled/server/typed-text.js +17 -0
  141. package/tests/hydration/components/basic.tsrx +44 -8
  142. package/tests/hydration/components/composite.tsrx +2 -2
  143. package/tests/hydration/components/events.tsrx +25 -25
  144. package/tests/hydration/components/for.tsrx +115 -67
  145. package/tests/hydration/components/fragment-cursor.tsrx +650 -0
  146. package/tests/hydration/components/head.tsrx +16 -16
  147. package/tests/hydration/components/hmr.tsrx +2 -2
  148. package/tests/hydration/components/html.tsrx +7 -7
  149. package/tests/hydration/components/if-children.tsrx +72 -25
  150. package/tests/hydration/components/if.tsrx +18 -18
  151. package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
  152. package/tests/hydration/components/portal.tsrx +3 -3
  153. package/tests/hydration/components/reactivity.tsrx +16 -16
  154. package/tests/hydration/components/streaming.tsrx +20 -20
  155. package/tests/hydration/components/switch.tsrx +20 -20
  156. package/tests/hydration/components/text-types.ts +4 -0
  157. package/tests/hydration/components/track-async-serialization.tsrx +80 -26
  158. package/tests/hydration/components/try.tsrx +8 -8
  159. package/tests/hydration/components/typed-text.tsrx +22 -0
  160. package/tests/hydration/fixtures/money.js +10 -0
  161. package/tests/hydration/for.test.js +36 -0
  162. package/tests/hydration/fragment-cursor.test.js +111 -0
  163. package/tests/hydration/if-children.test.js +24 -0
  164. package/tests/hydration/streaming.test.js +19 -0
  165. package/tests/hydration/track-async-serialization.test.js +87 -2
  166. package/tests/hydration/tsconfig.text-types.json +10 -0
  167. package/tests/hydration/typed-text.test.js +34 -0
  168. package/tests/server/await.test.tsrx +3 -3
  169. package/tests/server/basic.attributes.test.tsrx +42 -42
  170. package/tests/server/basic.components.test.tsrx +64 -8
  171. package/tests/server/basic.test.tsrx +34 -32
  172. package/tests/server/compiler.test.tsrx +1 -70
  173. package/tests/server/composite.props.test.tsrx +6 -6
  174. package/tests/server/dynamic-elements.test.tsrx +31 -8
  175. package/tests/server/for.test.tsrx +94 -0
  176. package/tests/server/head.test.tsrx +11 -11
  177. package/tests/server/scoped-styles.test.tsrx +2 -2
  178. package/tests/server/streaming-ssr.test.tsrx +48 -43
  179. package/tests/server/track-async-serialization.test.tsrx +204 -21
  180. package/tests/server/try.test.tsrx +48 -48
  181. package/tests/types/transport.ts +40 -0
  182. package/tests/utils/vite-plugin-config.test.js +23 -0
  183. package/types/index.d.ts +48 -7
  184. package/types/server.d.ts +3 -0
  185. package/types/transport.d.ts +15 -0
  186. package/src/runtime/internal/client/switch.js +0 -113
  187. package/src/utils/errors.js +0 -13
  188. package/tests/client/lazy-array.test.tsrx +0 -31
  189. package/tests/client/lazy-destructuring.test.tsrx +0 -438
  190. package/tests/client/tracked-index-access.test.tsrx +0 -113
  191. package/tests/server/lazy-destructuring.test.tsrx +0 -595
  192. package/tests/server/tracked-index-access.test.tsrx +0 -76
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "skipLibCheck": true
8
+ },
9
+ "include": ["components/typed-text.tsrx", "components/text-types.ts"]
10
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { flushSync as flush_sync, hydrate } from 'ripple';
3
+ import { render } from 'ripple/server';
4
+ import { container } from '../setup-hydration.js';
5
+ import { TypedText as server_typed_text } from './compiled/server/typed-text.js';
6
+ import { TypedText as client_typed_text } from './compiled/client/typed-text.js';
7
+
8
+ describe('typed text hydration', () => {
9
+ it('reuses server text nodes and updates imported primitive types', async () => {
10
+ const { body } = await render(server_typed_text);
11
+ expect(body).toContain('<first>');
12
+ container.innerHTML = body;
13
+ const run = container.querySelector('.typed-run');
14
+ const text = run.firstChild;
15
+ const number = container.querySelector('.typed-number').firstChild;
16
+ const tail = run.querySelector('span');
17
+ const unmount = hydrate(client_typed_text, { target: container });
18
+ try {
19
+ expect(container.querySelector('.typed-run')).toBe(run);
20
+ expect(run.firstChild).toBe(text);
21
+ expect(run.textContent).toBe('value: <first>2tail');
22
+ expect(container.querySelector('.typed-number').firstChild).toBe(number);
23
+ container.querySelector('button').click();
24
+ flush_sync();
25
+ expect(run.textContent).toBe('value: &next3tail');
26
+ expect(run.firstChild).toBe(text);
27
+ expect(run.querySelector('span')).toBe(tail);
28
+ expect(container.querySelector('.typed-number').firstChild).toBe(number);
29
+ expect(number.textContent).toBe('3');
30
+ } finally {
31
+ unmount();
32
+ }
33
+ });
34
+ });
@@ -8,16 +8,16 @@ describe('await in control flow', () => {
8
8
  // it('should handle await inside if statement', async () => {
9
9
  // function App() @{
10
10
  // let condition = true;
11
- // let &[data] = track('loading');
11
+ // const data = track('loading');
12
12
 
13
13
  // if (condition) {
14
14
  // await new Promise((resolve) => setTimeout(() => {
15
- // data = 'loaded';
15
+ // data.value = 'loaded';
16
16
  // resolve();
17
17
  // }, 10));
18
18
  // }
19
19
 
20
- // <div>{data}</div>
20
+ // <div>{data.value}</div>
21
21
  // }
22
22
 
23
23
  // const { body } = await render(App);
@@ -19,9 +19,9 @@ describe('basic server > attribute rendering', () => {
19
19
 
20
20
  it('render dynamic class attribute', async () => {
21
21
  function Basic() @{
22
- let &[active] = track(false);
22
+ const active = track(false);
23
23
  <>
24
- <div class={active ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
24
+ <div class={active.value ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
25
25
  <style>
26
26
  .active {
27
27
  color: green;
@@ -135,9 +135,9 @@ describe('basic server > attribute rendering', () => {
135
135
 
136
136
  it('render dynamic class object', async () => {
137
137
  function Basic() @{
138
- let &[active] = track(false);
138
+ const active = track(false);
139
139
  <>
140
- <div class={{ active: active, inactive: !active }}>{'Dynamic Class'}</div>
140
+ <div class={{ active: active.value, inactive: !active.value }}>{'Dynamic Class'}</div>
141
141
  <style>
142
142
  .active {
143
143
  color: green;
@@ -162,10 +162,10 @@ describe('basic server > attribute rendering', () => {
162
162
  'applies scoped ripple class to multiple elements with dynamic class expressions',
163
163
  async () => {
164
164
  function Basic() @{
165
- let &[selected] = track(1);
165
+ const selected = track(1);
166
166
  <>
167
- <div class={selected === 0 ? 'selected' : ''}>{`div 1`}</div>
168
- <div class={selected === 0 ? 'selected' : ''}>{`div 2`}</div>
167
+ <div class={selected.value === 0 ? 'selected' : ''}>{`div 1`}</div>
168
+ <div class={selected.value === 0 ? 'selected' : ''}>{`div 2`}</div>
169
169
  <style>
170
170
  div {
171
171
  background: green;
@@ -193,8 +193,8 @@ describe('basic server > attribute rendering', () => {
193
193
 
194
194
  it('render dynamic id attribute', async () => {
195
195
  function Basic() @{
196
- let &[count] = track(0);
197
- <div id={`item-${count}`}>{'Dynamic ID'}</div>
196
+ const count = track(0);
197
+ <div id={`item-${count.value}`}>{'Dynamic ID'}</div>
198
198
  }
199
199
 
200
200
  const { body } = await render(Basic);
@@ -207,8 +207,8 @@ describe('basic server > attribute rendering', () => {
207
207
 
208
208
  it('render dynamic style attribute', async () => {
209
209
  function Basic() @{
210
- let &[color] = track('red');
211
- <div style={`color: ${color}; font-weight: bold;`}>{'Dynamic Style'}</div>
210
+ const color = track('red');
211
+ <div style={`color: ${color.value}; font-weight: bold;`}>{'Dynamic Style'}</div>
212
212
  }
213
213
 
214
214
  const { body } = await render(Basic);
@@ -222,8 +222,8 @@ describe('basic server > attribute rendering', () => {
222
222
 
223
223
  it('render style attribute as dynamic object', async () => {
224
224
  function Basic() @{
225
- let &[color] = track('red');
226
- <div style={{ color: color, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
225
+ const color = track('red');
226
+ <div style={{ color: color.value, fontWeight: 'bold' }}>{'Dynamic Style'}</div>
227
227
  }
228
228
 
229
229
  const { body } = await render(Basic);
@@ -237,8 +237,8 @@ describe('basic server > attribute rendering', () => {
237
237
 
238
238
  it('render tracked variable as style attribute', async () => {
239
239
  function Basic() @{
240
- let &[style] = track({ color: 'red', fontWeight: 'bold' });
241
- <div {style}>{'Dynamic Style'}</div>
240
+ const style = track({ color: 'red', fontWeight: 'bold' });
241
+ <div style={style.value}>{'Dynamic Style'}</div>
242
242
  }
243
243
 
244
244
  const { body } = await render(Basic);
@@ -363,9 +363,9 @@ describe('basic server > attribute rendering', () => {
363
363
 
364
364
  it('render dynamic boolean attributes as false', async () => {
365
365
  function Basic() @{
366
- let &[disabled] = track(false);
367
- let &[checked] = track(false);
368
- <input type="checkbox" {disabled} {checked} />
366
+ const disabled = track(false);
367
+ const checked = track(false);
368
+ <input type="checkbox" disabled={disabled.value} checked={checked.value} />
369
369
  }
370
370
 
371
371
  const { body } = await render(Basic);
@@ -396,9 +396,9 @@ describe('basic server > attribute rendering', () => {
396
396
 
397
397
  it('render dynamic boolean attributes as true', async () => {
398
398
  function Basic() @{
399
- let &[disabled] = track(true);
400
- let &[checked] = track(true);
401
- <input type="checkbox" {disabled} {checked} />
399
+ const disabled = track(true);
400
+ const checked = track(true);
401
+ <input type="checkbox" disabled={disabled.value} checked={checked.value} />
402
402
  }
403
403
 
404
404
  const { body } = await render(Basic);
@@ -429,8 +429,8 @@ describe('basic server > attribute rendering', () => {
429
429
 
430
430
  it('renders formnovalidate as a boolean attribute', async () => {
431
431
  function Basic() @{
432
- let &[formnovalidate] = track(true);
433
- <button {formnovalidate}>{'Submit'}</button>
432
+ const formnovalidate = track(true);
433
+ <button formnovalidate={formnovalidate.value}>{'Submit'}</button>
434
434
  }
435
435
 
436
436
  const { body } = await render(Basic);
@@ -445,8 +445,8 @@ describe('basic server > attribute rendering', () => {
445
445
 
446
446
  it('renders hidden as a boolean attribute when true', async () => {
447
447
  function Basic() @{
448
- let &[hidden] = track(true);
449
- <div {hidden}>{'Hidden content'}</div>
448
+ const hidden = track(true);
449
+ <div hidden={hidden.value}>{'Hidden content'}</div>
450
450
  }
451
451
 
452
452
  const { body } = await render(Basic);
@@ -461,8 +461,8 @@ describe('basic server > attribute rendering', () => {
461
461
 
462
462
  it('does not render hidden when false', async () => {
463
463
  function Basic() @{
464
- let &[hidden] = track(false);
465
- <div {hidden}>{'Visible content'}</div>
464
+ const hidden = track(false);
465
+ <div hidden={hidden.value}>{'Visible content'}</div>
466
466
  }
467
467
 
468
468
  const { body } = await render(Basic);
@@ -476,12 +476,12 @@ describe('basic server > attribute rendering', () => {
476
476
 
477
477
  it('render multiple dynamic attributes', async () => {
478
478
  function Basic() @{
479
- let &[theme] = track('light');
480
- let &[size] = track('medium');
479
+ const theme = track('light');
480
+ const size = track('medium');
481
481
  <div
482
- class={`theme-${theme} size-${size}`}
483
- data-theme={theme}
484
- data-size={size}
482
+ class={`theme-${theme.value} size-${size.value}`}
483
+ data-theme={theme.value}
484
+ data-size={size.value}
485
485
  >{'Multiple Dynamic Attributes'}</div>
486
486
  }
487
487
 
@@ -497,11 +497,11 @@ describe('basic server > attribute rendering', () => {
497
497
 
498
498
  it('render conditional attributes', async () => {
499
499
  function Basic() @{
500
- let &[showTitle] = track(false);
501
- let &[showAria] = track(false);
500
+ const showTitle = track(false);
501
+ const showAria = track(false);
502
502
  <div
503
- title={showTitle ? 'This is a title' : undefined}
504
- aria-label={showAria ? 'Accessible label' : undefined}
503
+ title={showTitle.value ? 'This is a title' : undefined}
504
+ aria-label={showAria.value ? 'Accessible label' : undefined}
505
505
  >{'Conditional Attributes'}</div>
506
506
  }
507
507
 
@@ -516,11 +516,11 @@ describe('basic server > attribute rendering', () => {
516
516
 
517
517
  it('render spread attributes', async () => {
518
518
  function Basic() @{
519
- let &[attrs] = track<TestAttributes>({
519
+ const attrs = track<TestAttributes>({
520
520
  class: 'initial',
521
521
  id: 'test-1',
522
522
  });
523
- <div {...attrs}>{'Spread Attributes'}</div>
523
+ <div {...attrs.value}>{'Spread Attributes'}</div>
524
524
  }
525
525
 
526
526
  const { body } = await render(Basic);
@@ -535,12 +535,12 @@ describe('basic server > attribute rendering', () => {
535
535
 
536
536
  it('renders with reactive attributes with nested reactive attributes', async () => {
537
537
  function Basic() @{
538
- let &[value] = track('parent-class');
539
- let &[nested] = track('nested-class');
538
+ const value = track('parent-class');
539
+ const nested = track('nested-class');
540
540
  <>
541
- <p class={value}>{'Colored parent value'}</p>
541
+ <p class={value.value}>{'Colored parent value'}</p>
542
542
  <div>
543
- <p class={nested}>{'Colored nested value'}</p>
543
+ <p class={nested.value}>{'Colored nested value'}</p>
544
544
  </div>
545
545
  </>
546
546
  }
@@ -1,5 +1,6 @@
1
1
  import { track } from 'ripple';
2
2
  import type {
3
+ Derived,
3
4
  Tracked,
4
5
  PropsWithChildren,
5
6
  PropsWithExtras,
@@ -8,6 +9,61 @@ import type {
8
9
  } from 'ripple';
9
10
 
10
11
  describe('basic server > components & composition', () => {
12
+ it('renders a read-only view passed through readOnly()', async () => {
13
+ function Child({ count }: { count: Derived<number> }) @{
14
+ <span>{count.value}</span>
15
+ }
16
+
17
+ function App() @{
18
+ const count = track(5);
19
+ <div>
20
+ <Child count={count.readOnly()} />
21
+ </div>
22
+ }
23
+
24
+ const { body } = await render(App);
25
+
26
+ expect(body).toBeHtml('<div><span>5</span></div>');
27
+ });
28
+
29
+ it('renders rest and default pattern names in a keyed loop', async () => {
30
+ function App() @{
31
+ const rows = [
32
+ { id: 1, name: 'one', tags: ['a', 'b'] },
33
+ ];
34
+ <ul>
35
+ @for (const { id, ...rest } of rows; key id) {
36
+ <li>{rest.name}</li>
37
+ }
38
+ @for (const [head, second = 'none', ...others] of rows.map((row) => row.tags); key head) {
39
+ <li>{head + ':' + second + ':' + others.length}</li>
40
+ }
41
+ </ul>
42
+ }
43
+
44
+ const { body } = await render(App);
45
+
46
+ expect(body).toBeHtml('<ul><li>one</li><li>a:b:0</li></ul>');
47
+ });
48
+
49
+ it('renders nothing for a component prop that is undefined', async () => {
50
+ function Child({ Comp }: { Comp?: Component }) @{
51
+ <div>
52
+ {'before'}
53
+ <Comp />
54
+ {'after'}
55
+ </div>
56
+ }
57
+
58
+ function App() @{
59
+ <Child />
60
+ }
61
+
62
+ const { body } = await render(App);
63
+
64
+ expect(body).toBeHtml('<div>beforeafter</div>');
65
+ });
66
+
11
67
  async function render_expected_invalid_component(App: Component) {
12
68
  const console_error_spy = vi.spyOn(console, 'error').mockImplementation(() => {});
13
69
  try {
@@ -186,16 +242,16 @@ describe('basic server > components & composition', () => {
186
242
  }
187
243
 
188
244
  function Basic() @{
189
- let &[clicked] = track(false);
245
+ const clicked = track(false);
190
246
  <>
191
247
  <Card
192
248
  title="Test Card"
193
249
  content="This is a test card"
194
250
  buttonText="Click me"
195
- onAction={() => (clicked = true)}
251
+ onAction={() => (clicked.value = true)}
196
252
  />
197
253
  <div class="status">
198
- {clicked ? 'Clicked' : 'Not clicked'}
254
+ {clicked.value ? 'Clicked' : 'Not clicked'}
199
255
  </div>
200
256
  </>
201
257
  }
@@ -228,14 +284,14 @@ describe('basic server > components & composition', () => {
228
284
  }
229
285
 
230
286
  function Basic() @{
231
- let &[message, messageTracked] = track('Hello');
232
- let &[number, numberTracked] = track(1);
287
+ const message = track('Hello');
288
+ const number = track(1);
233
289
  <>
234
- <ChildComponent text={messageTracked} count={numberTracked} />
290
+ <ChildComponent text={message} count={number} />
235
291
  <button
236
292
  onClick={() => {
237
- message = message === 'Hello' ? 'Goodbye' : 'Hello';
238
- number++;
293
+ message.value = message.value === 'Hello' ? 'Goodbye' : 'Hello';
294
+ number.value++;
239
295
  }}
240
296
  >{'Update Props'}</button>
241
297
  </>
@@ -397,10 +397,10 @@ second
397
397
 
398
398
  it('renders tracked state updates', async () => {
399
399
  function Counter() @{
400
- let &[count] = track(0);
401
- count++;
402
- count = count + 5;
403
- <div>{count}</div>
400
+ const count = track(0);
401
+ count.value++;
402
+ count.value = count.value + 5;
403
+ <div>{count.value}</div>
404
404
  }
405
405
 
406
406
  const { body } = await render(Counter);
@@ -445,10 +445,12 @@ second
445
445
 
446
446
  it('renders spread props with tracked values', async () => {
447
447
  function SpreadProps() @{
448
- let &[id] = track('unique-id');
449
- let &[isActive] = track(true);
450
- let &[styles] = track({ color: 'red', fontSize: '16px' });
451
- <div {...{ id: id, class: { active: isActive }, style: styles }}>{'Spread Props'}</div>
448
+ const id = track('unique-id');
449
+ const isActive = track(true);
450
+ const styles = track({ color: 'red', fontSize: '16px' });
451
+ <div
452
+ {...{ id: id.value, class: { active: isActive.value }, style: styles.value }}
453
+ >{'Spread Props'}</div>
452
454
  }
453
455
 
454
456
  const { body } = await render(SpreadProps);
@@ -460,12 +462,12 @@ second
460
462
 
461
463
  it('handles AssignExpressions with tracked values or properties correctly', async () => {
462
464
  function Assignments() @{
463
- let &[count] = track(0);
465
+ const count = track(0);
464
466
  const obj = { value: track(5) };
465
- count += 10;
467
+ count.value += 10;
466
468
  obj.value.value *= 2;
467
469
  <>
468
- <div>{count}</div>
470
+ <div>{count.value}</div>
469
471
  <div>{obj.value.value}</div>
470
472
  </>
471
473
  }
@@ -477,12 +479,12 @@ second
477
479
 
478
480
  it(`handles derived changes via tracked dependencies' changes`, async () => {
479
481
  function Derived() @{
480
- let &[base] = track(5);
481
- let &[multiplier] = track(3);
482
- let &[derived] = track(() => base * multiplier);
483
- const before = derived;
484
- base += 2;
485
- const after = derived;
482
+ const base = track(5);
483
+ const multiplier = track(3);
484
+ const derived = track(() => base.value * multiplier.value);
485
+ const before = derived.value;
486
+ base.value += 2;
487
+ const after = derived.value;
486
488
  <>
487
489
  <div>{before}</div>
488
490
  <div>{after}</div>
@@ -496,13 +498,13 @@ second
496
498
 
497
499
  it(`handles derived changes based on another derived's dependencies' changes`, async () => {
498
500
  function NestedDerived() @{
499
- let &[a] = track(2);
500
- let &[b] = track(3);
501
- let &[sum] = track(() => a + b);
502
- let &[product] = track(() => sum * 2);
503
- const before = product;
504
- a = 4;
505
- const after = product;
501
+ const a = track(2);
502
+ const b = track(3);
503
+ const sum = track(() => a.value + b.value);
504
+ const product = track(() => sum.value * 2);
505
+ const before = product.value;
506
+ a.value = 4;
507
+ const after = product.value;
506
508
  <>
507
509
  <div>{before}</div>
508
510
  <div>{after}</div>
@@ -516,24 +518,24 @@ second
516
518
 
517
519
  it('handles lexical scopes correctly', async () => {
518
520
  function LexicalScopes() @{
519
- let &[x] = track(1);
521
+ const x = track(1);
520
522
  let nested = '';
521
523
  let deep = '';
522
524
  let deeper = '';
523
525
  {
524
- let &[x] = track(10);
525
- nested = String(x);
526
+ const x = track(10);
527
+ nested = String(x.value);
526
528
  }
527
529
  {
528
- let &[x] = track(12);
529
- deep = String(x);
530
+ const x = track(12);
531
+ deep = String(x.value);
530
532
  {
531
- let &[x] = track(15);
532
- deeper = String(x);
533
+ const x = track(15);
534
+ deeper = String(x.value);
533
535
  }
534
536
  }
535
537
  <>
536
- <div>{x}</div>
538
+ <div>{x.value}</div>
537
539
  <div>{nested}</div>
538
540
  <div>
539
541
  {deep}
@@ -47,52 +47,6 @@ beforeAll(() => {
47
47
  expect(code).not.toContain('!');
48
48
  });
49
49
 
50
- it(
51
- 'compiles writes to unknown lazy array index 1 through lazy array helpers in SSR output',
52
- () => {
53
- const source = `function Child({ pair: &[first, second] }) @{
54
- second = 10;
55
- <div>{first}</div>
56
- }
57
- function App() @{
58
- <Child pair={[0, 1]} />
59
- }`;
60
- const { code } = compile(source, 'test.tsrx', { mode: 'server' });
61
-
62
- expect(code).toContain('_$_.lazy_array_set(lazy, 10, 1)');
63
- expect(code).not.toContain('lazy[1] =');
64
- },
65
- );
66
-
67
- it('does not double-wrap member access on lazy array value bindings in SSR output', () => {
68
- const source = `function Child({ pair: &[first] }) @{
69
- let value = first[0];
70
- <div>{value}</div>
71
- }
72
- function App() @{
73
- <Child pair={[{ 0: 'x' }]} />
74
- }`;
75
- const { code } = compile(source, 'test.tsrx', { mode: 'server' });
76
-
77
- expect(code).toContain('let value = _$_.lazy_array_get(lazy, 0)[0];');
78
- expect(code).not.toContain('_$_.lazy_array_get(_$_.lazy_array_get(lazy, 0), 0)');
79
- });
80
-
81
- it('throws on indexed access through known tracked lazy destructures in SSR output', () => {
82
- const source = `import { track } from 'ripple';
83
- function App() @{
84
- let &[value, tracked_ref] = track({ 0: 'x' });
85
- let nested = value[0];
86
- tracked_ref[0] = { 0: 'y' };
87
- let next = value[0];
88
- <div>{nested}{next}</div>
89
- }`;
90
-
91
- expect(() => compile(source, 'test.tsrx', { mode: 'server' })).toThrow(
92
- /Use \.value or &\[\] lazy destructuring/,
93
- );
94
- });
95
-
96
50
  it('throws on known tracked indexed access in SSR output', () => {
97
51
  const source = `import { track } from 'ripple';
98
52
  function App() @{
@@ -106,33 +60,10 @@ function App() @{
106
60
  }`;
107
61
 
108
62
  expect(() => compile(source, 'test.tsrx', { mode: 'server' })).toThrow(
109
- /Use \.value or &\[\] lazy destructuring/,
63
+ /Accessing a tracked object directly is not allowed, use `\.value`/,
110
64
  );
111
65
  });
112
66
 
113
- it(
114
- 'compiles indexed access on unknown lazy tracked refs through lazy array helpers in SSR output',
115
- () => {
116
- const source = `import { track } from 'ripple';
117
- function Child({ pair: &[value, tracked_ref] }) @{
118
- ++tracked_ref[0];
119
- tracked_ref[0]++;
120
- <div>{value}</div>
121
- }
122
- function App() @{
123
- let tracked = track(0);
124
- <Child pair={tracked} />
125
- }`;
126
- const { code } = compile(source, 'test.tsrx', { mode: 'server' });
127
-
128
- expect(code).toContain('_$_.lazy_array_get(lazy, 1)');
129
- expect(code).toContain('_$_.lazy_array_update_pre(_$_.lazy_array_get(lazy, 1), 0)');
130
- expect(code).toContain('_$_.lazy_array_update(_$_.lazy_array_get(lazy, 1), 0)');
131
- expect(code).not.toContain('lazy[1][0]');
132
- expect(code).not.toContain('lazy_array_get(lazy, 1)[0]');
133
- },
134
- );
135
-
136
67
  it('removes class TypeScript syntax from JS output', () => {
137
68
  const source = `interface BaseEvent {}
138
69
 
@@ -8,10 +8,10 @@ describe('composite > props', () => {
8
8
  }
9
9
 
10
10
  function App() @{
11
- let &[foo] = track(123);
11
+ const foo = track(123);
12
12
  <>
13
13
  <Child />
14
- <Child {foo} />
14
+ <Child foo={foo.value} />
15
15
  </>
16
16
  }
17
17
 
@@ -70,10 +70,10 @@ describe('composite > props', () => {
70
70
  }
71
71
 
72
72
  function App() @{
73
- let &[foo] = track(123);
73
+ const foo = track(123);
74
74
  <>
75
75
  <Child />
76
- <Child {foo} />
76
+ <Child foo={foo.value} />
77
77
  </>
78
78
  }
79
79
 
@@ -85,7 +85,7 @@ describe('composite > props', () => {
85
85
  });
86
86
 
87
87
  it('correctly retains prop accessors and reactivity when using rest props', async () => {
88
- function Button(&{ children, ...rest }: Props) @{
88
+ function Button({ children, ...rest }: Props) @{
89
89
  <>
90
90
  <button {...rest}>{children}</button>
91
91
  <style>
@@ -99,7 +99,7 @@ describe('composite > props', () => {
99
99
  </>
100
100
  }
101
101
 
102
- function Toggle(&{ pressed, ...rest }: { pressed: Tracked<boolean> }) @{
102
+ function Toggle({ pressed, ...rest }: { pressed: Tracked<boolean> }) @{
103
103
  const onClick = () => (pressed.value = !pressed.value);
104
104
  <>
105
105
  <Button {...rest} class={pressed.value ? 'on' : 'off'} {onClick}>{'button 1'}</Button>