ripple 0.3.33 → 0.3.35

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.35
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies []:
8
+ - ripple@0.3.35
9
+ - @tsrx/ripple@0.0.17
10
+
11
+ ## 0.3.34
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+ [[`fee8620`](https://github.com/Ripple-TS/ripple/commit/fee8620fa4e82a7c7e4adb3e434e9db552a3e157),
17
+ [`2fcacb4`](https://github.com/Ripple-TS/ripple/commit/2fcacb471d7780074f92b20c9b394f7650a941bb)]:
18
+ - @tsrx/ripple@0.0.16
19
+ - ripple@0.3.34
20
+
3
21
  ## 0.3.33
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.33",
6
+ "version": "0.3.35",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -76,7 +76,7 @@
76
76
  "esm-env": "^1.2.2",
77
77
  "@types/estree": "^1.0.8",
78
78
  "@types/estree-jsx": "^1.0.5",
79
- "@tsrx/ripple": "0.0.15"
79
+ "@tsrx/ripple": "0.0.17"
80
80
  },
81
81
  "devDependencies": {
82
82
  "@types/node": "^24.3.0",
@@ -84,9 +84,9 @@
84
84
  "typescript": "^5.9.3",
85
85
  "@volar/language-core": "~2.4.28",
86
86
  "vscode-languageserver-types": "^3.17.5",
87
- "@tsrx/core": "0.0.13"
87
+ "@tsrx/core": "0.0.15"
88
88
  },
89
89
  "peerDependencies": {
90
- "ripple": "0.3.33"
90
+ "ripple": "0.3.35"
91
91
  }
92
92
  }
@@ -249,6 +249,64 @@ describe('basic client > rendering & text', () => {
249
249
  expect(container).toMatchSnapshot();
250
250
  });
251
251
 
252
+ it('runs nested JavaScript blocks inside component-local callables', () => {
253
+ component App() {
254
+ function readFunction() {
255
+ const label = 'function outer';
256
+ let result = '';
257
+
258
+ {
259
+ const label = 'function inner';
260
+ result = label;
261
+ }
262
+
263
+ return `${result} / ${label}`;
264
+ }
265
+
266
+ const readArrow = () => {
267
+ const offset = 5;
268
+ let value = offset;
269
+
270
+ {
271
+ const offset = 17;
272
+ value += offset;
273
+ }
274
+
275
+ return value;
276
+ };
277
+
278
+ class Reader {
279
+ read() {
280
+ const label = 'method outer';
281
+ let result = '';
282
+
283
+ {
284
+ const label = 'method inner';
285
+ result = label;
286
+ }
287
+
288
+ return `${result} / ${label}`;
289
+ }
290
+ }
291
+
292
+ const reader = new Reader();
293
+
294
+ <div class="block-function">{readFunction()}</div>
295
+ <div class="block-arrow">{readArrow()}</div>
296
+ <div class="block-method">{reader.read()}</div>
297
+ }
298
+
299
+ render(App);
300
+
301
+ expect(container.querySelector('.block-function').textContent).toBe(
302
+ 'function inner / function outer',
303
+ );
304
+ expect(container.querySelector('.block-arrow').textContent).toBe('22');
305
+ expect(container.querySelector('.block-method').textContent).toBe(
306
+ 'method inner / method outer',
307
+ );
308
+ });
309
+
252
310
  it('should handle consecutive text nodes without duplication', () => {
253
311
  component App() {
254
312
  const Something = conditional('a');
@@ -391,6 +391,91 @@ component App() {
391
391
  expect(track_result).not.toContain('lazy0');
392
392
  });
393
393
 
394
+ it('preserves optional markers in to_ts TypeScript output', () => {
395
+ const source = `
396
+ export type OptionalTuple = [bar: string, baz?: string];
397
+ export type OptionalFn = (bar: string, baz?: string) => void;
398
+ export interface OptionalInterfaceFn {
399
+ (bar: string, baz?: string): void;
400
+ }
401
+ export function optionalFn(bar: string, baz?: string) {
402
+ todo(bar, baz);
403
+ }
404
+ `;
405
+ const result = compile_to_volar_mappings(source, 'test.tsrx').code;
406
+
407
+ expect(result).toContain('export type OptionalTuple = [bar: string, baz?: string];');
408
+ expect(result).toContain('export type OptionalFn = (bar: string, baz?: string) => void;');
409
+ expect(result).toContain('(bar: string, baz?: string): void');
410
+ expect(result).toContain('export function optionalFn(bar: string, baz?: string)');
411
+ });
412
+
413
+ it('preserves component type parameters in to_ts output', () => {
414
+ const source = `
415
+ type Props<Item> = {
416
+ items: readonly Item[];
417
+ }
418
+
419
+ export component MyComponent<Item>(props: Props<Item>) {
420
+ <div />
421
+ }
422
+ `;
423
+ const result = compile_to_volar_mappings(source, 'test.tsrx').code;
424
+
425
+ expect(result).toContain('export function MyComponent<Item>(props: Props<Item>)');
426
+ });
427
+
428
+ it('preserves regular function type parameters in to_ts output', () => {
429
+ const source = `
430
+ type Props<Item> = {
431
+ items: readonly Item[];
432
+ }
433
+
434
+ export function getItems<Item>(props: Props<Item>) {
435
+ return props.items;
436
+ }
437
+ `;
438
+ const result = compile_to_volar_mappings(source, 'test.tsrx').code;
439
+
440
+ expect(result).toContain('export function getItems<Item>(props: Props<Item>)');
441
+ });
442
+
443
+ it('maps optional TypeScript identifiers in to_ts output', () => {
444
+ const source = `
445
+ export type OptionalTuple = [tupleRequired: string, tupleMaybe?: string];
446
+ export type OptionalFn = (fnRequired: string, fnMaybe?: string) => void;
447
+ export function optionalFn(declRequired: string, declMaybe?: string) {
448
+ todo(declRequired, declMaybe);
449
+ }
450
+ `;
451
+ const result = compile_to_volar_mappings(source, 'test.tsrx');
452
+
453
+ function expect_identifier_mapping(identifier: string, sourceNeedle: string) {
454
+ const source_offset = source.indexOf(sourceNeedle);
455
+ const generated_offset = result.code.indexOf(sourceNeedle);
456
+ const mapping = result.mappings.find(
457
+ (mapping: {
458
+ sourceOffsets: number[];
459
+ generatedOffsets: number[];
460
+ lengths: number[];
461
+ generatedLengths: number[];
462
+ }) => mapping.sourceOffsets[0] === source_offset &&
463
+ mapping.generatedOffsets[0] === generated_offset &&
464
+ mapping.lengths[0] === identifier.length &&
465
+ mapping.generatedLengths[0] === identifier.length,
466
+ );
467
+
468
+ expect(source_offset).toBeGreaterThan(-1);
469
+ expect(generated_offset).toBeGreaterThan(-1);
470
+ expect(mapping).toBeDefined();
471
+ }
472
+
473
+ expect(result.errors).toEqual([]);
474
+ expect_identifier_mapping('tupleMaybe', 'tupleMaybe?: string');
475
+ expect_identifier_mapping('fnMaybe', 'fnMaybe?: string');
476
+ expect_identifier_mapping('declMaybe', 'declMaybe?: string');
477
+ });
478
+
394
479
  it('uses tracked fast path for nested lazy params typed as Tracked', () => {
395
480
  const source = `
396
481
  import type { Tracked } from 'ripple';
@@ -1,6 +1,40 @@
1
1
  import { flushSync, track } from 'ripple';
2
2
 
3
3
  describe('tsx expression', () => {
4
+ it('renders an empty <tsx></tsx> element', () => {
5
+ component App() {
6
+ <tsx></tsx>
7
+ }
8
+ render(App);
9
+ expect(container.textContent).toBe('');
10
+ });
11
+
12
+ it('renders an empty <></> fragment shorthand', () => {
13
+ component App() {
14
+ <></>
15
+ }
16
+ render(App);
17
+ expect(container.textContent).toBe('');
18
+ });
19
+
20
+ it('renders an empty <tsx></tsx> assigned to a variable', () => {
21
+ component App() {
22
+ const el = <tsx></tsx>;
23
+ {el}
24
+ }
25
+ render(App);
26
+ expect(container.textContent).toBe('');
27
+ });
28
+
29
+ it('renders an empty <></> fragment assigned to a variable', () => {
30
+ component App() {
31
+ const el = <></>;
32
+ {el}
33
+ }
34
+ render(App);
35
+ expect(container.textContent).toBe('');
36
+ });
37
+
4
38
  it('renders a basic fragment shorthand element', () => {
5
39
  component App() {
6
40
  const el = <tsx><div>hello world</div></tsx>;
@@ -170,4 +170,58 @@ describe('basic client', () => {
170
170
 
171
171
  expect(body).toBeHtml('<div>1</div><div>10</div><div>12<span>15</span></div>');
172
172
  });
173
+
174
+ it('runs nested JavaScript blocks inside component-local callables', async () => {
175
+ component App() {
176
+ function readFunction() {
177
+ const label = 'function outer';
178
+ let result = '';
179
+
180
+ {
181
+ const label = 'function inner';
182
+ result = label;
183
+ }
184
+
185
+ return `${result} / ${label}`;
186
+ }
187
+
188
+ const readArrow = () => {
189
+ const offset = 5;
190
+ let value = offset;
191
+
192
+ {
193
+ const offset = 17;
194
+ value += offset;
195
+ }
196
+
197
+ return value;
198
+ };
199
+
200
+ class Reader {
201
+ read() {
202
+ const label = 'method outer';
203
+ let result = '';
204
+
205
+ {
206
+ const label = 'method inner';
207
+ result = label;
208
+ }
209
+
210
+ return `${result} / ${label}`;
211
+ }
212
+ }
213
+
214
+ const reader = new Reader();
215
+
216
+ <div class="block-function">{readFunction()}</div>
217
+ <div class="block-arrow">{readArrow()}</div>
218
+ <div class="block-method">{reader.read()}</div>
219
+ }
220
+
221
+ const { body } = await render(App);
222
+
223
+ expect(body).toBeHtml(
224
+ '<div class="block-function">function inner / function outer</div><div class="block-arrow">22</div><div class="block-method">method inner / method outer</div>',
225
+ );
226
+ });
173
227
  });