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.
@@ -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('@use element decorators', () => {
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 {@use (node) => { div = node; }}>{'Hello World'}</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 ref(node) {
40
+ function componentRef(node) {
41
41
  _node = node;
42
42
  }
43
43
 
44
- <Child {@use ref} {items} />
44
+ <Child {ref componentRef} {items} />
45
45
  }
46
46
 
47
47
  component Child(props) {
package/types/index.d.ts CHANGED
@@ -79,3 +79,5 @@ declare global {
79
79
  // Add other runtime functions as needed for TypeScript analysis
80
80
  };
81
81
  }
82
+
83
+ export declare function createRefKey(): symbol;