ripple 0.2.216 → 0.3.1

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 (155) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +16 -7
  3. package/src/compiler/errors.js +1 -1
  4. package/src/compiler/identifier-utils.js +2 -0
  5. package/src/compiler/index.d.ts +2 -6
  6. package/src/compiler/phases/1-parse/index.js +171 -233
  7. package/src/compiler/phases/2-analyze/index.js +192 -16
  8. package/src/compiler/phases/2-analyze/prune.js +2 -2
  9. package/src/compiler/phases/3-transform/client/index.js +308 -91
  10. package/src/compiler/phases/3-transform/segments.js +43 -15
  11. package/src/compiler/phases/3-transform/server/index.js +71 -21
  12. package/src/compiler/scope.js +31 -12
  13. package/src/compiler/source-map-utils.js +4 -6
  14. package/src/compiler/types/acorn.d.ts +11 -0
  15. package/src/compiler/types/estree-jsx.d.ts +11 -0
  16. package/src/compiler/types/estree.d.ts +11 -0
  17. package/src/compiler/types/import.d.ts +32 -18
  18. package/src/compiler/types/index.d.ts +75 -23
  19. package/src/compiler/types/parse.d.ts +7 -10
  20. package/src/compiler/utils.js +48 -0
  21. package/src/runtime/array.js +53 -22
  22. package/src/runtime/date.js +15 -5
  23. package/src/runtime/index-client.js +41 -7
  24. package/src/runtime/index-server.js +7 -7
  25. package/src/runtime/internal/client/bindings.js +2 -2
  26. package/src/runtime/internal/client/blocks.js +40 -1
  27. package/src/runtime/internal/client/context.js +8 -0
  28. package/src/runtime/internal/client/for.js +3 -3
  29. package/src/runtime/internal/client/index.js +27 -5
  30. package/src/runtime/internal/client/render.js +20 -8
  31. package/src/runtime/internal/client/runtime.js +9 -7
  32. package/src/runtime/internal/client/try.js +15 -22
  33. package/src/runtime/internal/client/utils.js +1 -1
  34. package/src/runtime/internal/server/context.js +8 -0
  35. package/src/runtime/internal/server/index.js +99 -6
  36. package/src/runtime/map.js +7 -7
  37. package/src/runtime/media-query.js +10 -1
  38. package/src/runtime/object.js +6 -6
  39. package/src/runtime/proxy.js +6 -6
  40. package/src/runtime/set.js +11 -11
  41. package/src/runtime/url-search-params.js +13 -2
  42. package/src/runtime/url.js +15 -5
  43. package/src/utils/builders.js +13 -3
  44. package/tests/client/array/array.copy-within.test.ripple +11 -11
  45. package/tests/client/array/array.derived.test.ripple +42 -42
  46. package/tests/client/array/array.iteration.test.ripple +12 -12
  47. package/tests/client/array/array.mutations.test.ripple +25 -25
  48. package/tests/client/array/array.static.test.ripple +103 -106
  49. package/tests/client/array/array.to-methods.test.ripple +8 -8
  50. package/tests/client/async-suspend.test.ripple +94 -0
  51. package/tests/client/basic/basic.attributes.test.ripple +31 -31
  52. package/tests/client/basic/basic.collections.test.ripple +7 -7
  53. package/tests/client/basic/basic.components.test.ripple +48 -10
  54. package/tests/client/basic/basic.errors.test.ripple +46 -31
  55. package/tests/client/basic/basic.events.test.ripple +11 -11
  56. package/tests/client/basic/basic.get-set.test.ripple +18 -18
  57. package/tests/client/basic/basic.reactivity.test.ripple +47 -42
  58. package/tests/client/basic/basic.rendering.test.ripple +7 -7
  59. package/tests/client/basic/basic.utilities.test.ripple +4 -4
  60. package/tests/client/boundaries.test.ripple +7 -7
  61. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +2 -2
  62. package/tests/client/compiler/compiler.assignments.test.ripple +21 -21
  63. package/tests/client/compiler/compiler.basic.test.ripple +223 -82
  64. package/tests/client/compiler/compiler.tracked-access.test.ripple +8 -9
  65. package/tests/client/composite/composite.dynamic-components.test.ripple +8 -8
  66. package/tests/client/composite/composite.generics.test.ripple +4 -4
  67. package/tests/client/composite/composite.props.test.ripple +9 -9
  68. package/tests/client/composite/composite.reactivity.test.ripple +32 -26
  69. package/tests/client/composite/composite.render.test.ripple +13 -4
  70. package/tests/client/computed-properties.test.ripple +3 -3
  71. package/tests/client/context.test.ripple +3 -3
  72. package/tests/client/css/global-additional-cases.test.ripple +4 -4
  73. package/tests/client/css/style-identifier.test.ripple +49 -41
  74. package/tests/client/date.test.ripple +40 -40
  75. package/tests/client/dynamic-elements.test.ripple +165 -30
  76. package/tests/client/events.test.ripple +25 -25
  77. package/tests/client/for.test.ripple +76 -8
  78. package/tests/client/function-overload.test.ripple +0 -1
  79. package/tests/client/head.test.ripple +7 -7
  80. package/tests/client/html.test.ripple +2 -2
  81. package/tests/client/input-value.test.ripple +174 -176
  82. package/tests/client/map.test.ripple +21 -21
  83. package/tests/client/media-query.test.ripple +4 -4
  84. package/tests/client/object.test.ripple +12 -12
  85. package/tests/client/portal.test.ripple +4 -4
  86. package/tests/client/ref.test.ripple +5 -5
  87. package/tests/client/return.test.ripple +17 -17
  88. package/tests/client/set.test.ripple +16 -16
  89. package/tests/client/svg.test.ripple +6 -7
  90. package/tests/client/switch.test.ripple +10 -10
  91. package/tests/client/tracked-expression.test.ripple +1 -3
  92. package/tests/client/try.test.ripple +33 -4
  93. package/tests/client/url/url.derived.test.ripple +10 -9
  94. package/tests/client/url/url.parsing.test.ripple +10 -10
  95. package/tests/client/url/url.partial-removal.test.ripple +10 -10
  96. package/tests/client/url/url.reactivity.test.ripple +17 -17
  97. package/tests/client/url/url.serialization.test.ripple +4 -4
  98. package/tests/client/url-search-params/url-search-params.derived.test.ripple +11 -10
  99. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +5 -7
  100. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +13 -13
  101. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +19 -19
  102. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +17 -17
  103. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +5 -5
  104. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +5 -5
  105. package/tests/hydration/compiled/client/events.js +8 -11
  106. package/tests/hydration/compiled/client/for.js +20 -23
  107. package/tests/hydration/compiled/client/head.js +17 -19
  108. package/tests/hydration/compiled/client/hmr.js +1 -3
  109. package/tests/hydration/compiled/client/html.js +1 -15
  110. package/tests/hydration/compiled/client/if-children.js +7 -9
  111. package/tests/hydration/compiled/client/if.js +5 -7
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +3 -5
  113. package/tests/hydration/compiled/client/portal.js +1 -1
  114. package/tests/hydration/compiled/client/reactivity.js +9 -11
  115. package/tests/hydration/compiled/client/return.js +11 -13
  116. package/tests/hydration/compiled/client/switch.js +4 -6
  117. package/tests/hydration/compiled/server/basic.js +0 -1
  118. package/tests/hydration/compiled/server/composite.js +0 -3
  119. package/tests/hydration/compiled/server/events.js +8 -12
  120. package/tests/hydration/compiled/server/for.js +20 -23
  121. package/tests/hydration/compiled/server/head.js +17 -19
  122. package/tests/hydration/compiled/server/hmr.js +1 -4
  123. package/tests/hydration/compiled/server/html.js +1 -35
  124. package/tests/hydration/compiled/server/if-children.js +7 -11
  125. package/tests/hydration/compiled/server/if.js +5 -7
  126. package/tests/hydration/compiled/server/mixed-control-flow.js +3 -5
  127. package/tests/hydration/compiled/server/portal.js +1 -9
  128. package/tests/hydration/compiled/server/reactivity.js +9 -11
  129. package/tests/hydration/compiled/server/return.js +11 -13
  130. package/tests/hydration/compiled/server/switch.js +4 -6
  131. package/tests/hydration/components/events.ripple +8 -9
  132. package/tests/hydration/components/for.ripple +20 -21
  133. package/tests/hydration/components/head.ripple +6 -8
  134. package/tests/hydration/components/hmr.ripple +1 -2
  135. package/tests/hydration/components/html.ripple +1 -3
  136. package/tests/hydration/components/if-children.ripple +7 -8
  137. package/tests/hydration/components/if.ripple +5 -6
  138. package/tests/hydration/components/mixed-control-flow.ripple +4 -6
  139. package/tests/hydration/components/portal.ripple +1 -1
  140. package/tests/hydration/components/reactivity.ripple +9 -10
  141. package/tests/hydration/components/return.ripple +11 -12
  142. package/tests/hydration/components/switch.ripple +6 -8
  143. package/tests/server/await.test.ripple +2 -2
  144. package/tests/server/basic.attributes.test.ripple +19 -21
  145. package/tests/server/basic.components.test.ripple +13 -7
  146. package/tests/server/basic.test.ripple +20 -21
  147. package/tests/server/compiler.test.ripple +5 -5
  148. package/tests/server/composite.props.test.ripple +6 -7
  149. package/tests/server/composite.test.ripple +4 -4
  150. package/tests/server/context.test.ripple +1 -3
  151. package/tests/server/dynamic-elements.test.ripple +24 -24
  152. package/tests/server/head.test.ripple +5 -7
  153. package/tests/server/style-identifier.test.ripple +16 -17
  154. package/types/index.d.ts +266 -62
  155. package/types/server.d.ts +6 -6
@@ -1,11 +1,11 @@
1
- import { flushSync, TrackedArray } from 'ripple';
1
+ import { flushSync, RippleArray } from 'ripple';
2
2
  import { MAX_ARRAY_LENGTH } from '../../../src/runtime/internal/client/constants.js';
3
3
 
4
- describe('TrackedArray > static', () => {
4
+ describe('RippleArray > static', () => {
5
5
  it('handles static methods - from and of', () => {
6
6
  component ArrayTest() {
7
- let itemsFrom = TrackedArray.from([1, 2, 3], (x: number) => x * 2);
8
- let itemsOf = TrackedArray.of(4, 5, 6);
7
+ let itemsFrom = RippleArray.from([1, 2, 3], (x: number) => x * 2);
8
+ let itemsOf = RippleArray.of(4, 5, 6);
9
9
 
10
10
  <button onClick={() => itemsFrom.push(8)}>{'add to from'}</button>
11
11
  <button onClick={() => itemsOf.push(7)}>{'add to of'}</button>
@@ -35,49 +35,87 @@ describe('TrackedArray > static', () => {
35
35
  expect(container.querySelectorAll('pre')[1].textContent).toBe('[4,5,6,7]');
36
36
  });
37
37
 
38
- ('fromAsync' in Array.prototype ? describe : describe.skip)(
39
- 'TrackedArray fromAsync',
40
- async () => {
41
- it('handles static fromAsync method with reactivity', async () => {
42
- component Parent() {
43
- try {
44
- <ArrayTest />
45
- } pending {
46
- <div>{'Loading placeholder...'}</div>
47
- }
38
+ ('fromAsync' in Array.prototype ? describe : describe.skip)('RippleArray fromAsync', async () => {
39
+ it('handles static fromAsync method with reactivity', async () => {
40
+ component Parent() {
41
+ try {
42
+ <ArrayTest />
43
+ } pending {
44
+ <div>{'Loading placeholder...'}</div>
48
45
  }
46
+ }
49
47
 
50
- component ArrayTest() {
51
- let items = await TrackedArray.fromAsync([1, 2, 3]);
48
+ component ArrayTest() {
49
+ let items = await RippleArray.fromAsync([1, 2, 3]);
50
+
51
+ <button
52
+ onClick={() => {
53
+ if (items) items.push(4);
54
+ }}
55
+ >
56
+ {'add item'}
57
+ </button>
58
+
59
+ <pre>{JSON.stringify(items)}</pre>
60
+ }
61
+
62
+ render(Parent);
52
63
 
53
- <button
54
- onClick={() => {
55
- if (items) items.push(4);
56
- }}
57
- >
58
- {'add item'}
59
- </button>
64
+ await new Promise((resolve) => setTimeout(resolve, 0));
65
+ flushSync();
60
66
 
61
- <pre>{JSON.stringify(items)}</pre>
67
+ const addButton = container.querySelector('button');
68
+
69
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
70
+
71
+ // Test adding an item to the async-created array
72
+ addButton.click();
73
+ flushSync();
74
+
75
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4]');
76
+ });
77
+
78
+ it('handles static fromAsync method with mapping function', async () => {
79
+ component Parent() {
80
+ try {
81
+ <ArrayTest />
82
+ } pending {
83
+ <div>{'Loading placeholder...'}</div>
62
84
  }
85
+ }
63
86
 
64
- render(Parent);
87
+ component ArrayTest() {
88
+ let items = await RippleArray.fromAsync([1, 2, 3], (x: number) => x * 2);
89
+
90
+ <button
91
+ onClick={() => {
92
+ if (items) items.push(8);
93
+ }}
94
+ >
95
+ {'add item'}
96
+ </button>
97
+ <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
98
+ }
65
99
 
66
- await new Promise((resolve) => setTimeout(resolve, 0));
67
- flushSync();
100
+ render(Parent);
68
101
 
69
- const addButton = container.querySelector('button');
102
+ await new Promise((resolve) => setTimeout(resolve, 0));
103
+ flushSync();
70
104
 
71
- expect(container.querySelectorAll('pre')[0].textContent).toBe('[1,2,3]');
105
+ const addButton = container.querySelector('button');
72
106
 
73
- // Test adding an item to the async-created array
74
- addButton.click();
75
- flushSync();
107
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
108
+
109
+ addButton.click();
110
+ flushSync();
76
111
 
77
- expect(container.querySelectorAll('pre')[1].textContent).toBe('[1,2,3,4]');
78
- });
112
+ expect(container.querySelector('pre').textContent).toBe('[2,4,6,8]');
113
+ });
79
114
 
80
- it('handles static fromAsync method with mapping function', async () => {
115
+ // TODO: Fix this test case, needs some async love around try statements being using in a not template way
116
+ ('fromAsync' in Array.prototype ? it : it.skip)(
117
+ 'handles error in fromAsync method',
118
+ async () => {
81
119
  component Parent() {
82
120
  try {
83
121
  <ArrayTest />
@@ -87,84 +125,43 @@ describe('TrackedArray > static', () => {
87
125
  }
88
126
 
89
127
  component ArrayTest() {
90
- let items = await TrackedArray.fromAsync([1, 2, 3], (x: number) => x * 2);
91
-
92
- <button
93
- onClick={() => {
94
- if (items) items.push(8);
95
- }}
96
- >
97
- {'add item'}
98
- </button>
99
- <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
100
- }
101
-
102
- render(Parent);
128
+ let items: RippleArray<string> | null = null;
129
+ let error: string | null = null;
103
130
 
104
- await new Promise((resolve) => setTimeout(resolve, 0));
105
- flushSync();
106
-
107
- const addButton = container.querySelector('button');
108
-
109
- expect(container.querySelector('pre').textContent).toBe('[2,4,6]');
110
-
111
- addButton.click();
112
- flushSync();
113
-
114
- expect(container.querySelector('pre').textContent).toBe('[2,4,6,8]');
115
- });
116
-
117
- // TODO: Fix this test case, needs some async love around try statements being using in a not template way
118
- ('fromAsync' in Array.prototype ? it : it.skip)(
119
- 'handles error in fromAsync method',
120
- async () => {
121
- component Parent() {
122
- try {
123
- <ArrayTest />
124
- } pending {
125
- <div>{'Loading placeholder...'}</div>
126
- }
131
+ async function* throwingIterable() {
132
+ throw new Error('Async error');
127
133
  }
128
134
 
129
- component ArrayTest() {
130
- let items: TrackedArray<string> | null = null;
131
- let error: string | null = null;
132
-
133
- async function* throwingIterable() {
134
- throw new Error('Async error');
135
- }
136
-
137
- try {
138
- items = await TrackedArray.fromAsync(throwingIterable());
139
- for (const item of items) {
140
- <li>{item}</li>
141
- }
142
- } pending {
143
- <div>{'Loading...'}</div>
144
- } catch (e) {
145
- error = (e as Error).message;
135
+ try {
136
+ items = await RippleArray.fromAsync(throwingIterable());
137
+ for (const item of items) {
138
+ <li>{item}</li>
146
139
  }
147
-
148
- <pre>{error ? 'Error: ' + error : 'No error'}</pre>
149
- <pre>{items ? JSON.stringify(items) : 'No items'}</pre>
140
+ } pending {
141
+ <div>{'Loading...'}</div>
142
+ } catch (e) {
143
+ error = (e as Error).message;
150
144
  }
151
145
 
152
- render(Parent);
146
+ <pre>{error ? 'Error: ' + error : 'No error'}</pre>
147
+ <pre>{items ? JSON.stringify(items) : 'No items'}</pre>
148
+ }
149
+
150
+ render(Parent);
153
151
 
154
- await new Promise((resolve) => setTimeout(resolve, 0));
155
- flushSync();
152
+ await new Promise((resolve) => setTimeout(resolve, 0));
153
+ flushSync();
156
154
 
157
- expect(container.querySelectorAll('pre')[0].textContent).toBe('Error: Async error');
158
- expect(container.querySelectorAll('pre')[1].textContent).toBe('No items');
159
- },
160
- );
161
- },
162
- );
155
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('Error: Async error');
156
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('No items');
157
+ },
158
+ );
159
+ });
163
160
 
164
- describe('Creates TrackedArray with a single element', () => {
161
+ describe('Creates RippleArray with a single element', () => {
165
162
  it('specifies int', () => {
166
163
  component ArrayTest() {
167
- let items = new TrackedArray(3);
164
+ let items = new RippleArray(3);
168
165
  <pre>{JSON.stringify(items)}</pre>
169
166
  <pre>{items.length}</pre>
170
167
  }
@@ -180,7 +177,7 @@ describe('TrackedArray > static', () => {
180
177
  let error = null;
181
178
 
182
179
  try {
183
- new TrackedArray(MAX_ARRAY_LENGTH + 1);
180
+ new RippleArray(MAX_ARRAY_LENGTH + 1);
184
181
  } catch (e) {
185
182
  error = (e as Error).message;
186
183
  }
@@ -195,7 +192,7 @@ describe('TrackedArray > static', () => {
195
192
 
196
193
  it('specifies int using static from method', () => {
197
194
  component ArrayTest() {
198
- let items = TrackedArray.from([4]);
195
+ let items = RippleArray.from([4]);
199
196
  <pre>{JSON.stringify(items)}</pre>
200
197
  <pre>{items.length}</pre>
201
198
  }
@@ -208,7 +205,7 @@ describe('TrackedArray > static', () => {
208
205
 
209
206
  it('specifies int using static of method', () => {
210
207
  component ArrayTest() {
211
- let items = TrackedArray.of(5);
208
+ let items = RippleArray.of(5);
212
209
  <pre>{JSON.stringify(items)}</pre>
213
210
  <pre>{items.length}</pre>
214
211
  }
@@ -231,7 +228,7 @@ describe('TrackedArray > static', () => {
231
228
  }
232
229
 
233
230
  component ArrayTest() {
234
- const items = await TrackedArray.fromAsync([6]);
231
+ const items = await RippleArray.fromAsync([6]);
235
232
 
236
233
  <pre>{items ? JSON.stringify(items) : 'Loading...'}</pre>
237
234
  <pre>{items ? items.length : ''}</pre>
@@ -1,14 +1,14 @@
1
- import { track, flushSync, TrackedArray } from 'ripple';
1
+ import { flushSync, RippleArray } from 'ripple';
2
2
 
3
- describe('TrackedArray > to* methods', () => {
3
+ describe('RippleArray > to* methods', () => {
4
4
  it('handles toReversed method with reactivity', (context) => {
5
5
  if (!('toReversed' in Array.prototype)) {
6
6
  context.skip();
7
7
  }
8
8
 
9
9
  component ArrayTest() {
10
- let items = new TrackedArray(1, 2, 3, 4);
11
- let reversed = track(() => items.toReversed());
10
+ let items = new RippleArray(1, 2, 3, 4);
11
+ let reversed = #ripple.track(() => items.toReversed());
12
12
 
13
13
  <button onClick={() => items.push(5)}>{'add item'}</button>
14
14
  <pre>{JSON.stringify(items)}</pre>
@@ -37,8 +37,8 @@ describe('TrackedArray > to* methods', () => {
37
37
  }
38
38
 
39
39
  component ArrayTest() {
40
- let items = new TrackedArray(3, 1, 4, 2);
41
- let sorted = track(() => items.toSorted());
40
+ let items = new RippleArray(3, 1, 4, 2);
41
+ let sorted = #ripple.track(() => items.toSorted());
42
42
 
43
43
  <button onClick={() => items.push(0)}>{'add item'}</button>
44
44
  <pre>{JSON.stringify(items)}</pre>
@@ -67,8 +67,8 @@ describe('TrackedArray > to* methods', () => {
67
67
  }
68
68
 
69
69
  component ArrayTest() {
70
- let items = new TrackedArray<string | number>(1, 2, 3, 4, 5);
71
- let spliced = track(() => items.toSpliced(1, 2, 'a', 'b'));
70
+ let items = new RippleArray<string | number>(1, 2, 3, 4, 5);
71
+ let spliced = #ripple.track(() => items.toSpliced(1, 2, 'a', 'b'));
72
72
 
73
73
  <button onClick={() => (items[2] = 30)}>{'change item'}</button>
74
74
  <pre>{JSON.stringify(items)}</pre>
@@ -0,0 +1,94 @@
1
+ import { flushSync } from 'ripple';
2
+
3
+ describe('async suspense', () => {
4
+ it('hides child content during re-suspension when tracked dependency changes', async () => {
5
+ let resolve_fn: (() => void) | null = null;
6
+
7
+ component Child({ count }: { count: any }) {
8
+ await #ripple.track(() => {
9
+ @count;
10
+ return new Promise<void>((resolve) => {
11
+ resolve_fn = resolve;
12
+ });
13
+ });
14
+
15
+ <div class="child-content">{'child content'}</div>
16
+ }
17
+
18
+ component App() {
19
+ let count = #ripple.track(0);
20
+
21
+ try {
22
+ <Child {count} />
23
+ } pending {
24
+ <div class="pending">{'pending...'}</div>
25
+ }
26
+
27
+ <button onClick={() => @count++}>{'Increment'}</button>
28
+ }
29
+
30
+ render(App);
31
+
32
+ // Initial state: should show pending
33
+ await new Promise((resolve) => setTimeout(resolve, 0));
34
+ flushSync();
35
+ expect(container.innerHTML).toContain('pending...');
36
+ expect(container.innerHTML).not.toContain('child content');
37
+
38
+ // Resolve the first promise
39
+ (resolve_fn as () => void)?.();
40
+ await new Promise((resolve) => setTimeout(resolve, 0));
41
+ flushSync();
42
+
43
+ // After resolution: should show child content, not pending
44
+ expect(container.innerHTML).toContain('child content');
45
+ expect(container.innerHTML).not.toContain('pending...');
46
+
47
+ // Now trigger re-suspension by changing count
48
+ const button = container.querySelector('button');
49
+ button?.click();
50
+ flushSync();
51
+
52
+ // Wait for microtask to process
53
+ await new Promise((resolve) => setTimeout(resolve, 0));
54
+ flushSync();
55
+
56
+ // BUG: During re-suspension, we should only see pending, not child content
57
+ // The child content should be hidden when the new promise is pending
58
+ expect(container.innerHTML).toContain('pending...');
59
+ expect(container.innerHTML).not.toContain('child content');
60
+ });
61
+
62
+ it('shows pending UI immediately when child suspends', async () => {
63
+ component Child() {
64
+ await new Promise((resolve) => setTimeout(resolve, 50));
65
+ <div class="child">{'loaded'}</div>
66
+ }
67
+
68
+ component App() {
69
+ try {
70
+ <Child />
71
+ } pending {
72
+ <div class="pending">{'loading...'}</div>
73
+ }
74
+ }
75
+
76
+ render(App);
77
+
78
+ // Wait for microtask
79
+ await new Promise((resolve) => setTimeout(resolve, 0));
80
+ flushSync();
81
+
82
+ // Should show pending, not child content
83
+ expect(container.innerHTML).toContain('loading...');
84
+ expect(container.innerHTML).not.toContain('loaded');
85
+
86
+ // Wait for child to resolve
87
+ await new Promise((resolve) => setTimeout(resolve, 100));
88
+ flushSync();
89
+
90
+ // Should show child content, not pending
91
+ expect(container.innerHTML).toContain('loaded');
92
+ expect(container.innerHTML).not.toContain('loading...');
93
+ });
94
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { track, flushSync, type TrackedArray } from 'ripple';
2
+ import { flushSync, type RippleArray } from 'ripple';
3
3
 
4
4
  describe('basic client > attribute rendering', () => {
5
5
  it('render static attributes', () => {
@@ -14,7 +14,7 @@ describe('basic client > attribute rendering', () => {
14
14
 
15
15
  it('render dynamic class attribute', () => {
16
16
  component Basic() {
17
- let active = track(false);
17
+ let active = #ripple.track(false);
18
18
 
19
19
  <button
20
20
  onClick={() => {
@@ -98,7 +98,7 @@ describe('basic client > attribute rendering', () => {
98
98
 
99
99
  it('render dynamic class object', () => {
100
100
  component Basic() {
101
- let active = track(false);
101
+ let active = #ripple.track(false);
102
102
 
103
103
  <button
104
104
  onClick={() => {
@@ -141,7 +141,7 @@ describe('basic client > attribute rendering', () => {
141
141
 
142
142
  it('applies scoped ripple class to multiple elements with dynamic class expressions', () => {
143
143
  component Basic() {
144
- let selected = track(1);
144
+ let selected = #ripple.track(1);
145
145
 
146
146
  <div class={@selected === 0 ? 'selected' : ''}>{`div 1`}</div>
147
147
  <div class={@selected === 0 ? 'selected' : ''}>{`div 2`}</div>
@@ -170,7 +170,7 @@ describe('basic client > attribute rendering', () => {
170
170
 
171
171
  it('render dynamic id attribute', () => {
172
172
  component Basic() {
173
- let count = track(0);
173
+ let count = #ripple.track(0);
174
174
 
175
175
  <button
176
176
  onClick={() => {
@@ -202,7 +202,7 @@ describe('basic client > attribute rendering', () => {
202
202
 
203
203
  it('render dynamic style attribute', () => {
204
204
  component Basic() {
205
- let color = track('red');
205
+ let color = #ripple.track('red');
206
206
 
207
207
  <button
208
208
  onClick={() => {
@@ -231,7 +231,7 @@ describe('basic client > attribute rendering', () => {
231
231
 
232
232
  it('render style attribute as dynamic object', () => {
233
233
  component Basic() {
234
- let color = track('red');
234
+ let color = #ripple.track('red');
235
235
 
236
236
  <button
237
237
  onClick={() => {
@@ -260,7 +260,7 @@ describe('basic client > attribute rendering', () => {
260
260
 
261
261
  it('render tracked variable as style attribute', () => {
262
262
  component Basic() {
263
- let style = track({ color: 'red', fontWeight: 'bold' });
263
+ let style = #ripple.track({ color: 'red', fontWeight: 'bold' });
264
264
 
265
265
  function toggleColor() {
266
266
  @style = { ...@style, color: @style.color === 'red' ? 'blue' : 'red' };
@@ -287,7 +287,7 @@ describe('basic client > attribute rendering', () => {
287
287
 
288
288
  it('render tracked object as style attribute', () => {
289
289
  component Basic() {
290
- let style = #{ color: 'red', fontWeight: 'bold' };
290
+ let style = #ripple{ color: 'red', fontWeight: 'bold' };
291
291
 
292
292
  function toggleColor() {
293
293
  style.color = style.color === 'red' ? 'blue' : 'red';
@@ -360,8 +360,8 @@ describe('basic client > attribute rendering', () => {
360
360
 
361
361
  it('render dynamic boolean attributes', () => {
362
362
  component Basic() {
363
- let disabled = track(false);
364
- let checked = track(false);
363
+ let disabled = #ripple.track(false);
364
+ let checked = #ripple.track(false);
365
365
 
366
366
  <button
367
367
  onClick={() => {
@@ -391,8 +391,8 @@ describe('basic client > attribute rendering', () => {
391
391
 
392
392
  it('render multiple dynamic attributes', () => {
393
393
  component Basic() {
394
- let theme = track('light');
395
- let size = track('medium');
394
+ let theme = #ripple.track('light');
395
+ let size = #ripple.track('medium');
396
396
 
397
397
  <button
398
398
  onClick={() => {
@@ -426,8 +426,8 @@ describe('basic client > attribute rendering', () => {
426
426
 
427
427
  it('render conditional attributes', () => {
428
428
  component Basic() {
429
- let showTitle = track(false);
430
- let showAria = track(false);
429
+ let showTitle = #ripple.track(false);
430
+ let showAria = #ripple.track(false);
431
431
 
432
432
  <button
433
433
  onClick={() => {
@@ -468,7 +468,7 @@ describe('basic client > attribute rendering', () => {
468
468
 
469
469
  it('render spread attributes', () => {
470
470
  component Basic() {
471
- let attrs = track<TestAttributes>(
471
+ let attrs = #ripple.track<TestAttributes>(
472
472
  {
473
473
  class: 'initial',
474
474
  id: 'test-1',
@@ -508,12 +508,12 @@ describe('basic client > attribute rendering', () => {
508
508
 
509
509
  it('renders with reactive attributes with nested reactive attributes', () => {
510
510
  component App() {
511
- let value = track('parent-class');
511
+ let value = #ripple.track('parent-class');
512
512
 
513
513
  <p class={@value}>{'Colored parent value'}</p>
514
514
 
515
515
  <div>
516
- let nested = track('nested-class');
516
+ let nested = #ripple.track('nested-class');
517
517
 
518
518
  <p class={@nested}>{'Colored nested value'}</p>
519
519
  </div>
@@ -556,8 +556,8 @@ describe('basic client > attribute rendering', () => {
556
556
 
557
557
  it('handles reactive event handler changes', () => {
558
558
  component Basic() {
559
- let count = track(0);
560
- let mode = track<'increment' | 'decrement'>('increment');
559
+ let count = #ripple.track(0);
560
+ let mode = #ripple.track<'increment' | 'decrement'>('increment');
561
561
 
562
562
  const incrementHandler = () => {
563
563
  @count++;
@@ -613,7 +613,7 @@ describe('basic client > attribute rendering', () => {
613
613
 
614
614
  it('handles events with capture option', () => {
615
615
  component Basic() {
616
- let captureOrder: TrackedArray<string> = #[];
616
+ let captureOrder: RippleArray<string> = #ripple[];
617
617
 
618
618
  const handleCaptureClick = {
619
619
  handleEvent() {
@@ -647,7 +647,7 @@ describe('basic client > attribute rendering', () => {
647
647
 
648
648
  it('handles events with Capture suffix in the name', () => {
649
649
  component Basic() {
650
- let captureOrder: TrackedArray<string> = #[];
650
+ let captureOrder: RippleArray<string> = #ripple[];
651
651
 
652
652
  const handleCaptureClick = () => {
653
653
  captureOrder.push('capture');
@@ -678,7 +678,7 @@ describe('basic client > attribute rendering', () => {
678
678
 
679
679
  it('handles custom events with customName option', () => {
680
680
  component Basic() {
681
- let customEventCount = track(0);
681
+ let customEventCount = #ripple.track(0);
682
682
 
683
683
  const handleCustom = {
684
684
  handleEvent(event: CustomEvent) {
@@ -713,8 +713,8 @@ describe('basic client > attribute rendering', () => {
713
713
 
714
714
  it('handles events with delegated: false option to bypass delegation', () => {
715
715
  component Basic() {
716
- let delegatedCount = track(0);
717
- let nonDelegatedCount = track(0);
716
+ let delegatedCount = #ripple.track(0);
717
+ let nonDelegatedCount = #ripple.track(0);
718
718
 
719
719
  const delegatedHandler = () => {
720
720
  @delegatedCount++;
@@ -827,8 +827,8 @@ describe('basic client > attribute rendering', () => {
827
827
 
828
828
  it('handles events defined as function directly vs as object', () => {
829
829
  component Basic() {
830
- let functionCount = track(0);
831
- let objectCount = track(0);
830
+ let functionCount = #ripple.track(0);
831
+ let objectCount = #ripple.track(0);
832
832
 
833
833
  const functionHandler = () => {
834
834
  @functionCount++;
@@ -880,8 +880,8 @@ describe('basic client > attribute rendering', () => {
880
880
 
881
881
  it('handles passive event option', () => {
882
882
  component Basic() {
883
- let passiveDefaultPrevented = track<boolean | null>(null);
884
- let nonPassiveDefaultPrevented = track<boolean | null>(null);
883
+ let passiveDefaultPrevented = #ripple.track<boolean | null>(null);
884
+ let nonPassiveDefaultPrevented = #ripple.track<boolean | null>(null);
885
885
 
886
886
  const passiveHandler = {
887
887
  handleEvent(event: Event) {
@@ -945,8 +945,8 @@ describe('basic client > attribute rendering', () => {
945
945
 
946
946
  it('handles once option to fire event only once', () => {
947
947
  component Basic() {
948
- let onceCount = track(0);
949
- let regularCount = track(0);
948
+ let onceCount = #ripple.track(0);
949
+ let regularCount = #ripple.track(0);
950
950
 
951
951
  const onceHandler = {
952
952
  handleEvent() {
@@ -1,10 +1,10 @@
1
- import { track, flushSync } from 'ripple';
1
+ import { flushSync } from 'ripple';
2
2
  import { TRACKED_ARRAY } from '../../../src/runtime/internal/client/constants.js';
3
3
 
4
4
  describe('basic client > collections', () => {
5
5
  it('renders with simple reactive objects', () => {
6
6
  component Basic() {
7
- let user = track(
7
+ let user = #ripple.track(
8
8
  {
9
9
  name: 'John',
10
10
  age: 25,
@@ -40,10 +40,10 @@ describe('basic client > collections', () => {
40
40
 
41
41
  it('renders with nested reactive objects', () => {
42
42
  component Basic() {
43
- let user = track(
43
+ let user = #ripple.track(
44
44
  {
45
- name: track('John'),
46
- age: track(25),
45
+ name: #ripple.track('John'),
46
+ age: #ripple.track(25),
47
47
  },
48
48
  );
49
49
 
@@ -75,9 +75,9 @@ describe('basic client > collections', () => {
75
75
  expect(ageDiv.textContent).toBe('30');
76
76
  });
77
77
 
78
- it('works as a reactive TrackedArray when constructed using # syntactic sugar', () => {
78
+ it('works as a reactive RippleArray when constructed using # syntactic sugar', () => {
79
79
  component App() {
80
- const array = #[1, 2, 3];
80
+ const array = #ripple[1, 2, 3];
81
81
 
82
82
  <pre>{String(array[3])}</pre>
83
83
  <pre>{array[0]}</pre>