ripple 0.2.42 → 0.2.44
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 -5
- package/src/compiler/phases/3-transform/index.js +78 -40
- package/src/compiler/phases/3-transform/segments.js +2 -2
- package/src/runtime/array.js +393 -57
- package/src/runtime/index.js +1 -0
- package/src/runtime/internal/client/blocks.js +5 -5
- package/src/runtime/internal/client/constants.js +2 -1
- package/src/runtime/internal/client/index.js +3 -2
- package/src/runtime/internal/client/render.js +6 -6
- package/src/runtime/internal/client/runtime.js +52 -18
- 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 +180 -4
- package/tests/compiler.test.ripple +31 -1
- package/tests/{decorators.test.ripple → ref.test.ripple} +4 -4
- package/types/index.d.ts +2 -0
package/tests/basic.test.ripple
CHANGED
|
@@ -70,6 +70,45 @@ 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
|
+
|
|
91
|
+
it('render tick template literal for nested children', () => {
|
|
92
|
+
component Child({ $level, $children }) {
|
|
93
|
+
if($level == 1) {
|
|
94
|
+
<h1><$children /></h1>
|
|
95
|
+
}
|
|
96
|
+
if($level == 2) {
|
|
97
|
+
<h2><$children /></h2>
|
|
98
|
+
}
|
|
99
|
+
if($level == 3) {
|
|
100
|
+
<h3><$children /></h3>
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
component App() {
|
|
105
|
+
<Child $level={1}>{`Heading 1`} </Child>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render(App);
|
|
109
|
+
expect(container.querySelector('h1').textContent).toEqual('Heading 1');
|
|
110
|
+
});
|
|
111
|
+
|
|
73
112
|
it('render dynamic class attribute', () => {
|
|
74
113
|
component Basic() {
|
|
75
114
|
let $active = false;
|
|
@@ -145,6 +184,31 @@ describe('basic', () => {
|
|
|
145
184
|
expect(div.style.fontWeight).toBe('bold');
|
|
146
185
|
});
|
|
147
186
|
|
|
187
|
+
it('render spread props without duplication', () => {
|
|
188
|
+
component App() {
|
|
189
|
+
const checkBoxProp = {name:'car'}
|
|
190
|
+
|
|
191
|
+
<div>
|
|
192
|
+
<input {...checkBoxProp} type="checkbox" id="vehicle1" value="Bike" />
|
|
193
|
+
</div>
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
render(App);
|
|
197
|
+
|
|
198
|
+
const input = container.querySelector('input');
|
|
199
|
+
const html = container.innerHTML;
|
|
200
|
+
|
|
201
|
+
expect(input.getAttribute('name')).toBe('car');
|
|
202
|
+
expect(input.getAttribute('type')).toBe('checkbox');
|
|
203
|
+
expect(input.getAttribute('id')).toBe('vehicle1');
|
|
204
|
+
expect(input.getAttribute('value')).toBe('Bike');
|
|
205
|
+
|
|
206
|
+
expect(html).not.toContain('type="checkbox"type="checkbox"');
|
|
207
|
+
expect(html).not.toContain('value="Bike"value="Bike"');
|
|
208
|
+
|
|
209
|
+
expect(container).toMatchSnapshot();
|
|
210
|
+
});
|
|
211
|
+
|
|
148
212
|
it('render dynamic boolean attributes', () => {
|
|
149
213
|
component Basic() {
|
|
150
214
|
let $disabled = false;
|
|
@@ -350,7 +414,7 @@ describe('basic', () => {
|
|
|
350
414
|
let $focusCount = 0;
|
|
351
415
|
let $clickCount = 0;
|
|
352
416
|
|
|
353
|
-
<button
|
|
417
|
+
<button
|
|
354
418
|
onFocus={() => $focusCount++}
|
|
355
419
|
onClick={() => $clickCount++}
|
|
356
420
|
>{'Test Button'}</button>
|
|
@@ -465,7 +529,7 @@ describe('basic', () => {
|
|
|
465
529
|
it('renders with computed reactive state', () => {
|
|
466
530
|
component Basic() {
|
|
467
531
|
let $count = 5;
|
|
468
|
-
|
|
532
|
+
|
|
469
533
|
<div class='count'>{$count}</div>
|
|
470
534
|
<div class='doubled'>{$count * 2}</div>
|
|
471
535
|
<div class='is-even'>{$count % 2 === 0 ? 'Even' : 'Odd'}</div>
|
|
@@ -543,7 +607,7 @@ describe('basic', () => {
|
|
|
543
607
|
|
|
544
608
|
<button onClick={() => $showContent = !$showContent}>{'Toggle Content'}</button>
|
|
545
609
|
<button onClick={() => $userRole = $userRole === 'guest' ? 'admin' : 'guest'}>{'Toggle Role'}</button>
|
|
546
|
-
|
|
610
|
+
|
|
547
611
|
<div class='content'>
|
|
548
612
|
if ($showContent) {
|
|
549
613
|
if ($userRole === 'admin') {
|
|
@@ -655,7 +719,7 @@ describe('basic', () => {
|
|
|
655
719
|
<div class='counter'>{$counter}</div>
|
|
656
720
|
<div class='parity'>{$isEven ? 'Even' : 'Odd'}</div>
|
|
657
721
|
<div class='history-count'>{$history.length}</div>
|
|
658
|
-
|
|
722
|
+
|
|
659
723
|
<button class='inc-btn' onClick={handleIncrement}>{'+'}</button>
|
|
660
724
|
<button class='dec-btn' onClick={handleDecrement}>{'-'}</button>
|
|
661
725
|
<button class='reset-btn' onClick={handleReset}>{'Reset'}</button>
|
|
@@ -918,4 +982,116 @@ describe('basic', () => {
|
|
|
918
982
|
expect(divs[3].textContent).toBe('Even numbers: ');
|
|
919
983
|
expect(logs).toEqual(['0, 0', '1, 0', 'arr includes 1', '1, 1']);
|
|
920
984
|
});
|
|
985
|
+
|
|
986
|
+
it('it retains this context with bracketed prop functions and keeps original chaining', () => {
|
|
987
|
+
component App() {
|
|
988
|
+
const SYMBOL_PROP = Symbol();
|
|
989
|
+
let $hasError = false;
|
|
990
|
+
const obj = {
|
|
991
|
+
$count: 0,
|
|
992
|
+
increment() {
|
|
993
|
+
this.$count++;
|
|
994
|
+
},
|
|
995
|
+
[SYMBOL_PROP]() {
|
|
996
|
+
this.$count++;
|
|
997
|
+
},
|
|
998
|
+
arr: [() => obj.$count++, () => obj.$count--],
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
const obj2 = null;
|
|
1002
|
+
|
|
1003
|
+
<button onClick={() => obj['increment']()}>{'Increment'}</button>
|
|
1004
|
+
<button onClick={() => obj[SYMBOL_PROP]()}>{'Increment'}</button>
|
|
1005
|
+
<button
|
|
1006
|
+
onClick={() => {
|
|
1007
|
+
$hasError = false;
|
|
1008
|
+
try {
|
|
1009
|
+
obj['nonexistent']();
|
|
1010
|
+
} catch {
|
|
1011
|
+
$hasError = true;
|
|
1012
|
+
}
|
|
1013
|
+
}}
|
|
1014
|
+
>{'Nonexistent'}</button>
|
|
1015
|
+
<button
|
|
1016
|
+
onClick={() => {
|
|
1017
|
+
$hasError = false;
|
|
1018
|
+
try {
|
|
1019
|
+
obj['nonexistent']?.();
|
|
1020
|
+
} catch {
|
|
1021
|
+
$hasError = true;
|
|
1022
|
+
}
|
|
1023
|
+
}}
|
|
1024
|
+
>{'Nonexistent chaining'}</button>
|
|
1025
|
+
<button
|
|
1026
|
+
onClick={() => {
|
|
1027
|
+
$hasError = false;
|
|
1028
|
+
try {
|
|
1029
|
+
obj2['nonexistent']();
|
|
1030
|
+
} catch {
|
|
1031
|
+
$hasError = true;
|
|
1032
|
+
}
|
|
1033
|
+
}}
|
|
1034
|
+
>{'Object null'}</button>
|
|
1035
|
+
<button
|
|
1036
|
+
onClick={() => {
|
|
1037
|
+
$hasError = false;
|
|
1038
|
+
try {
|
|
1039
|
+
obj2?.['nonexistent']?.();
|
|
1040
|
+
} catch {
|
|
1041
|
+
$hasError = true;
|
|
1042
|
+
}
|
|
1043
|
+
}}
|
|
1044
|
+
>{'Object null chained'}</button>
|
|
1045
|
+
<button onClick={() => obj.arr[obj.arr.length - 1]()}>{'BinaryExpression prop'}</button>
|
|
1046
|
+
|
|
1047
|
+
<span>{obj.$count}</span>
|
|
1048
|
+
<span>{$hasError}</span>
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
render(App);
|
|
1052
|
+
|
|
1053
|
+
const button1 = container.querySelectorAll('button')[0];
|
|
1054
|
+
const button2 = container.querySelectorAll('button')[1];
|
|
1055
|
+
const button3 = container.querySelectorAll('button')[2];
|
|
1056
|
+
const button4 = container.querySelectorAll('button')[3];
|
|
1057
|
+
const button5 = container.querySelectorAll('button')[4];
|
|
1058
|
+
const button6 = container.querySelectorAll('button')[5];
|
|
1059
|
+
const button7 = container.querySelectorAll('button')[6];
|
|
1060
|
+
|
|
1061
|
+
const countSpan = container.querySelectorAll('span')[0];
|
|
1062
|
+
const errorSpan = container.querySelectorAll('span')[1];
|
|
1063
|
+
|
|
1064
|
+
expect(countSpan.textContent).toBe('0');
|
|
1065
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1066
|
+
|
|
1067
|
+
button1.click();
|
|
1068
|
+
flushSync();
|
|
1069
|
+
|
|
1070
|
+
expect(countSpan.textContent).toBe('1');
|
|
1071
|
+
|
|
1072
|
+
button2.click();
|
|
1073
|
+
flushSync();
|
|
1074
|
+
|
|
1075
|
+
expect(countSpan.textContent).toBe('2');
|
|
1076
|
+
|
|
1077
|
+
button3.click();
|
|
1078
|
+
flushSync();
|
|
1079
|
+
expect(errorSpan.textContent).toBe('true');
|
|
1080
|
+
|
|
1081
|
+
button4.click();
|
|
1082
|
+
flushSync();
|
|
1083
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1084
|
+
|
|
1085
|
+
button5.click();
|
|
1086
|
+
flushSync();
|
|
1087
|
+
expect(errorSpan.textContent).toBe('true');
|
|
1088
|
+
|
|
1089
|
+
button6.click();
|
|
1090
|
+
flushSync();
|
|
1091
|
+
expect(errorSpan.textContent).toBe('false');
|
|
1092
|
+
|
|
1093
|
+
button7.click();
|
|
1094
|
+
flushSync();
|
|
1095
|
+
expect(countSpan.textContent).toBe('1');
|
|
1096
|
+
});
|
|
921
1097
|
});
|
|
@@ -178,4 +178,34 @@ describe('compiler success tests', () => {
|
|
|
178
178
|
124, 123, 124, 123, 124, 123, 124, 123
|
|
179
179
|
]);
|
|
180
180
|
});
|
|
181
|
-
|
|
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
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { mount, flushSync, RippleArray } from 'ripple';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
4
|
+
describe('refs', () => {
|
|
5
5
|
let container;
|
|
6
6
|
|
|
7
7
|
function render(component) {
|
|
@@ -24,7 +24,7 @@ describe('@use element decorators', () => {
|
|
|
24
24
|
let div;
|
|
25
25
|
|
|
26
26
|
component Component() {
|
|
27
|
-
<div {
|
|
27
|
+
<div {ref (node) => { div = node; }}>{'Hello World'}</div>
|
|
28
28
|
}
|
|
29
29
|
render(Component);
|
|
30
30
|
flushSync();
|
|
@@ -37,11 +37,11 @@ describe('@use element decorators', () => {
|
|
|
37
37
|
component Component() {
|
|
38
38
|
let items = RippleArray.from([1, 2, 3]);
|
|
39
39
|
|
|
40
|
-
function
|
|
40
|
+
function componentRef(node) {
|
|
41
41
|
_node = node;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
<Child {
|
|
44
|
+
<Child {ref componentRef} {items} />
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
component Child(props) {
|