ripple 0.2.115 → 0.2.118

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 (93) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/1-parse/index.js +79 -0
  4. package/src/compiler/phases/3-transform/client/index.js +54 -8
  5. package/src/compiler/phases/3-transform/segments.js +107 -60
  6. package/src/compiler/phases/3-transform/server/index.js +21 -11
  7. package/src/compiler/types/index.d.ts +16 -0
  8. package/src/runtime/index-client.js +19 -185
  9. package/src/runtime/index-server.js +24 -0
  10. package/src/runtime/internal/client/bindings.js +443 -0
  11. package/src/runtime/internal/client/index.js +4 -0
  12. package/src/runtime/internal/client/runtime.js +10 -0
  13. package/src/runtime/internal/client/utils.js +0 -8
  14. package/src/runtime/map.js +11 -1
  15. package/src/runtime/set.js +11 -1
  16. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  17. package/tests/client/_etc.test.ripple +5 -0
  18. package/tests/client/array/array.copy-within.test.ripple +120 -0
  19. package/tests/client/array/array.derived.test.ripple +495 -0
  20. package/tests/client/array/array.iteration.test.ripple +115 -0
  21. package/tests/client/array/array.mutations.test.ripple +385 -0
  22. package/tests/client/array/array.static.test.ripple +237 -0
  23. package/tests/client/array/array.to-methods.test.ripple +93 -0
  24. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  26. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  27. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  28. package/tests/client/basic/basic.collections.test.ripple +94 -0
  29. package/tests/client/basic/basic.components.test.ripple +225 -0
  30. package/tests/client/basic/basic.errors.test.ripple +126 -0
  31. package/tests/client/basic/basic.events.test.ripple +222 -0
  32. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  33. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  34. package/tests/client/basic/basic.styling.test.ripple +63 -0
  35. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  36. package/tests/client/boundaries.test.ripple +2 -21
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  39. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  40. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  41. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  42. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  43. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  44. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  45. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  46. package/tests/client/composite/composite.generics.test.ripple +211 -0
  47. package/tests/client/composite/composite.props.test.ripple +106 -0
  48. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  49. package/tests/client/composite/composite.render.test.ripple +84 -0
  50. package/tests/client/computed-properties.test.ripple +2 -21
  51. package/tests/client/context.test.ripple +5 -22
  52. package/tests/client/date.test.ripple +1 -20
  53. package/tests/client/dynamic-elements.test.ripple +16 -24
  54. package/tests/client/for.test.ripple +4 -23
  55. package/tests/client/head.test.ripple +11 -23
  56. package/tests/client/html.test.ripple +1 -20
  57. package/tests/client/input-value.test.ripple +11 -31
  58. package/tests/client/map.test.ripple +82 -20
  59. package/tests/client/media-query.test.ripple +10 -23
  60. package/tests/client/object.test.ripple +5 -24
  61. package/tests/client/portal.test.ripple +2 -19
  62. package/tests/client/ref.test.ripple +8 -26
  63. package/tests/client/set.test.ripple +84 -22
  64. package/tests/client/svg.test.ripple +1 -22
  65. package/tests/client/switch.test.ripple +6 -25
  66. package/tests/client/tracked-expression.test.ripple +2 -21
  67. package/tests/client/typescript-generics.test.ripple +0 -21
  68. package/tests/client/url/url.derived.test.ripple +83 -0
  69. package/tests/client/url/url.parsing.test.ripple +165 -0
  70. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  71. package/tests/client/url/url.reactivity.test.ripple +449 -0
  72. package/tests/client/url/url.serialization.test.ripple +50 -0
  73. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  74. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  75. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  76. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  77. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  78. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  79. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  80. package/tests/client.d.ts +12 -0
  81. package/tests/server/if.test.ripple +66 -0
  82. package/tests/setup-client.js +28 -0
  83. package/tsconfig.json +4 -2
  84. package/types/index.d.ts +92 -46
  85. package/LICENSE +0 -21
  86. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  87. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  88. package/tests/client/array.test.ripple +0 -1455
  89. package/tests/client/basic.test.ripple +0 -1892
  90. package/tests/client/compiler.test.ripple +0 -541
  91. package/tests/client/composite.test.ripple +0 -692
  92. package/tests/client/url-search-params.test.ripple +0 -912
  93. package/tests/client/url.test.ripple +0 -954
@@ -1,25 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track, TrackedArray, TrackedMap, effect } from 'ripple';
1
+ import { flushSync, track } from 'ripple';
3
2
 
4
3
  describe('computed tracked properties', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
4
  it('should update a property using assignment', () => {
24
5
  component App() {
25
6
  let obj = {
@@ -61,4 +42,4 @@ describe('computed tracked properties', () => {
61
42
 
62
43
  expect(container).toMatchSnapshot();
63
44
  });
64
- });
45
+ });
@@ -1,26 +1,9 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, Context, flushSync, track } from 'ripple';
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Context, flushSync } from 'ripple';
3
3
 
4
4
  describe('context', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- });
21
-
22
5
  it('creates a reactive ref with initial value', () => {
23
- const MyContext = new Context(null);
6
+ const MyContext = new Context<string | null>(null);
24
7
 
25
8
  component Child() {
26
9
  const value = MyContext.get();
@@ -43,10 +26,10 @@ describe('context', () => {
43
26
 
44
27
  it('handles context captured inside a computed tracked', () => {
45
28
 
46
- const MyContext = new Context(null)
29
+ const MyContext = new Context<number | null>(null)
47
30
 
48
31
  const doubleContext = () => {
49
- const value = MyContext.get()
32
+ const value = MyContext.get() as number
50
33
  return value * 2
51
34
  }
52
35
 
@@ -1,25 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, TrackedDate, track } from 'ripple';
1
+ import { flushSync, TrackedDate, track } from 'ripple';
3
2
 
4
3
  describe('TrackedDate', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container,
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
4
  it('handles getTime() with reactive updates', () => {
24
5
  component DateTest() {
25
6
  let date = new TrackedDate(2025, 0, 1);
@@ -1,23 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track, createRefKey } from 'ripple';
1
+ import { flushSync, track, createRefKey } from 'ripple';
3
2
 
4
3
  describe('dynamic DOM elements', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container,
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
- afterEach(() => {
18
- document.body.removeChild(container);
19
- container = null;
20
- });
21
4
  it('renders static dynamic element', () => {
22
5
  component App() {
23
6
  let tag = track('div');
@@ -30,6 +13,7 @@ describe('dynamic DOM elements', () => {
30
13
  expect(element).toBeTruthy();
31
14
  expect(element.textContent).toBe('Hello World');
32
15
  });
16
+
33
17
  it('renders reactive dynamic element', () => {
34
18
  component App() {
35
19
  let tag = track('div');
@@ -53,6 +37,7 @@ describe('dynamic DOM elements', () => {
53
37
  expect(dynamicElement.tagName).toBe('SPAN');
54
38
  expect(dynamicElement.textContent).toBe('Hello World');
55
39
  });
40
+
56
41
  it('renders self-closing dynamic element', () => {
57
42
  component App() {
58
43
  let tag = track('input');
@@ -61,11 +46,12 @@ describe('dynamic DOM elements', () => {
61
46
  }
62
47
  render(App);
63
48
 
64
- const element = container.querySelector('input');
49
+ const element = container.querySelector('input') as HTMLInputElement;
65
50
  expect(element).toBeTruthy();
66
51
  expect(element.type).toBe('text');
67
52
  expect(element.value).toBe('test');
68
53
  });
54
+
69
55
  it('handles dynamic element with attributes', () => {
70
56
  component App() {
71
57
  let tag = track('div');
@@ -81,6 +67,7 @@ describe('dynamic DOM elements', () => {
81
67
  expect(element.getAttribute('data-testid')).toBe('dynamic-element');
82
68
  expect(element.textContent).toBe('Content');
83
69
  });
70
+
84
71
  it('handles nested dynamic elements', () => {
85
72
  component App() {
86
73
  let outerTag = track('div');
@@ -100,6 +87,7 @@ describe('dynamic DOM elements', () => {
100
87
  expect(inner.textContent).toBe('Nested content');
101
88
  expect(outer.contains(inner)).toBe(true);
102
89
  });
90
+
103
91
  it('handles dynamic element with class object', () => {
104
92
  component App() {
105
93
  let tag = track('div');
@@ -116,12 +104,13 @@ describe('dynamic DOM elements', () => {
116
104
  expect(element.classList.contains('active')).toBe(true);
117
105
  expect(element.classList.contains('dynamic-element')).toBe(true);
118
106
  });
107
+
119
108
  it('handles dynamic element with style object', () => {
120
109
  component App() {
121
110
  let tag = track('span');
122
111
 
123
- <@tag style={{
124
- color: 'red',
112
+ <@tag style={{
113
+ color: 'red',
125
114
  fontSize: '16px',
126
115
  fontWeight: 'bold'
127
116
  }}>
@@ -136,6 +125,7 @@ describe('dynamic DOM elements', () => {
136
125
  expect(element.style.fontSize).toBe('16px');
137
126
  expect(element.style.fontWeight).toBe('bold');
138
127
  });
128
+
139
129
  it('handles dynamic element with spread attributes', () => {
140
130
  component App() {
141
131
  let tag = track('section');
@@ -158,13 +148,14 @@ describe('dynamic DOM elements', () => {
158
148
  expect(element.className).toBe('spread-class');
159
149
  expect(element.getAttribute('data-extra')).toBe('additional');
160
150
  });
151
+
161
152
  it('handles dynamic element with ref', () => {
162
- let capturedElement = null;
153
+ let capturedElement: HTMLArticleElement | undefined;
163
154
 
164
155
  component App() {
165
156
  let tag = track('article');
166
157
 
167
- <@tag {ref (node) => { capturedElement = node; }} id="ref-test">
158
+ <@tag {ref (node: HTMLArticleElement) => { capturedElement = node; }} id="ref-test">
168
159
  {'Element with ref'}
169
160
  </@tag>
170
161
  }
@@ -175,11 +166,12 @@ describe('dynamic DOM elements', () => {
175
166
  expect(capturedElement.id).toBe('ref-test');
176
167
  expect(capturedElement.textContent).toBe('Element with ref');
177
168
  });
169
+
178
170
  it('handles dynamic element with createRefKey in spread', () => {
179
171
  component App() {
180
172
  let tag = track('header');
181
173
 
182
- function elementRef(node) {
174
+ function elementRef(node: HTMLHeaderElement) {
183
175
  // Set an attribute on the element to prove ref was called
184
176
  node.setAttribute('data-spread-ref-called', 'true');
185
177
  node.setAttribute('data-spread-ref-tag', node.tagName.toLowerCase());
@@ -1,26 +1,7 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, TrackedArray, track } from 'ripple';
1
+ import { flushSync, TrackedArray, track } from 'ripple';
3
2
 
4
3
  describe('for statements', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
- it('render a simple static array', () => {
4
+ it('renders a simple static array', () => {
24
5
  component App() {
25
6
  const items = ['Item 1', 'Item 2', 'Item 3'];
26
7
 
@@ -34,7 +15,7 @@ describe('for statements', () => {
34
15
  expect(container).toMatchSnapshot();
35
16
  });
36
17
 
37
- it('render a simple dynamic array', () => {
18
+ it('renders a simple dynamic array', () => {
38
19
  component App() {
39
20
  const items = new TrackedArray('Item 1', 'Item 2', 'Item 3');
40
21
 
@@ -76,7 +57,7 @@ describe('for statements', () => {
76
57
  render(App);
77
58
 
78
59
  expect(container).toMatchSnapshot();
79
-
60
+
80
61
  const button = container.querySelector('button');
81
62
 
82
63
  button.click();
@@ -1,26 +1,14 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track } from 'ripple';
1
+ import { flushSync, track } from 'ripple';
3
2
 
4
3
  describe('head elements', () => {
5
- let container;
6
- let originalTitle;
7
-
8
- function render(component) {
9
- mount(component, {
10
- target: container
11
- });
12
- }
4
+ let originalTitle: string;
13
5
 
14
6
  beforeEach(() => {
15
- container = document.createElement('div');
16
- document.body.appendChild(container);
17
7
  // Store original title to restore later
18
8
  originalTitle = document.title;
19
9
  });
20
10
 
21
11
  afterEach(() => {
22
- document.body.removeChild(container);
23
- container = null;
24
12
  // Restore original title
25
13
  document.title = originalTitle;
26
14
  });
@@ -34,7 +22,7 @@ describe('head elements', () => {
34
22
  }
35
23
 
36
24
  render(App);
37
-
25
+
38
26
  expect(document.title).toBe('Static Test Title');
39
27
  expect(container.querySelector('div').textContent).toBe('Content');
40
28
  });
@@ -53,7 +41,7 @@ describe('head elements', () => {
53
41
  }
54
42
 
55
43
  render(App);
56
-
44
+
57
45
  expect(document.title).toBe('Initial Title');
58
46
  expect(container.querySelector('span').textContent).toBe('Initial Title');
59
47
 
@@ -78,7 +66,7 @@ describe('head elements', () => {
78
66
  }
79
67
 
80
68
  render(App);
81
-
69
+
82
70
  expect(document.title).toBe('Hello World!');
83
71
 
84
72
  const button = container.querySelector('button');
@@ -103,7 +91,7 @@ describe('head elements', () => {
103
91
  }
104
92
 
105
93
  render(App);
106
-
94
+
107
95
  expect(document.title).toBe('Count: 0');
108
96
 
109
97
  const button = container.querySelector('button');
@@ -127,11 +115,11 @@ describe('head elements', () => {
127
115
  }
128
116
 
129
117
  render(App);
130
-
118
+
131
119
  expect(document.title).toBe('Step 1 of 3');
132
120
 
133
121
  const button = container.querySelector('button');
134
-
122
+
135
123
  button.click();
136
124
  flushSync();
137
125
  expect(document.title).toBe('Step 2 of 3');
@@ -154,7 +142,7 @@ describe('head elements', () => {
154
142
  }
155
143
 
156
144
  render(App);
157
-
145
+
158
146
  expect(document.title).toBe('');
159
147
  });
160
148
 
@@ -173,11 +161,11 @@ describe('head elements', () => {
173
161
  }
174
162
 
175
163
  render(App);
176
-
164
+
177
165
  expect(document.title).toBe('App - Main Page');
178
166
 
179
167
  const buttons = container.querySelectorAll('button');
180
-
168
+
181
169
  // Toggle prefix off
182
170
  buttons[0].click();
183
171
  flushSync();
@@ -1,25 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track } from 'ripple';
1
+ import { flushSync, track } from 'ripple';
3
2
 
4
3
  describe('html directive', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
4
  it('renders static html', () => {
24
5
  component App() {
25
6
  let str = '<div>Test</div>';
@@ -1,28 +1,8 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, track, effect } from 'ripple';
3
- import { bindValue, bindChecked } from 'ripple';
1
+ import { flushSync, track, effect, bindValue, bindChecked } from 'ripple';
4
2
 
5
3
  describe('use value()', () => {
6
- let container;
7
-
8
- function render(component) {
9
- mount(component, {
10
- target: container,
11
- });
12
- }
13
-
14
- beforeEach(() => {
15
- container = document.createElement('div');
16
- document.body.appendChild(container);
17
- });
18
-
19
- afterEach(() => {
20
- document.body.removeChild(container);
21
- container = null;
22
- });
23
-
24
4
  it('should update value on input', () => {
25
- const logs = [];
5
+ const logs: string[] = [];
26
6
 
27
7
  component App() {
28
8
  const text = track('');
@@ -36,7 +16,7 @@ describe('use value()', () => {
36
16
  render(App);
37
17
  flushSync();
38
18
 
39
- const input = container.querySelector('input');
19
+ const input = container.querySelector('input') as HTMLInputElement;
40
20
  input.value = 'Hello';
41
21
  input.dispatchEvent(new Event('input'));
42
22
  flushSync();
@@ -45,7 +25,7 @@ describe('use value()', () => {
45
25
  });
46
26
 
47
27
  it('should update value on input with a predefined value', () => {
48
- const logs = [];
28
+ const logs: string[] = [];
49
29
 
50
30
  component App() {
51
31
  const text = track('foo');
@@ -59,8 +39,8 @@ describe('use value()', () => {
59
39
  render(App);
60
40
  flushSync();
61
41
 
62
- expect(container.querySelector('input').value).toBe('foo');
63
- const input = container.querySelector('input');
42
+ const input = container.querySelector('input') as HTMLInputElement;
43
+ expect(input.value).toBe('foo');
64
44
  input.value = 'Hello';
65
45
  input.dispatchEvent(new Event('input'));
66
46
  flushSync();
@@ -69,7 +49,7 @@ describe('use value()', () => {
69
49
  });
70
50
 
71
51
  it('should update checked on input', () => {
72
- const logs = [];
52
+ const logs: string[] = [];
73
53
 
74
54
  component App() {
75
55
  const value = track(false);
@@ -83,17 +63,17 @@ describe('use value()', () => {
83
63
  render(App);
84
64
  flushSync();
85
65
 
86
- const input = container.querySelector('input');
66
+ const input = container.querySelector('input') as HTMLInputElement;
87
67
  input.checked = true;
88
68
  input.dispatchEvent(new Event('change'));
89
69
  flushSync();
90
-
70
+
91
71
  expect(input.checked).toBe(true);
92
72
  expect(logs).toEqual(['checked changed', false, 'checked changed', true]);
93
73
  });
94
74
 
95
75
  it('should update select value on change', () => {
96
- const logs = [];
76
+ const logs: string[] = [];
97
77
 
98
78
  component App() {
99
79
  const select = track('2');
@@ -112,7 +92,7 @@ describe('use value()', () => {
112
92
  render(App);
113
93
  flushSync();
114
94
 
115
- const select = container.querySelector('select');
95
+ const select = container.querySelector('select') as HTMLSelectElement;
116
96
  select.value = '3';
117
97
  select.dispatchEvent(new Event('change'));
118
98
  flushSync();
@@ -1,25 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, TrackedMap, track } from 'ripple';
1
+ import { flushSync, TrackedMap, track } from 'ripple';
3
2
 
4
3
  describe('TrackedMap', () => {
5
- let container;
6
-
7
- function render(component) {
8
- mount(component, {
9
- target: container
10
- });
11
- }
12
-
13
- beforeEach(() => {
14
- container = document.createElement('div');
15
- document.body.appendChild(container);
16
- });
17
-
18
- afterEach(() => {
19
- document.body.removeChild(container);
20
- container = null;
21
- });
22
-
23
4
  it('handles set with update and delete operations with a reactive size var', () => {
24
5
  component MapTest() {
25
6
  let map = new TrackedMap([['a', 1], ['b', 2], ['c', 3]]);
@@ -138,4 +119,85 @@ describe('TrackedMap', () => {
138
119
 
139
120
  expect(container.querySelectorAll('pre')[0].textContent).toBe('[["foo",1],["bar",2]]');
140
121
  });
122
+
123
+ it('creates empty TrackedMap using #Map() shorthand syntax', () => {
124
+ component MapTest() {
125
+ let map = #Map();
126
+
127
+ <button onClick={() => map.set('a', 1)}>{'add'}</button>
128
+ <pre>{map.size}</pre>
129
+ }
130
+
131
+ render(MapTest);
132
+
133
+ expect(container.querySelector('pre').textContent).toBe('0');
134
+
135
+ const addButton = container.querySelector('button');
136
+ addButton.click();
137
+ flushSync();
138
+
139
+ expect(container.querySelector('pre').textContent).toBe('1');
140
+ });
141
+
142
+
143
+ it('creates TrackedMap with initial entries using #Map() shorthand syntax', () => {
144
+ component MapTest() {
145
+ let map = #Map([['a', 1], ['b', 2], ['c', 3]]);
146
+ let value = track(() => map.get('b'));
147
+
148
+ <button onClick={() => map.set('b', 10)}>{'update'}</button>
149
+ <pre>{map.size}</pre>
150
+ <pre>{@value}</pre>
151
+ }
152
+
153
+ render(MapTest);
154
+
155
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('3');
156
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
157
+
158
+ const updateButton = container.querySelector('button');
159
+ updateButton.click();
160
+ flushSync();
161
+
162
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('10');
163
+ });
164
+
165
+ it('handles all operations with #Map() shorthand syntax', () => {
166
+ component MapTest() {
167
+ let map = #Map([['x', 100], ['y', 200]]);
168
+ let keys = track(() => Array.from(map.keys()));
169
+
170
+ <button onClick={() => map.set('z', 300)}>{'add'}</button>
171
+ <button onClick={() => map.delete('x')}>{'delete'}</button>
172
+ <button onClick={() => map.clear()}>{'clear'}</button>
173
+
174
+ <pre>{JSON.stringify(@keys)}</pre>
175
+ <pre>{map.size}</pre>
176
+ }
177
+
178
+ render(MapTest);
179
+
180
+ const [addButton, deleteButton, clearButton] = container.querySelectorAll('button');
181
+
182
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
183
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
184
+
185
+ addButton.click();
186
+ flushSync();
187
+
188
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y","z"]');
189
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
190
+
191
+ deleteButton.click();
192
+ flushSync();
193
+
194
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('["y","z"]');
195
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('2');
196
+
197
+ clearButton.click();
198
+ flushSync();
199
+
200
+ expect(container.querySelectorAll('pre')[0].textContent).toBe('[]');
201
+ expect(container.querySelectorAll('pre')[1].textContent).toBe('0');
202
+ });
141
203
  });
@@ -1,25 +1,26 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { mount, flushSync, MediaQuery, track } from 'ripple';
1
+ import { flushSync, MediaQuery, track } from 'ripple';
2
+
3
+ type Callback = (event: MediaQueryListEvent) => void;
3
4
 
4
5
  function setupMatchMedia() {
5
- let listeners = new Set();
6
+ let listeners = new Set<Callback>();
6
7
 
7
8
  // A mock implementation of matchMedia
8
9
  const mockMatchMedia = vi.fn().mockImplementation(query => {
9
10
  return {
10
11
  media: query,
11
12
  matches: false, // default value
12
- addEventListener: (type, cb) => {
13
+ addEventListener: (type: string, cb: Callback) => {
13
14
  if (type === 'change') listeners.add(cb);
14
15
  },
15
- removeEventListener: (type, cb) => {
16
+ removeEventListener: (type: string, cb: Callback) => {
16
17
  if (type === 'change') listeners.delete(cb);
17
18
  },
18
19
  /** @param {function(MediaQueryListEvent): void} cb */
19
- addListener: (cb) => listeners.add(cb),
20
+ addListener: (cb: Callback) => listeners.add(cb),
20
21
  /** @param {function(MediaQueryListEvent): void} cb */
21
- removeListener: (cb) => listeners.delete(cb),
22
- dispatch: (event) => {
22
+ removeListener: (cb: Callback) => listeners.delete(cb),
23
+ dispatch: (event: MediaQueryListEvent) => {
23
24
  listeners.forEach((cb) => cb(event));
24
25
  },
25
26
  listenersCount: () => listeners.size,
@@ -35,24 +36,10 @@ function setupMatchMedia() {
35
36
  }
36
37
 
37
38
  describe('MediaQuery', () => {
38
- let container;
39
- let mm;
40
-
41
- function render(component) {
42
- mount(component, {
43
- target: container
44
- });
45
- }
39
+ let mm: ReturnType<typeof setupMatchMedia>;
46
40
 
47
41
  beforeEach(() => {
48
42
  mm = setupMatchMedia();
49
- container = document.createElement('div');
50
- document.body.appendChild(container);
51
- });
52
-
53
- afterEach(() => {
54
- document.body.removeChild(container);
55
- container = null;
56
43
  });
57
44
 
58
45
  it('should be reactive if matchMedia changes', () => {