ripple 0.2.124 → 0.2.126

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.
@@ -24,6 +24,7 @@ import {
24
24
  TRY_BLOCK,
25
25
  UNINITIALIZED,
26
26
  REF_PROP,
27
+ TRACKED_OBJECT,
27
28
  } from './constants.js';
28
29
  import { capture, suspend } from './try.js';
29
30
  import {
@@ -412,7 +413,7 @@ function is_tracking_dirty(tracking) {
412
413
  var tracked = tracking.t;
413
414
 
414
415
  if ((tracked.f & DERIVED) !== 0) {
415
- update_derived(/** @type {Derived} **/ (tracked));
416
+ update_derived(/** @type {Derived} **/(tracked));
416
417
  }
417
418
 
418
419
  if (tracked.c > tracking.c) {
@@ -472,7 +473,7 @@ export function async_computed(fn, block) {
472
473
  }
473
474
 
474
475
  promise.then((v) => {
475
- if (parent && is_destroyed(/** @type {Block} */ (parent))) {
476
+ if (parent && is_destroyed(/** @type {Block} */(parent))) {
476
477
  return;
477
478
  }
478
479
  if (promise === current && t.v !== v) {
@@ -746,7 +747,7 @@ export function get(tracked) {
746
747
  }
747
748
 
748
749
  return (tracked.f & DERIVED) !== 0
749
- ? get_derived(/** @type {Derived} */ (tracked))
750
+ ? get_derived(/** @type {Derived} */(tracked))
750
751
  : get_tracked(tracked);
751
752
  }
752
753
 
@@ -872,7 +873,7 @@ export function flush_sync(fn) {
872
873
  * @returns {Object}
873
874
  */
874
875
  export function spread_props(fn, block) {
875
- let computed = derived(fn, block);
876
+ var computed = derived(fn, block);
876
877
 
877
878
  return new Proxy(
878
879
  {},
@@ -882,9 +883,23 @@ export function spread_props(fn, block) {
882
883
  return obj[property];
883
884
  },
884
885
  has(target, property) {
886
+ if (property === TRACKED_OBJECT) {
887
+ return true;
888
+ }
885
889
  const obj = get_derived(computed);
886
890
  return property in obj;
887
891
  },
892
+ getOwnPropertyDescriptor(target, key) {
893
+ const obj = get_derived(computed);
894
+
895
+ if (key in obj) {
896
+ return {
897
+ enumerable: true,
898
+ configurable: true,
899
+ value: obj[key],
900
+ };
901
+ }
902
+ },
888
903
  ownKeys() {
889
904
  const obj = get_derived(computed);
890
905
  return Reflect.ownKeys(obj);
@@ -1164,7 +1179,7 @@ export async function maybe_tracked(v) {
1164
1179
  } else {
1165
1180
  value = await async_computed(async () => {
1166
1181
  return await get_tracked(v);
1167
- }, /** @type {Block} */ (active_block));
1182
+ }, /** @type {Block} */(active_block));
1168
1183
  }
1169
1184
  } else {
1170
1185
  value = await v;
@@ -30,6 +30,8 @@ class Output {
30
30
  body = '';
31
31
  /** @type {Set<string>} */
32
32
  css = new Set();
33
+ /** @type {Promise<any>[]} */
34
+ promises = [];
33
35
  /** @type {Output | null} */
34
36
  #parent = null;
35
37
 
@@ -60,18 +62,27 @@ class Output {
60
62
  /** @type {render} */
61
63
  export async function render(component) {
62
64
  const output = new Output(null);
65
+ let head, body, css;
63
66
 
64
- if (component.async) {
65
- await component(output, {});
66
- } else {
67
- component(output, {});
68
- }
69
-
70
- const { head, body, css } = output;
67
+ try {
68
+ if (component.async) {
69
+ await component(output, {});
70
+ } else {
71
+ component(output, {});
72
+ }
73
+ if (output.promises.length > 0) {
74
+ await Promise.all(output.promises);
75
+ }
71
76
 
72
- return { head, body, css };
77
+ head = output.head
78
+ body = output.body
79
+ css = output.css
80
+ }
81
+ catch (error) {
82
+ console.log(error)
83
+ }
84
+ return { head, body, css }
73
85
  }
74
-
75
86
  /**
76
87
  * @returns {void}
77
88
  */
@@ -237,14 +237,14 @@ export function export_builder(declaration, specifiers = [], attributes = [], so
237
237
  * @param {ESTree.BlockStatement} body
238
238
  * @returns {ESTree.FunctionDeclaration}
239
239
  */
240
- export function function_declaration(id, params, body) {
240
+ export function function_declaration(id, params, body, async = false) {
241
241
  return {
242
242
  type: 'FunctionDeclaration',
243
243
  id,
244
244
  params,
245
245
  body,
246
246
  generator: false,
247
- async: false,
247
+ async,
248
248
  metadata: /** @type {any} */ (null), // should not be used by codegen
249
249
  };
250
250
  }
@@ -238,86 +238,6 @@ exports[`for statements > handles updating with new objects with same key 2`] =
238
238
  </div>
239
239
  `;
240
240
 
241
- exports[`for statements > render a simple dynamic array 1`] = `
242
- <div>
243
- <!---->
244
- <div
245
- class="Item 1"
246
- >
247
- Item 1
248
- </div>
249
- <div
250
- class="Item 2"
251
- >
252
- Item 2
253
- </div>
254
- <div
255
- class="Item 3"
256
- >
257
- Item 3
258
- </div>
259
- <!---->
260
- <button>
261
- Add Item
262
- </button>
263
-
264
- </div>
265
- `;
266
-
267
- exports[`for statements > render a simple dynamic array 2`] = `
268
- <div>
269
- <!---->
270
- <div
271
- class="Item 1"
272
- >
273
- Item 1
274
- </div>
275
- <div
276
- class="Item 2"
277
- >
278
- Item 2
279
- </div>
280
- <div
281
- class="Item 3"
282
- >
283
- Item 3
284
- </div>
285
- <div
286
- class="Item 4"
287
- >
288
- Item 4
289
- </div>
290
- <!---->
291
- <button>
292
- Add Item
293
- </button>
294
-
295
- </div>
296
- `;
297
-
298
- exports[`for statements > render a simple static array 1`] = `
299
- <div>
300
- <!---->
301
- <div
302
- class="Item 1"
303
- >
304
- Item 1
305
- </div>
306
- <div
307
- class="Item 2"
308
- >
309
- Item 2
310
- </div>
311
- <div
312
- class="Item 3"
313
- >
314
- Item 3
315
- </div>
316
- <!---->
317
-
318
- </div>
319
- `;
320
-
321
241
  exports[`for statements > renders a simple dynamic array 1`] = `
322
242
  <div>
323
243
  <!---->
@@ -56,51 +56,3 @@ exports[`basic client > rendering & text > should handle lexical scopes correctl
56
56
 
57
57
  </div>
58
58
  `;
59
-
60
- exports[`basic client > text rendering > basic operations 1`] = `
61
- <div>
62
- <div>
63
- 0
64
- </div>
65
- <div>
66
- 2
67
- </div>
68
- <div>
69
- 5
70
- </div>
71
- <div>
72
- 2
73
- </div>
74
-
75
- </div>
76
- `;
77
-
78
- exports[`basic client > text rendering > renders semi-dynamic text 1`] = `
79
- <div>
80
- <div>
81
- Hello World
82
- </div>
83
-
84
- </div>
85
- `;
86
-
87
- exports[`basic client > text rendering > renders simple JS expression logic correctly 1`] = `
88
- <div>
89
- <div>
90
- {"0":"Test"}
91
- </div>
92
- <div>
93
- 1
94
- </div>
95
-
96
- </div>
97
- `;
98
-
99
- exports[`basic client > text rendering > renders static text 1`] = `
100
- <div>
101
- <div>
102
- Hello World
103
- </div>
104
-
105
- </div>
106
- `;
@@ -123,4 +123,20 @@ describe('basic client > errors', () => {
123
123
 
124
124
  expect(error).toBe('Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation');
125
125
  });
126
+
127
+ it('should throw error for await in client-side control-flow statements', () => {
128
+ const code = `
129
+ export default component App() {
130
+ let data = 'initial';
131
+ if (true) {
132
+ await new Promise(r => setTimeout(r, 100));
133
+ data = 'loaded';
134
+ }
135
+ <div>{data}</div>
136
+ }
137
+ `;
138
+ expect(() => {
139
+ compile(code, 'test.ripple', { mode: 'client' });
140
+ }).toThrow('`await` is not allowed in client-side control-flow statements');
141
+ });
126
142
  });
@@ -0,0 +1,10 @@
1
+ export function test(arg: string): string
2
+ export function test(arg: number): string
3
+
4
+ export function test(arg: string | number): string {
5
+ if (typeof arg === 'string') {
6
+ return arg;
7
+ }
8
+
9
+ return arg.toString();
10
+ }
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mount, flushSync } from 'ripple';
3
+
4
+ import { test } from './function-overload-import.ripple';
5
+
6
+ describe('function overload import tests', () => {
7
+ describe('function overloads', () => {
8
+ it('test function with string argument returns the string', () => {
9
+ const result = test('hello');
10
+ expect(result).toBe('hello');
11
+ expect(typeof result).toBe('string');
12
+ });
13
+
14
+ it('test function with number argument returns string representation', () => {
15
+ const result = test(42);
16
+ expect(result).toBe('42');
17
+ expect(typeof result).toBe('string');
18
+ });
19
+
20
+ it('test function with zero returns "0"', () => {
21
+ const result = test(0);
22
+ expect(result).toBe('0');
23
+ });
24
+
25
+ it('test function with negative number returns string representation', () => {
26
+ const result = test(-100);
27
+ expect(result).toBe('-100');
28
+ });
29
+
30
+ it('test function with empty string returns empty string', () => {
31
+ const result = test('');
32
+ expect(result).toBe('');
33
+ });
34
+
35
+ it('test function with decimal number returns string representation', () => {
36
+ const result = test(3.14159);
37
+ expect(result).toBe('3.14159');
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from 'ripple/server';
3
+ import { track, set, get } from 'ripple';
4
+
5
+ describe('await in control flow', () => {
6
+ it('should handle await inside if statement', async () => {
7
+ component App() {
8
+ let condition = true;
9
+ let data = track('loading');
10
+
11
+ if (condition) {
12
+ await new Promise(resolve => setTimeout(() => {
13
+ @data = 'loaded';
14
+ resolve();
15
+ }, 10));
16
+ }
17
+
18
+ <div>{@data}</div>
19
+ }
20
+
21
+ const { body } = await render(App);
22
+ expect(body).toBe('<div>loaded</div>');
23
+ });
24
+
25
+ it('should handle await inside for...of loop', async () => {
26
+ component App() {
27
+ const items = [1, 2, 3];
28
+ let result = '';
29
+
30
+ for (const item of items) {
31
+ await new Promise(resolve => setTimeout(resolve, 5));
32
+ result += item;
33
+ }
34
+
35
+ <div>{result}</div>
36
+ }
37
+
38
+ const { body } = await render(App);
39
+ expect(body).toBe('<div>123</div>');
40
+ });
41
+
42
+ it('should handle await inside switch statement', async () => {
43
+ component App() {
44
+ let value = 'b';
45
+
46
+ switch (value) {
47
+ case 'a':
48
+ <div>{'Case A'}</div>
49
+ break;
50
+ case 'b':
51
+ await new Promise(resolve => setTimeout(resolve, 10));
52
+ <div>{'Case B'}</div>
53
+ break;
54
+ default:
55
+ <div>{'Default Case'}</div>
56
+ }
57
+ }
58
+
59
+ const { body } = await render(App);
60
+ expect(body).toBe('<div>Case B</div>');
61
+ });});
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from 'ripple/server';
3
+
4
+ describe('for statements in SSR', () => {
5
+ it('renders a simple static array', async () => {
6
+ component App() {
7
+ const items = ['Item 1', 'Item 2', 'Item 3'];
8
+
9
+ for (const item of items) {
10
+ <div class={item}>{item}</div>
11
+ }
12
+ }
13
+
14
+ const { body } = await render(App);
15
+ expect(body).toBe('<div class="Item 1">Item 1</div><div class="Item 2">Item 2</div><div class="Item 3">Item 3</div>');
16
+ });
17
+
18
+ it('renders nested for...of loops', async () => {
19
+ component App() {
20
+ const groups = [
21
+ {
22
+ name: 'Group 1',
23
+ items: ['Item 1.1', 'Item 1.2']
24
+ },
25
+ {
26
+ name: 'Group 2',
27
+ items: ['Item 2.1', 'Item 2.2']
28
+ }
29
+ ];
30
+
31
+ for (const group of groups) {
32
+ <h1>{group.name}</h1>
33
+ <ul>
34
+ for (const item of group.items) {
35
+ <li>{item}</li>
36
+ }
37
+ </ul>
38
+ }
39
+ }
40
+
41
+ const { body } = await render(App);
42
+ expect(body).toBe('<h1>Group 1</h1><ul><li>Item 1.1</li><li>Item 1.2</li></ul><h1>Group 2</h1><ul><li>Item 2.1</li><li>Item 2.2</li></ul>');
43
+ });
44
+ });
@@ -63,4 +63,24 @@ describe('if statements in SSR', () => {
63
63
  const { body } = await render(App);
64
64
  expect(body).toBe('<div>Default Case</div>');
65
65
  });
66
- });
66
+
67
+ it('renders nested if-else blocks correctly', async () => {
68
+ component App() {
69
+ let outer = true;
70
+ let inner = false;
71
+
72
+ if (outer) {
73
+ if (inner) {
74
+ <div>{'Outer true, Inner true'}</div>
75
+ } else {
76
+ <div>{'Outer true, Inner false'}</div>
77
+ }
78
+ } else {
79
+ <div>{'Outer false'}</div>
80
+ }
81
+ }
82
+
83
+ const { body } = await render(App);
84
+ expect(body).toBe('<div>Outer true, Inner false</div>');
85
+ });
86
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { escape } from '../../src/utils/escaping.js';
3
+
4
+ describe('escape utility', () => {
5
+ describe('content escaping (is_attr = false)', () => {
6
+ it('should escape & to &amp;', () => {
7
+ expect(escape('foo & bar', false)).toBe('foo &amp; bar');
8
+ });
9
+
10
+ it('should escape < to &lt;', () => {
11
+ expect(escape('foo < bar', false)).toBe('foo &lt; bar');
12
+ });
13
+
14
+ it('should escape multiple special characters', () => {
15
+ expect(escape('a & b < c', false)).toBe('a &amp; b &lt; c');
16
+ });
17
+
18
+ it('should not escape double quotes in content', () => {
19
+ expect(escape('foo "bar" baz', false)).toBe('foo "bar" baz');
20
+ });
21
+
22
+ it('should handle empty string', () => {
23
+ expect(escape('', false)).toBe('');
24
+ });
25
+
26
+ it('should handle string with no special characters', () => {
27
+ expect(escape('hello world', false)).toBe('hello world');
28
+ });
29
+
30
+ it('should handle null values', () => {
31
+ expect(escape(null, false)).toBe('');
32
+ });
33
+
34
+ it('should handle undefined values', () => {
35
+ expect(escape(undefined, false)).toBe('');
36
+ });
37
+
38
+ it('should handle numbers', () => {
39
+ expect(escape(123, false)).toBe('123');
40
+ });
41
+
42
+ it('should escape consecutive special characters', () => {
43
+ expect(escape('&&<<', false)).toBe('&amp;&amp;&lt;&lt;');
44
+ });
45
+
46
+ it('should handle special characters at start', () => {
47
+ expect(escape('&hello', false)).toBe('&amp;hello');
48
+ });
49
+
50
+ it('should handle special characters at end', () => {
51
+ expect(escape('hello<', false)).toBe('hello&lt;');
52
+ });
53
+ });
54
+
55
+ describe('attribute escaping (is_attr = true)', () => {
56
+ it('should escape & to &amp;', () => {
57
+ expect(escape('foo & bar', true)).toBe('foo &amp; bar');
58
+ });
59
+
60
+ it('should escape < to &lt;', () => {
61
+ expect(escape('foo < bar', true)).toBe('foo &lt; bar');
62
+ });
63
+
64
+ it('should escape " to &quot;', () => {
65
+ expect(escape('foo "bar" baz', true)).toBe('foo &quot;bar&quot; baz');
66
+ });
67
+
68
+ it('should escape all three special characters', () => {
69
+ expect(escape('a & b < c "d"', true)).toBe('a &amp; b &lt; c &quot;d&quot;');
70
+ });
71
+
72
+ it('should handle empty string', () => {
73
+ expect(escape('', true)).toBe('');
74
+ });
75
+
76
+ it('should handle string with no special characters', () => {
77
+ expect(escape('hello world', true)).toBe('hello world');
78
+ });
79
+
80
+ it('should handle null values', () => {
81
+ expect(escape(null, true)).toBe('');
82
+ });
83
+
84
+ it('should handle undefined values', () => {
85
+ expect(escape(undefined, true)).toBe('');
86
+ });
87
+
88
+ it('should escape consecutive quotes', () => {
89
+ expect(escape('"""', true)).toBe('&quot;&quot;&quot;');
90
+ });
91
+
92
+ it('should handle mixed escaping', () => {
93
+ expect(escape('<div class="foo & bar">', true)).toBe('&lt;div class=&quot;foo &amp; bar&quot;>');
94
+ });
95
+ });
96
+
97
+ describe('default parameter behavior', () => {
98
+ it('should default to content escaping when is_attr is undefined', () => {
99
+ expect(escape('foo "bar"')).toBe('foo "bar"');
100
+ });
101
+ });
102
+ });