ripple 0.2.184 → 0.2.185

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.
@@ -5,8 +5,8 @@ describe('switch statements', () => {
5
5
  component App() {
6
6
  let value = track('b');
7
7
 
8
- <button onClick={() => @value = 'c'}>{'Change to C'}</button>
9
- <button onClick={() => @value = 'a'}>{'Change to A'}</button>
8
+ <button onClick={() => (@value = 'c')}>{'Change to C'}</button>
9
+ <button onClick={() => (@value = 'a')}>{'Change to A'}</button>
10
10
 
11
11
  switch (@value) {
12
12
  case 'a':
@@ -24,15 +24,16 @@ describe('switch statements', () => {
24
24
  }
25
25
 
26
26
  render(App);
27
- expect(container.textContent).toBe('Change to CChange to ACase B');
27
+ expect(container.querySelector('div').textContent).toBe('Case B');
28
28
 
29
- container.querySelectorAll('button')[0].click(); // Change to C
29
+ container.querySelectorAll('button')[0].click();
30
30
  flushSync();
31
- expect(container.textContent).toBe('Change to CChange to ACase C');
32
31
 
33
- container.querySelectorAll('button')[1].click(); // Change to A
32
+ expect(container.querySelector('div').textContent).toBe('Case C');
33
+ container.querySelectorAll('button')[1].click();
34
34
  flushSync();
35
- expect(container.textContent).toBe('Change to CChange to ACase A');
35
+
36
+ expect(container.querySelector('div').textContent).toBe('Case A');
36
37
  });
37
38
 
38
39
  it('renders switch with reactive discriminant', () => {
@@ -54,22 +55,24 @@ describe('switch statements', () => {
54
55
  }
55
56
 
56
57
  render(App);
57
- expect(container.textContent).toBe('IncrementCount is 1');
58
+ expect(container.querySelector('div').textContent).toBe('Count is 1');
58
59
 
59
60
  container.querySelector('button').click();
60
61
  flushSync();
61
- expect(container.textContent).toBe('IncrementCount is 2');
62
+
63
+ expect(container.querySelector('div').textContent).toBe('Count is 2');
62
64
 
63
65
  container.querySelector('button').click();
64
66
  flushSync();
65
- expect(container.textContent).toBe('IncrementCount is other');
67
+
68
+ expect(container.querySelector('div').textContent).toBe('Count is other');
66
69
  });
67
70
 
68
71
  it('renders switch with default clause only', () => {
69
72
  component App() {
70
73
  let value = track('x');
71
74
 
72
- <button onClick={() => @value = 'y'}>{'Change Value'}</button>
75
+ <button onClick={() => (@value = 'y')}>{'Change Value'}</button>
73
76
 
74
77
  switch (@value) {
75
78
  default:
@@ -78,11 +81,54 @@ describe('switch statements', () => {
78
81
  }
79
82
 
80
83
  render(App);
81
- expect(container.textContent).toBe('Change ValueDefault for x');
84
+ expect(container.querySelector('div').textContent).toBe('Default for x');
82
85
 
83
86
  container.querySelector('button').click();
84
87
  flushSync();
85
- expect(container.textContent).toBe('Change ValueDefault for y');
88
+
89
+ expect(container.querySelector('div').textContent).toBe('Default for y');
90
+ });
91
+
92
+ it('renders switch using fallthrough without recreating DOM unnecessarily', () => {
93
+ component App() {
94
+ let value = track('a');
95
+
96
+ <button onClick={() => (@value = 'b')}>{'Change to B'}</button>
97
+ <button onClick={() => (@value = 'c')}>{'Change to C'}</button>
98
+
99
+ switch (@value) {
100
+ case 'a':
101
+ <div>{'Case A'}</div>
102
+ break;
103
+ case 'b':
104
+ case 'c':
105
+ <div>{'Case B or C'}</div>
106
+ break;
107
+ default:
108
+ <div>{'Default Case'}</div>
109
+ }
110
+ }
111
+
112
+ render(App);
113
+ const [buttonB, buttonC] = container.querySelectorAll('button');
114
+
115
+ expect(container.querySelector('div').textContent).toContain('Case A');
116
+
117
+ buttonB.click();
118
+ flushSync();
119
+
120
+ expect(container.querySelector('div').textContent).toBe('Case B or C');
121
+
122
+ buttonC.click();
123
+ flushSync();
124
+
125
+ expect(container.querySelector('div').textContent).toBe('Case B or C');
126
+
127
+ container.querySelector('div').textContent = 'DOM check';
128
+ buttonB.click();
129
+ flushSync();
130
+
131
+ expect(container.querySelector('div').textContent).toBe('DOM check');
86
132
  });
87
133
 
88
134
  it('renders switch with template content and JS logic', () => {
@@ -90,9 +136,9 @@ describe('switch statements', () => {
90
136
  let status = track('active');
91
137
  let message = track('');
92
138
 
93
- <button onClick={() => @status = 'pending'}>{'Pending'}</button>
94
- <button onClick={() => @status = 'completed'}>{'Completed'}</button>
95
- <button onClick={() => @status = 'error'}>{'Error'}</button>
139
+ <button onClick={() => (@status = 'pending')}>{'Pending'}</button>
140
+ <button onClick={() => (@status = 'completed')}>{'Completed'}</button>
141
+ <button onClick={() => (@status = 'error')}>{'Error'}</button>
96
142
 
97
143
  switch (@status) {
98
144
  case 'active':
@@ -114,20 +160,24 @@ describe('switch statements', () => {
114
160
  }
115
161
 
116
162
  render(App);
117
- expect(container.textContent).toBe('PendingCompletedErrorStatus: Currently active.');
163
+ const [buttonPending, buttonCompleted, buttonError] = container.querySelectorAll('button');
164
+ expect(container.querySelector('div').textContent).toBe('Status: Currently active.');
118
165
 
119
- container.querySelectorAll('button')[0].click(); // Pending
166
+ buttonPending.click();
120
167
  flushSync();
121
- expect(container.textContent).toBe('PendingCompletedErrorStatus: Waiting for completion.');
122
168
 
123
- container.querySelectorAll('button')[1].click(); // Completed
169
+ expect(container.querySelector('div').textContent).toBe('Status: Waiting for completion.');
170
+
171
+ buttonCompleted.click();
124
172
  flushSync();
125
- expect(container.textContent).toBe('PendingCompletedErrorStatus: Task finished!');
173
+
174
+ expect(container.querySelector('div').textContent).toBe('Status: Task finished!');
126
175
  expect(container.querySelector('.success')).toBeTruthy();
127
176
 
128
- container.querySelectorAll('button')[2].click(); // Error
177
+ buttonError.click();
129
178
  flushSync();
130
- expect(container.textContent).toBe('PendingCompletedErrorStatus: An error occurred.');
179
+
180
+ expect(container.querySelector('div').textContent).toBe('Status: An error occurred.');
131
181
  expect(container.querySelector('.error')).toBeTruthy();
132
182
  });
133
183
  });
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { render } from 'ripple/server';
3
+ import { track } from 'ripple';
3
4
 
4
5
  describe('basic client', () => {
5
6
  it('render static text', async () => {
@@ -12,4 +13,122 @@ describe('basic client', () => {
12
13
  expect(head).toBe('');
13
14
  expect(body).toBe('<div>Hello World</div>');
14
15
  });
16
+
17
+ it('renders tracked state updates', async () => {
18
+ component Counter() {
19
+ const count = track(0);
20
+
21
+ @count++;
22
+ @count = @count + 5;
23
+
24
+ <div>{@count}</div>
25
+ }
26
+
27
+ const { body } = await render(Counter);
28
+
29
+ expect(body).toBe('<div>6</div>');
30
+ });
31
+
32
+ it('renders dynamic component with tracked props', async () => {
33
+ component Child({ count, ...rest }) {
34
+ <div {...rest}>{'Child Component'}</div>
35
+ <div>{@count}</div>
36
+ }
37
+
38
+ component Parent() {
39
+ const count = track(10);
40
+ let Dynamic = track(() => Child);
41
+
42
+ <@Dynamic {count} class={{ test: true }} />
43
+ }
44
+
45
+ const { body } = await render(Parent);
46
+
47
+ expect(body).toBe('<div class="test">Child Component</div><div>10</div>');
48
+ });
49
+
50
+ it('renders tracked object properties', async () => {
51
+ component ObjCounter() {
52
+ const obj = { count: track(2) };
53
+
54
+ obj.@count += 3;
55
+ obj.@count = obj.@count + 1;
56
+
57
+ <div>{obj.@count}</div>
58
+ }
59
+
60
+ const { body } = await render(ObjCounter);
61
+
62
+ expect(body).toBe('<div>6</div>');
63
+ });
64
+
65
+ it('renders spread props with tracked values', async () => {
66
+ component SpreadProps() {
67
+ const id = track('unique-id');
68
+ const isActive = track(true);
69
+ const styles = track({ color: 'red', fontSize: '16px' });
70
+
71
+ <div {...{ id: @id, class: { active: @isActive }, style: @styles }}>{'Spread Props'}</div>
72
+ }
73
+
74
+ const { body } = await render(SpreadProps);
75
+
76
+ expect(body).toBe(
77
+ '<div id="unique-id" class="active" style="color: red; font-size: 16px;">Spread Props</div>',
78
+ );
79
+ });
80
+
81
+ it('handles AssignExpressions with tracked values or properties correctly', async () => {
82
+ component Assignments() {
83
+ const count = track(0);
84
+ const obj = { value: track(5) };
85
+
86
+ @count += 10;
87
+ obj.@value *= 2;
88
+
89
+ <div>{@count}</div>
90
+ <div>{obj.@value}</div>
91
+ }
92
+
93
+ const { body } = await render(Assignments);
94
+
95
+ expect(body).toBe('<div>10</div><div>10</div>');
96
+ });
97
+
98
+ it(`handles derived changes via tracked dependencies' changes`, async () => {
99
+ component Derived() {
100
+ let base = track(5);
101
+ let multiplier = track(3);
102
+ const derived = track(() => @base * @multiplier);
103
+
104
+ <div>{@derived}</div>
105
+
106
+ @base += 2;
107
+
108
+ <div>{@derived}</div>
109
+ }
110
+
111
+ const { body } = await render(Derived);
112
+
113
+ expect(body).toBe('<div>15</div><div>21</div>');
114
+ });
115
+
116
+ it(`handles derived changes based on another derived's dependencies' changes`, async () => {
117
+ component NestedDerived() {
118
+ let a = track(2);
119
+ let b = track(3);
120
+ const sum = track(() => @a + @b);
121
+ const product = track(() => @sum * 2);
122
+
123
+ <div>{@product}</div>
124
+
125
+ @a = 4;
126
+
127
+ <div>{@product}</div>
128
+ }
129
+
130
+ const { body } = await render(NestedDerived);
131
+
132
+ expect(body).toBe('<div>10</div><div>14</div>');
133
+ });
15
134
  });
@@ -76,7 +76,7 @@ describe('generics', () => {
76
76
 
77
77
  // 14. Generic with constraint + default
78
78
  type Extractor<T extends { id: number } = { id: number }> = (v: T) => number;
79
- const m: Extractor = (v) => v.id;
79
+ const m: Extractor<{ id: number }> = (v) => v.id;
80
80
 
81
81
  // 15. Generic in angle after "new" + trailing call
82
82
  class Wrapper<T> {
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from 'ripple/server';
3
+ import { Context } from 'ripple';
4
+
5
+ describe('Context API', () => {
6
+ it('handles context override in nested components', async () => {
7
+ const MessageContext = new Context('default');
8
+
9
+ component Inner() {
10
+ const msg = MessageContext.get();
11
+ <span>{msg}</span>
12
+ }
13
+
14
+ component Middle() {
15
+ MessageContext.set('middle');
16
+ <div>
17
+ <Inner />
18
+ </div>
19
+ }
20
+
21
+ component Outer() {
22
+ MessageContext.set('outer');
23
+ <Middle />
24
+ <Inner />
25
+ }
26
+
27
+ const { body } = await render(Outer);
28
+
29
+ expect(body).toBe('<div><span>middle</span></div><span>outer</span>');
30
+ });
31
+ });
@@ -24,4 +24,25 @@ describe('SSR: switch statements', () => {
24
24
  const { body } = await render(App);
25
25
  expect(body).toBe('<div>Case B</div>');
26
26
  });
27
+
28
+ it('renders switch using fallthrough case', async () => {
29
+ component App() {
30
+ let value = 'b';
31
+
32
+ switch (value) {
33
+ case 'a':
34
+ <div>{'Case A'}</div>
35
+ break;
36
+ case 'b':
37
+ case 'c':
38
+ <div>{'Case B or C'}</div>
39
+ break;
40
+ default:
41
+ <div>{'Default Case'}</div>
42
+ }
43
+ }
44
+
45
+ const { body } = await render(App);
46
+ expect(body).toBe('<div>Case B or C</div>');
47
+ });
27
48
  });