ripple 0.2.41 → 0.2.43
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.
- package/package.json +1 -1
- package/src/compiler/phases/1-parse/index.js +4 -4
- package/src/compiler/phases/3-transform/index.js +77 -29
- package/src/runtime/array.js +393 -57
- package/src/runtime/internal/client/constants.js +1 -0
- package/src/runtime/internal/client/index.js +1 -0
- package/src/runtime/internal/client/runtime.js +49 -15
- package/src/runtime/internal/client/utils.js +18 -0
- package/tests/__snapshots__/basic.test.ripple.snap +14 -0
- package/tests/array.test.ripple +1424 -10
- package/tests/basic.test.ripple +159 -4
- package/tests/compiler.test.ripple +56 -1
package/tests/basic.test.ripple
CHANGED
|
@@ -70,6 +70,24 @@ describe('basic', () => {
|
|
|
70
70
|
expect(container.querySelector('div').textContent).toEqual('Hello Ripple');
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('render empty string literal', () => {
|
|
74
|
+
component Basic() {
|
|
75
|
+
<div>{''}</div>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render(Basic);
|
|
79
|
+
expect(container.querySelector('div').textContent).toEqual('');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('render empty template literal', () => {
|
|
83
|
+
component Basic() {
|
|
84
|
+
<div>{``}</div>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
render(Basic);
|
|
88
|
+
expect(container.querySelector('div').textContent).toEqual('');
|
|
89
|
+
});
|
|
90
|
+
|
|
73
91
|
it('render dynamic class attribute', () => {
|
|
74
92
|
component Basic() {
|
|
75
93
|
let $active = false;
|
|
@@ -145,6 +163,31 @@ describe('basic', () => {
|
|
|
145
163
|
expect(div.style.fontWeight).toBe('bold');
|
|
146
164
|
});
|
|
147
165
|
|
|
166
|
+
it('render spread props without duplication', () => {
|
|
167
|
+
component App() {
|
|
168
|
+
const checkBoxProp = {name:'car'}
|
|
169
|
+
|
|
170
|
+
<div>
|
|
171
|
+
<input {...checkBoxProp} type="checkbox" id="vehicle1" value="Bike" />
|
|
172
|
+
</div>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
render(App);
|
|
176
|
+
|
|
177
|
+
const input = container.querySelector('input');
|
|
178
|
+
const html = container.innerHTML;
|
|
179
|
+
|
|
180
|
+
expect(input.getAttribute('name')).toBe('car');
|
|
181
|
+
expect(input.getAttribute('type')).toBe('checkbox');
|
|
182
|
+
expect(input.getAttribute('id')).toBe('vehicle1');
|
|
183
|
+
expect(input.getAttribute('value')).toBe('Bike');
|
|
184
|
+
|
|
185
|
+
expect(html).not.toContain('type="checkbox"type="checkbox"');
|
|
186
|
+
expect(html).not.toContain('value="Bike"value="Bike"');
|
|
187
|
+
|
|
188
|
+
expect(container).toMatchSnapshot();
|
|
189
|
+
});
|
|
190
|
+
|
|
148
191
|
it('render dynamic boolean attributes', () => {
|
|
149
192
|
component Basic() {
|
|
150
193
|
let $disabled = false;
|
|
@@ -350,7 +393,7 @@ describe('basic', () => {
|
|
|
350
393
|
let $focusCount = 0;
|
|
351
394
|
let $clickCount = 0;
|
|
352
395
|
|
|
353
|
-
<button
|
|
396
|
+
<button
|
|
354
397
|
onFocus={() => $focusCount++}
|
|
355
398
|
onClick={() => $clickCount++}
|
|
356
399
|
>{'Test Button'}</button>
|
|
@@ -465,7 +508,7 @@ describe('basic', () => {
|
|
|
465
508
|
it('renders with computed reactive state', () => {
|
|
466
509
|
component Basic() {
|
|
467
510
|
let $count = 5;
|
|
468
|
-
|
|
511
|
+
|
|
469
512
|
<div class='count'>{$count}</div>
|
|
470
513
|
<div class='doubled'>{$count * 2}</div>
|
|
471
514
|
<div class='is-even'>{$count % 2 === 0 ? 'Even' : 'Odd'}</div>
|
|
@@ -543,7 +586,7 @@ describe('basic', () => {
|
|
|
543
586
|
|
|
544
587
|
<button onClick={() => $showContent = !$showContent}>{'Toggle Content'}</button>
|
|
545
588
|
<button onClick={() => $userRole = $userRole === 'guest' ? 'admin' : 'guest'}>{'Toggle Role'}</button>
|
|
546
|
-
|
|
589
|
+
|
|
547
590
|
<div class='content'>
|
|
548
591
|
if ($showContent) {
|
|
549
592
|
if ($userRole === 'admin') {
|
|
@@ -655,7 +698,7 @@ describe('basic', () => {
|
|
|
655
698
|
<div class='counter'>{$counter}</div>
|
|
656
699
|
<div class='parity'>{$isEven ? 'Even' : 'Odd'}</div>
|
|
657
700
|
<div class='history-count'>{$history.length}</div>
|
|
658
|
-
|
|
701
|
+
|
|
659
702
|
<button class='inc-btn' onClick={handleIncrement}>{'+'}</button>
|
|
660
703
|
<button class='dec-btn' onClick={handleDecrement}>{'-'}</button>
|
|
661
704
|
<button class='reset-btn' onClick={handleReset}>{'Reset'}</button>
|
|
@@ -918,4 +961,116 @@ describe('basic', () => {
|
|
|
918
961
|
expect(divs[3].textContent).toBe('Even numbers: ');
|
|
919
962
|
expect(logs).toEqual(['0, 0', '1, 0', 'arr includes 1', '1, 1']);
|
|
920
963
|
});
|
|
964
|
+
|
|
965
|
+
it('it retains this context with bracketed prop functions and keeps original chaining', () => {
|
|
966
|
+
component App() {
|
|
967
|
+
const SYMBOL_PROP = Symbol();
|
|
968
|
+
let $hasError = false;
|
|
969
|
+
const obj = {
|
|
970
|
+
$count: 0,
|
|
971
|
+
increment() {
|
|
972
|
+
this.$count++;
|
|
973
|
+
},
|
|
974
|
+
[SYMBOL_PROP]() {
|
|
975
|
+
this.$count++;
|
|
976
|
+
},
|
|
977
|
+
arr: [() => obj.$count++, () => obj.$count--],
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
const obj2 = null;
|
|
981
|
+
|
|
982
|
+
<button onClick={() => obj['increment']()}>{'Increment'}</button>
|
|
983
|
+
<button onClick={() => obj[SYMBOL_PROP]()}>{'Increment'}</button>
|
|
984
|
+
<button
|
|
985
|
+
onClick={() => {
|
|
986
|
+
$hasError = false;
|
|
987
|
+
try {
|
|
988
|
+
obj['nonexistent']();
|
|
989
|
+
} catch {
|
|
990
|
+
$hasError = true;
|
|
991
|
+
}
|
|
992
|
+
}}
|
|
993
|
+
>{'Nonexistent'}</button>
|
|
994
|
+
<button
|
|
995
|
+
onClick={() => {
|
|
996
|
+
$hasError = false;
|
|
997
|
+
try {
|
|
998
|
+
obj['nonexistent']?.();
|
|
999
|
+
} catch {
|
|
1000
|
+
$hasError = true;
|
|
1001
|
+
}
|
|
1002
|
+
}}
|
|
1003
|
+
>{'Nonexistent chaining'}</button>
|
|
1004
|
+
<button
|
|
1005
|
+
onClick={() => {
|
|
1006
|
+
$hasError = false;
|
|
1007
|
+
try {
|
|
1008
|
+
obj2['nonexistent']();
|
|
1009
|
+
} catch {
|
|
1010
|
+
$hasError = true;
|
|
1011
|
+
}
|
|
1012
|
+
}}
|
|
1013
|
+
>{'Object null'}</button>
|
|
1014
|
+
<button
|
|
1015
|
+
onClick={() => {
|
|
1016
|
+
$hasError = false;
|
|
1017
|
+
try {
|
|
1018
|
+
obj2?.['nonexistent']?.();
|
|
1019
|
+
} catch {
|
|
1020
|
+
$hasError = true;
|
|
1021
|
+
}
|
|
1022
|
+
}}
|
|
1023
|
+
>{'Object null chained'}</button>
|
|
1024
|
+
<button onClick={() => obj.arr[obj.arr.length - 1]()}>{'BinaryExpression prop'}</button>
|
|
1025
|
+
|
|
1026
|
+
<span>{obj.$count}</span>
|
|
1027
|
+
<span>{$hasError}</span>
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
render(App);
|
|
1031
|
+
|
|
1032
|
+
const button1 = container.querySelectorAll('button')[0];
|
|
1033
|
+
const button2 = container.querySelectorAll('button')[1];
|
|
1034
|
+
const button3 = container.querySelectorAll('button')[2];
|
|
1035
|
+
const button4 = container.querySelectorAll('button')[3];
|
|
1036
|
+
const button5 = container.querySelectorAll('button')[4];
|
|
1037
|
+
const button6 = container.querySelectorAll('button')[5];
|
|
1038
|
+
const button7 = container.querySelectorAll('button')[6];
|
|
1039
|
+
|
|
1040
|
+
const countSpan = container.querySelectorAll('span')[0];
|
|
1041
|
+
const errorSpan = container.querySelectorAll('span')[1];
|
|
1042
|
+
|
|
1043
|
+
expect(countSpan.textContent).toBe('0');
|
|
1044
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1045
|
+
|
|
1046
|
+
button1.click();
|
|
1047
|
+
flushSync();
|
|
1048
|
+
|
|
1049
|
+
expect(countSpan.textContent).toBe('1');
|
|
1050
|
+
|
|
1051
|
+
button2.click();
|
|
1052
|
+
flushSync();
|
|
1053
|
+
|
|
1054
|
+
expect(countSpan.textContent).toBe('2');
|
|
1055
|
+
|
|
1056
|
+
button3.click();
|
|
1057
|
+
flushSync();
|
|
1058
|
+
expect(errorSpan.textContent).toBe('true');
|
|
1059
|
+
|
|
1060
|
+
button4.click();
|
|
1061
|
+
flushSync();
|
|
1062
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1063
|
+
|
|
1064
|
+
button5.click();
|
|
1065
|
+
flushSync();
|
|
1066
|
+
expect(errorSpan.textContent).toBe('true');
|
|
1067
|
+
|
|
1068
|
+
button6.click();
|
|
1069
|
+
flushSync();
|
|
1070
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1071
|
+
|
|
1072
|
+
button7.click();
|
|
1073
|
+
flushSync();
|
|
1074
|
+
expect(countSpan.textContent).toBe('1');
|
|
1075
|
+
});
|
|
921
1076
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { mount, RippleArray } from 'ripple';
|
|
3
|
+
import { parse } from 'ripple/compiler'
|
|
3
4
|
|
|
4
5
|
describe('compiler success tests', () => {
|
|
5
6
|
let container;
|
|
@@ -20,6 +21,30 @@ describe('compiler success tests', () => {
|
|
|
20
21
|
container = null;
|
|
21
22
|
});
|
|
22
23
|
|
|
24
|
+
|
|
25
|
+
it('Parses style content correctly', () => {
|
|
26
|
+
const source = `export component App() {
|
|
27
|
+
<div id="myid" class="myclass">{"Hello World"}</div>
|
|
28
|
+
|
|
29
|
+
<style>#style</style>
|
|
30
|
+
}`;
|
|
31
|
+
const style1 = '.myid {color: green }';
|
|
32
|
+
const style2 = '#myid {color: green }';
|
|
33
|
+
const style3 = 'div {color: green }';
|
|
34
|
+
|
|
35
|
+
let input = source.replace('#style', style1);
|
|
36
|
+
let ast = parse(input);
|
|
37
|
+
expect(ast.body[0].declaration.css.source).toEqual(style1);
|
|
38
|
+
|
|
39
|
+
input = source.replace('#style', style2);
|
|
40
|
+
ast = parse(input);
|
|
41
|
+
expect(ast.body[0].declaration.css.source).toEqual(style2);
|
|
42
|
+
|
|
43
|
+
input = source.replace('#style', style3);
|
|
44
|
+
ast = parse(input);
|
|
45
|
+
expect(ast.body[0].declaration.css.source).toEqual(style3);
|
|
46
|
+
});
|
|
47
|
+
|
|
23
48
|
it('renders without crashing', () => {
|
|
24
49
|
component App() {
|
|
25
50
|
let foo;
|
|
@@ -153,4 +178,34 @@ describe('compiler success tests', () => {
|
|
|
153
178
|
124, 123, 124, 123, 124, 123, 124, 123
|
|
154
179
|
]);
|
|
155
180
|
});
|
|
156
|
-
|
|
181
|
+
|
|
182
|
+
it('renders without crashing using mapped types', () => {
|
|
183
|
+
component App() {
|
|
184
|
+
type RecordKey = 'test';
|
|
185
|
+
type RecordValue = { a: string, b: number };
|
|
186
|
+
|
|
187
|
+
const config: Record<RecordKey, RecordValue> = {
|
|
188
|
+
test: {
|
|
189
|
+
a: 'test',
|
|
190
|
+
b: 1
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const config2: { [key in RecordKey]: RecordValue } = {
|
|
195
|
+
test: {
|
|
196
|
+
a: 'test2',
|
|
197
|
+
b: 2
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const config3: { [key: RecordKey]: RecordValue } = {
|
|
202
|
+
test: {
|
|
203
|
+
a: 'test3',
|
|
204
|
+
b: 3
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
render(App);
|
|
210
|
+
});
|
|
211
|
+
});
|