ripple 0.2.6 → 0.2.7

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.
@@ -1,273 +1,302 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+
2
3
  import { mount, flushSync } from 'ripple';
3
4
 
4
5
  describe('basic', () => {
5
- let container;
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
+ it('render static text', () => {
25
+ component Basic() {
26
+ <div>{'Hello World'}</div>
27
+ }
28
+
29
+ render(Basic);
30
+ expect(container).toMatchSnapshot();
31
+ });
32
+
33
+ it('render static attributes', () => {
34
+ component Basic() {
35
+ <div class='foo' id='bar' style='color: red;'>{'Hello World'}</div>
36
+ }
37
+
38
+ render(Basic);
39
+
40
+ expect(container).toMatchSnapshot();
41
+ });
42
+
43
+ it('render semi-dynamic text', () => {
44
+ component Basic() {
45
+ let text = 'Hello World';
46
+ <div>{text}</div>
47
+ }
48
+
49
+ render(Basic);
50
+
51
+ expect(container).toMatchSnapshot();
52
+ });
53
+
54
+ it('render dynamic text', () => {
55
+ component Basic() {
56
+ let $text = 'Hello World';
57
+ <button onClick={() => $text = 'Hello Ripple'}>{'Change Text'}</button>
58
+ <div>{$text}</div>
59
+ }
60
+
61
+ render(Basic);
62
+
63
+ const button = container.querySelector('button');
64
+
65
+ button.click();
66
+ flushSync();
67
+
68
+ expect(container.querySelector('div').textContent).toEqual('Hello Ripple');
69
+ });
70
+
71
+ it('render dynamic class attribute', () => {
72
+ component Basic() {
73
+ let $active = false;
74
+ <button onClick={() => $active = !$active}>{'Toggle'}</button>
75
+ <div $class={$active ? 'active' : 'inactive'}>{'Dynamic Class'}</div>
76
+ }
77
+
78
+ render(Basic);
79
+
80
+ const button = container.querySelector('button');
81
+ const div = container.querySelector('div');
82
+
83
+ expect(div.className).toBe('inactive');
84
+
85
+ button.click();
86
+ flushSync();
87
+
88
+ expect(div.className).toBe('active');
89
+
90
+ button.click();
91
+ flushSync();
92
+
93
+ expect(div.className).toBe('inactive');
94
+ });
95
+
96
+ it('render dynamic id attribute', () => {
97
+ component Basic() {
98
+ let $count = 0;
99
+ <button onClick={() => $count++}>{'Increment'}</button>
100
+ <div $id={`item-${$count}`}>{'Dynamic ID'}</div>
101
+ }
102
+
103
+ render(Basic);
104
+
105
+ const button = container.querySelector('button');
106
+ const div = container.querySelector('div');
107
+
108
+ expect(div.id).toBe('item-0');
109
+
110
+ button.click();
111
+ flushSync();
112
+
113
+ expect(div.id).toBe('item-1');
114
+
115
+ button.click();
116
+ flushSync();
117
+
118
+ expect(div.id).toBe('item-2');
119
+ });
120
+
121
+ it('render dynamic style attribute', () => {
122
+ component Basic() {
123
+ let $color = 'red';
124
+ <button onClick={() => $color = $color === 'red' ? 'blue' : 'red'}>{'Change Color'}</button>
125
+ <div $style={`color: ${$color}; font-weight: bold;`}>{'Dynamic Style'}</div>
126
+ }
127
+
128
+ render(Basic);
129
+
130
+ const button = container.querySelector('button');
131
+ const div = container.querySelector('div');
132
+
133
+ expect(div.style.color).toBe('red');
134
+ expect(div.style.fontWeight).toBe('bold');
135
+
136
+ button.click();
137
+ flushSync();
6
138
 
7
- function render(component) {
8
- mount(component, { target: container });
9
- }
139
+ expect(div.style.color).toBe('blue');
140
+ expect(div.style.fontWeight).toBe('bold');
141
+ });
10
142
 
11
- beforeEach(() => {
12
- container = document.createElement('div');
13
- document.body.appendChild(container);
14
- });
143
+ it('render dynamic boolean attributes', () => {
144
+ component Basic() {
145
+ let $disabled = false;
146
+ let $checked = false;
147
+ <button onClick={() => {
148
+ $disabled = !$disabled;
149
+ $checked = !$checked;
150
+ }}>{'Toggle'}</button>
151
+ <input type='checkbox' $disabled={$disabled} $checked={$checked} />
152
+ }
15
153
 
16
- afterEach(() => {
17
- document.body.removeChild(container);
18
- container = null;
19
- });
20
-
21
- it('render static text', () => {
22
- component Basic() {
23
- <div>{"Hello World"}</div>
24
- }
25
-
26
- render(Basic);
27
-
28
- expect(container).toMatchSnapshot();
29
- });
154
+ render(Basic);
155
+
156
+ const button = container.querySelector('button');
157
+ const input = container.querySelector('input');
30
158
 
31
- it('render static attributes', () => {
32
- component Basic() {
33
- <div class="foo" id="bar" style="color: red;">{"Hello World"}</div>
34
- }
159
+ expect(input.disabled).toBe(false);
160
+ expect(input.checked).toBe(false);
35
161
 
36
- render(Basic);
162
+ button.click();
163
+ flushSync();
37
164
 
38
- expect(container).toMatchSnapshot();
39
- });
40
-
41
- it('render semi-dynamic text', () => {
42
- component Basic() {
43
- let text = 'Hello World';
44
- <div>{text}</div>
45
- }
46
-
47
- render(Basic);
48
-
49
- expect(container).toMatchSnapshot();
50
- });
51
-
52
- it('render dynamic text', () => {
53
- component Basic() {
54
- let $text = 'Hello World';
55
-
56
- <button onClick={() => $text = 'Hello Ripple'}>{"Change Text"}</button>
57
-
58
- <div>{$text}</div>
59
- }
60
-
61
- render(Basic);
62
-
63
- const button = container.querySelector('button');
64
-
65
- button.click();
66
- flushSync();
67
-
68
- expect(container.querySelector('div').textContent).toEqual('Hello Ripple');
69
- });
70
-
71
- it('render dynamic class attribute', () => {
72
- component Basic() {
73
- let $active = false;
74
-
75
- <button onClick={() => $active = !$active}>{"Toggle"}</button>
76
-
77
- <div $class={$active ? 'active' : 'inactive'}>{"Dynamic Class"}</div>
78
- }
79
-
80
- render(Basic);
81
-
82
- const button = container.querySelector('button');
83
- const div = container.querySelector('div');
84
-
85
- expect(div.className).toBe('inactive');
86
-
87
- button.click();
88
- flushSync();
89
-
90
- expect(div.className).toBe('active');
91
-
92
- button.click();
93
- flushSync();
94
-
95
- expect(div.className).toBe('inactive');
96
- });
97
-
98
- it('render dynamic id attribute', () => {
99
- component Basic() {
100
- let $count = 0;
101
-
102
- <button onClick={() => $count++}>{"Increment"}</button>
103
-
104
- <div $id={`item-${$count}`}>{"Dynamic ID"}</div>
105
- }
106
-
107
- render(Basic);
108
-
109
- const button = container.querySelector('button');
110
- const div = container.querySelector('div');
111
-
112
- expect(div.id).toBe('item-0');
113
-
114
- button.click();
115
- flushSync();
116
-
117
- expect(div.id).toBe('item-1');
118
-
119
- button.click();
120
- flushSync();
121
-
122
- expect(div.id).toBe('item-2');
123
- });
124
-
125
- it('render dynamic style attribute', () => {
126
- component Basic() {
127
- let $color = 'red';
128
-
129
- <button onClick={() => $color = $color === 'red' ? 'blue' : 'red'}>{"Change Color"}</button>
130
-
131
- <div $style={`color: ${$color}; font-weight: bold;`}>{"Dynamic Style"}</div>
132
- }
133
-
134
- render(Basic);
135
-
136
- const button = container.querySelector('button');
137
- const div = container.querySelector('div');
138
-
139
- expect(div.style.color).toBe('red');
140
- expect(div.style.fontWeight).toBe('bold');
141
-
142
- button.click();
143
- flushSync();
144
-
145
- expect(div.style.color).toBe('blue');
146
- expect(div.style.fontWeight).toBe('bold');
147
- });
148
-
149
- it('render dynamic boolean attributes', () => {
150
- component Basic() {
151
- let $disabled = false;
152
- let $checked = false;
153
-
154
- <button onClick={() => { $disabled = !$disabled; $checked = !$checked; }}>{"Toggle"}</button>
155
-
156
- <input type="checkbox" $disabled={$disabled} $checked={$checked} />
157
- }
158
-
159
- render(Basic);
160
-
161
- const button = container.querySelector('button');
162
- const input = container.querySelector('input');
163
-
164
- expect(input.disabled).toBe(false);
165
- expect(input.checked).toBe(false);
166
-
167
- button.click();
168
- flushSync();
169
-
170
- expect(input.disabled).toBe(true);
171
- expect(input.checked).toBe(true);
172
- });
173
-
174
- it('render multiple dynamic attributes', () => {
175
- component Basic() {
176
- let $theme = 'light';
177
- let $size = 'medium';
178
-
179
- <button onClick={() => {
180
- $theme = $theme === 'light' ? 'dark' : 'light';
181
- $size = $size === 'medium' ? 'large' : 'medium';
182
- }}>{"Toggle Theme & Size"}</button>
183
-
184
- <div
185
- $class={`theme-${$theme} size-${$size}`}
186
- $data-theme={$theme}
187
- $data-size={$size}
188
- >
189
- {"Multiple Dynamic Attributes"}
190
- </div>
191
- }
192
-
193
- render(Basic);
194
-
195
- const button = container.querySelector('button');
196
- const div = container.querySelector('div');
197
-
198
- expect(div.className).toBe('theme-light size-medium');
199
- expect(div.getAttribute('data-theme')).toBe('light');
200
- expect(div.getAttribute('data-size')).toBe('medium');
201
-
202
- button.click();
203
- flushSync();
204
-
205
- expect(div.className).toBe('theme-dark size-large');
206
- expect(div.getAttribute('data-theme')).toBe('dark');
207
- expect(div.getAttribute('data-size')).toBe('large');
208
- });
209
-
210
- it('render conditional attributes', () => {
211
- component Basic() {
212
- let $showTitle = false;
213
- let $showAria = false;
214
-
215
- <button onClick={() => { $showTitle = !$showTitle; $showAria = !$showAria; }}>{"Toggle Attributes"}</button>
216
-
217
- <div
218
- $title={$showTitle ? 'This is a title' : null}
219
- $aria-label={$showAria ? 'Accessible label' : null}
220
- >
221
- {"Conditional Attributes"}
222
- </div>
223
- }
224
-
225
- render(Basic);
226
-
227
- const button = container.querySelector('button');
228
- const div = container.querySelector('div');
229
-
230
- expect(div.hasAttribute('title')).toBe(false);
231
- expect(div.hasAttribute('aria-label')).toBe(false);
232
-
233
- button.click();
234
- flushSync();
235
-
236
- expect(div.getAttribute('title')).toBe('This is a title');
237
- expect(div.getAttribute('aria-label')).toBe('Accessible label');
238
-
239
- button.click();
240
- flushSync();
241
-
242
- expect(div.hasAttribute('title')).toBe(false);
243
- expect(div.hasAttribute('aria-label')).toBe(false);
244
- });
245
-
246
- it('render spread attributes', () => {
247
- component Basic() {
248
- let $attrs = { class: 'initial', id: 'test-1' };
249
-
250
- <button onClick={() => {
251
- $attrs = { class: 'updated', id: 'test-2', 'data-extra': 'value' };
252
- }}>{"Update Attributes"}</button>
253
-
254
- <div {...$attrs}>{"Spread Attributes"}</div>
255
- }
256
-
257
- render(Basic);
258
-
259
- const button = container.querySelector('button');
260
- const div = container.querySelector('div');
261
-
262
- expect(div.className).toBe('initial');
263
- expect(div.id).toBe('test-1');
264
- expect(div.hasAttribute('data-extra')).toBe(false);
265
-
266
- button.click();
267
- flushSync();
268
-
269
- expect(div.className).toBe('updated');
270
- expect(div.id).toBe('test-2');
271
- expect(div.getAttribute('data-extra')).toBe('value');
272
- });
273
- });
165
+ expect(input.disabled).toBe(true);
166
+ expect(input.checked).toBe(true);
167
+ });
168
+
169
+ it('render multiple dynamic attributes', () => {
170
+ component Basic() {
171
+ let $theme = 'light';
172
+ let $size = 'medium';
173
+ <button
174
+ onClick={() => {
175
+ $theme = $theme === 'light' ? 'dark' : 'light';
176
+ $size = $size === 'medium' ? 'large' : 'medium';
177
+ }}
178
+ >{'Toggle Theme & Size'}</button>
179
+ <div $class={`theme-${$theme} size-${$size}`} $data-theme={$theme} $data-size={$size}>{'Multiple Dynamic Attributes'}</div>
180
+ }
181
+
182
+ render(Basic);
183
+
184
+ const button = container.querySelector('button');
185
+ const div = container.querySelector('div');
186
+
187
+ expect(div.className).toBe('theme-light size-medium');
188
+ expect(div.getAttribute('data-theme')).toBe('light');
189
+ expect(div.getAttribute('data-size')).toBe('medium');
190
+
191
+ button.click();
192
+ flushSync();
193
+
194
+ expect(div.className).toBe('theme-dark size-large');
195
+ expect(div.getAttribute('data-theme')).toBe('dark');
196
+ expect(div.getAttribute('data-size')).toBe('large');
197
+ });
198
+
199
+ it('render conditional attributes', () => {
200
+ component Basic() {
201
+ let $showTitle = false;
202
+ let $showAria = false;
203
+ <button onClick={() => {
204
+ $showTitle = !$showTitle;
205
+ $showAria = !$showAria;
206
+ }}>{'Toggle Attributes'}</button>
207
+ <div
208
+ $title={$showTitle ? 'This is a title' : null}
209
+ $aria-label={$showAria ? 'Accessible label' : null}
210
+ >{'Conditional Attributes'}</div>
211
+ }
212
+
213
+ render(Basic);
214
+
215
+ const button = container.querySelector('button');
216
+ const div = container.querySelector('div');
217
+
218
+ expect(div.hasAttribute('title')).toBe(false);
219
+ expect(div.hasAttribute('aria-label')).toBe(false);
220
+
221
+ button.click();
222
+ flushSync();
223
+
224
+ expect(div.getAttribute('title')).toBe('This is a title');
225
+ expect(div.getAttribute('aria-label')).toBe('Accessible label');
226
+
227
+ button.click();
228
+ flushSync();
229
+
230
+ expect(div.hasAttribute('title')).toBe(false);
231
+ expect(div.hasAttribute('aria-label')).toBe(false);
232
+ });
233
+
234
+ it('render spread attributes', () => {
235
+ component Basic() {
236
+ let $attrs = {
237
+ class: 'initial',
238
+ id: 'test-1'
239
+ };
240
+ <button
241
+ onClick={() => {
242
+ $attrs = {
243
+ class: 'updated',
244
+ id: 'test-2',
245
+ 'data-extra': 'value'
246
+ };
247
+ }}
248
+ >{'Update Attributes'}</button>
249
+ <div {...$attrs}>{'Spread Attributes'}</div>
250
+ }
251
+
252
+ render(Basic);
253
+
254
+ const button = container.querySelector('button');
255
+ const div = container.querySelector('div');
256
+
257
+ expect(div.className).toBe('initial');
258
+ expect(div.id).toBe('test-1');
259
+ expect(div.hasAttribute('data-extra')).toBe(false);
260
+
261
+ button.click();
262
+ flushSync();
263
+
264
+ expect(div.className).toBe('updated');
265
+ expect(div.id).toBe('test-2');
266
+ expect(div.getAttribute('data-extra')).toBe('value');
267
+ });
268
+
269
+ it('renders multiple reactive lexical blocks', () => {
270
+ component Basic() {
271
+ <div>
272
+ let obj = {
273
+ $count: 0
274
+ };
275
+
276
+ <span>{obj.$count}</span>
277
+ </div>
278
+ <div>
279
+ let b = {
280
+ $count: 0
281
+ };
282
+
283
+ <button onClick={() => b.$count--}>{'-'}</button>
284
+ <span class='count'>{b.$count}</span>
285
+ <button onClick={() => b.$count++}>{'+'}</button>
286
+ </div>
287
+ }
288
+ render(Basic);
289
+
290
+ const buttons = container.querySelectorAll('button');
291
+
292
+ buttons[0].click();
293
+ flushSync();
294
+
295
+ expect(container.querySelector('.count').textContent).toBe('-1');
296
+
297
+ buttons[1].click();
298
+ flushSync();
299
+
300
+ expect(container.querySelector('.count').textContent).toBe('0');
301
+ });
302
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+
3
+ import { mount, flushSync } from 'ripple';
4
+
5
+ describe('composite components', () => {
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
+ it('renders composite components with object state', () => {
25
+ component Button({ obj }) {
26
+ <button class='count2' onClick={() => {
27
+ obj.$count++;
28
+ }}>{obj.$count}</button>
29
+ }
30
+
31
+ component App() {
32
+ <div>
33
+ let obj = {
34
+ $count: 0
35
+ };
36
+ let $button = true;
37
+
38
+ <span class='count'>{obj.$count}</span>
39
+ <span>{' '}</span>
40
+ <Button obj={obj} />
41
+ </div>
42
+ }
43
+
44
+ render(App);
45
+
46
+ const button = container.querySelector('button');
47
+
48
+ button.click();
49
+ flushSync();
50
+
51
+ expect(container.querySelector('.count').textContent).toBe('1');
52
+ expect(container.querySelector('.count2').textContent).toBe('1');
53
+ });
54
+
55
+ it('renders composite components with object state', () => {
56
+ component Button({ obj }) {
57
+ <button class='count2' onClick={() => {
58
+ obj.$count++;
59
+ }}>{obj.$count}</button>
60
+ }
61
+
62
+ component App() {
63
+ <div>
64
+ let obj = {
65
+ $count: 0
66
+ };
67
+
68
+ <span class='count'>{obj.$count}</span>
69
+ <Button obj={obj} />
70
+ </div>
71
+ }
72
+
73
+ render(App);
74
+
75
+ const button = container.querySelector('button');
76
+
77
+ button.click();
78
+ flushSync();
79
+
80
+ expect(container.querySelector('.count').textContent).toBe('1');
81
+ expect(container.querySelector('.count2').textContent).toBe('1');
82
+ });
83
+
84
+ it('renders composite components with object state wrapped in an if statement', () => {
85
+ component Button({ obj }) {
86
+ <button class='count2' onClick={() => {
87
+ obj.$count++;
88
+ }}>{obj.$count}</button>
89
+ }
90
+
91
+ component OtherComponent({ obj }) {
92
+ <div class='count3'>{obj.$count}</div>
93
+ }
94
+
95
+ component App() {
96
+ <div>
97
+ let obj = {
98
+ $count: 0
99
+ };
100
+ let $button = true;
101
+
102
+ <span class='count'>{obj.$count}</span>
103
+ <span>{' '}</span>
104
+ if (obj) {
105
+ <Button obj={obj} />
106
+ }
107
+
108
+ if (obj) {
109
+ <OtherComponent obj={obj} />
110
+ }
111
+ </div>
112
+ }
113
+
114
+ render(App);
115
+
116
+ const button = container.querySelector('button');
117
+
118
+ button.click();
119
+ flushSync();
120
+
121
+ expect(container.querySelector('.count').textContent).toBe('1');
122
+ expect(container.querySelector('.count2').textContent).toBe('1');
123
+ expect(container.querySelector('.count3').textContent).toBe('1');
124
+ });
125
+ });