ripple 0.2.103 → 0.2.105
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 +2 -1
- package/src/compiler/phases/1-parse/index.js +40 -1
- package/src/compiler/phases/2-analyze/index.js +18 -3
- package/src/compiler/phases/3-transform/client/index.js +14 -0
- package/src/compiler/phases/3-transform/segments.js +531 -12
- package/src/compiler/phases/3-transform/server/index.js +237 -19
- package/src/compiler/types/index.d.ts +5 -0
- package/src/runtime/index-client.js +2 -0
- package/src/runtime/internal/client/css.js +70 -0
- package/src/runtime/internal/server/css-registry.js +35 -0
- package/src/runtime/internal/server/index.js +32 -17
- package/src/server/index.js +2 -1
- package/tests/client/__snapshots__/tracked-expression.test.ripple.snap +34 -0
- package/tests/client/compiler.test.ripple +98 -10
- package/tests/client/tracked-expression.test.ripple +46 -0
|
@@ -287,13 +287,13 @@ describe('compiler success tests', () => {
|
|
|
287
287
|
component Child(props) {
|
|
288
288
|
<div />
|
|
289
289
|
}
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
export default component App() {
|
|
292
292
|
<Child data-scope="test" aria-label="accessible" class="valid" />
|
|
293
293
|
}`;
|
|
294
294
|
|
|
295
295
|
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
// Should contain properly quoted hyphenated properties and unquoted valid identifiers
|
|
298
298
|
expect(result.js.code).toMatch(/'data-scope': "test"/);
|
|
299
299
|
expect(result.js.code).toMatch(/'aria-label': "accessible"/);
|
|
@@ -323,9 +323,9 @@ describe('compiler success tests', () => {
|
|
|
323
323
|
component Child(props) {
|
|
324
324
|
<div />
|
|
325
325
|
}
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
export default component App() {
|
|
328
|
-
<Child
|
|
328
|
+
<Child
|
|
329
329
|
validProp="valid"
|
|
330
330
|
class="valid"
|
|
331
331
|
id="valid"
|
|
@@ -336,12 +336,12 @@ describe('compiler success tests', () => {
|
|
|
336
336
|
}`;
|
|
337
337
|
|
|
338
338
|
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
339
|
-
|
|
339
|
+
|
|
340
340
|
// Valid identifiers should not be quoted
|
|
341
341
|
expect(result.js.code).toMatch(/validProp: "valid"/);
|
|
342
342
|
expect(result.js.code).toMatch(/class: "valid"/);
|
|
343
343
|
expect(result.js.code).toMatch(/id: "valid"/);
|
|
344
|
-
|
|
344
|
+
|
|
345
345
|
// Invalid identifiers (with hyphens) should be quoted
|
|
346
346
|
expect(result.js.code).toMatch(/'data-invalid': "invalid"/);
|
|
347
347
|
expect(result.js.code).toMatch(/'aria-invalid': "invalid"/);
|
|
@@ -353,25 +353,113 @@ describe('compiler success tests', () => {
|
|
|
353
353
|
component Child(props) {
|
|
354
354
|
<div />
|
|
355
355
|
}
|
|
356
|
-
|
|
356
|
+
|
|
357
357
|
export default component App() {
|
|
358
358
|
<Child data-scope="test" />
|
|
359
359
|
}`;
|
|
360
360
|
|
|
361
361
|
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
// Extract the props object from the generated code and test it's valid JavaScript
|
|
364
364
|
const match = result.js.code.match(/Child\([^,]+,\s*(\{[^}]+\})/);
|
|
365
365
|
expect(match).toBeTruthy();
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
const propsObject = match[1];
|
|
368
368
|
expect(() => {
|
|
369
369
|
// Test that the object literal is syntactically valid
|
|
370
370
|
new Function(`return ${propsObject}`);
|
|
371
371
|
}).not.toThrow();
|
|
372
|
-
|
|
372
|
+
|
|
373
373
|
// Also verify it contains the expected quoted property
|
|
374
374
|
expect(propsObject).toMatch(/'data-scope': "test"/);
|
|
375
375
|
});
|
|
376
376
|
});
|
|
377
|
+
|
|
378
|
+
describe('regex handling', () => {
|
|
379
|
+
it('renders without crashing using regex literals in method calls', () => {
|
|
380
|
+
component App() {
|
|
381
|
+
let text = 'Hello <span>world</span> and <div>content</div>';
|
|
382
|
+
|
|
383
|
+
// Test various regex patterns in method calls that previously failed
|
|
384
|
+
let matchResult = text.match(/<span>/);
|
|
385
|
+
let replaceResult = text.replace(/<div>/g, '[DIV]');
|
|
386
|
+
let searchResult = text.search(/<span>/);
|
|
387
|
+
|
|
388
|
+
// Test regex literals in variable assignments (should work)
|
|
389
|
+
let spanRegex = /<span>/g;
|
|
390
|
+
let divRegex = /<div.*?>/;
|
|
391
|
+
|
|
392
|
+
// Test more complex regex patterns
|
|
393
|
+
let complexMatch = text.match(/<[^>]*>/g);
|
|
394
|
+
let htmlTags = text.replace(/<(\/*)(\w+)[^>]*>/g, '[$1$2]');
|
|
395
|
+
|
|
396
|
+
// Test edge cases with multiple angle brackets
|
|
397
|
+
let multiAngle = '<<test>> <span>content</span>'.match(/<span>/);
|
|
398
|
+
|
|
399
|
+
<div>
|
|
400
|
+
<span>{String(matchResult)}</span>
|
|
401
|
+
<span>{replaceResult}</span>
|
|
402
|
+
<span>{String(searchResult)}</span>
|
|
403
|
+
<span>{String(spanRegex)}</span>
|
|
404
|
+
<span>{String(divRegex)}</span>
|
|
405
|
+
<span>{String(complexMatch)}</span>
|
|
406
|
+
<span>{htmlTags}</span>
|
|
407
|
+
<span>{String(multiAngle)}</span>
|
|
408
|
+
</div>
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
render(App);
|
|
412
|
+
|
|
413
|
+
const matchResult = container.querySelectorAll('span')[0];
|
|
414
|
+
const replaceResult = container.querySelectorAll('span')[1];
|
|
415
|
+
const searchResult = container.querySelectorAll('span')[2];
|
|
416
|
+
const spanRegex = container.querySelectorAll('span')[3];
|
|
417
|
+
const divRegex = container.querySelectorAll('span')[4];
|
|
418
|
+
const complexMatch = container.querySelectorAll('span')[5];
|
|
419
|
+
const htmlTags = container.querySelectorAll('span')[6];
|
|
420
|
+
const multiAngle = container.querySelectorAll('span')[7];
|
|
421
|
+
|
|
422
|
+
expect(matchResult.textContent).toBe('<span>');
|
|
423
|
+
expect(replaceResult.textContent).toBe('Hello <span>world</span> and [DIV]content</div>');
|
|
424
|
+
expect(searchResult.textContent).toBe('6');
|
|
425
|
+
expect(spanRegex.textContent).toBe('/<span>/g');
|
|
426
|
+
expect(divRegex.textContent).toBe('/<div.*?>/');
|
|
427
|
+
expect(complexMatch.textContent).toBe('<span>,</span>,<div>,</div>');
|
|
428
|
+
expect(htmlTags.textContent).toBe('Hello [span]world[/span] and [div]content[/div]');
|
|
429
|
+
expect(multiAngle.textContent).toBe('<span>');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('renders without crashing mixing regex and JSX syntax', () => {
|
|
433
|
+
component App() {
|
|
434
|
+
let htmlString = '<p>Paragraph</p><div>Content</div>';
|
|
435
|
+
|
|
436
|
+
// Mix of regex parsing and legitimate JSX
|
|
437
|
+
let paragraphs = htmlString.match(/<p[^>]*>.*?<\/p>/g);
|
|
438
|
+
let cleaned = htmlString.replace(/<\/?[^>]+>/g, '');
|
|
439
|
+
let splitArray = htmlString.split(/<\/?\w+>/g).filter(s => s.trim());
|
|
440
|
+
|
|
441
|
+
<div class='container'>
|
|
442
|
+
<span class='result'>{String(paragraphs)}</span>
|
|
443
|
+
<span class='cleaned'>{cleaned}</span>
|
|
444
|
+
<p>{'This is real JSX'}</p>
|
|
445
|
+
<div><span>
|
|
446
|
+
{'Split result: '}
|
|
447
|
+
{splitArray.join(', ')}
|
|
448
|
+
</span></div>
|
|
449
|
+
</div>
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
render(App);
|
|
453
|
+
|
|
454
|
+
const result = container.querySelector('.result');
|
|
455
|
+
const cleaned = container.querySelector('.cleaned');
|
|
456
|
+
const jsxParagraph = container.querySelector('p');
|
|
457
|
+
const splitResult = container.querySelector('.container > div > span');
|
|
458
|
+
|
|
459
|
+
expect(result.textContent).toBe('<p>Paragraph</p>');
|
|
460
|
+
expect(cleaned.textContent).toBe('ParagraphContent');
|
|
461
|
+
expect(jsxParagraph.textContent).toBe('This is real JSX');
|
|
462
|
+
expect(splitResult.textContent).toBe('Split result: Paragraph, Content');
|
|
463
|
+
});
|
|
464
|
+
});
|
|
377
465
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, TrackedSet, track } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('TrackedExpression tests', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle the syntax correctly', () => {
|
|
24
|
+
component App() {
|
|
25
|
+
let count = track(0);
|
|
26
|
+
|
|
27
|
+
function get_count() {
|
|
28
|
+
return count;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<div>{@(count)}</div>
|
|
33
|
+
<div>{@(get_count())}</div>
|
|
34
|
+
<div>{++@(count)}</div>
|
|
35
|
+
<div>{++@(get_count())}</div>
|
|
36
|
+
<div>{@(count)++}</div>
|
|
37
|
+
<div>{@(get_count())++}</div>
|
|
38
|
+
<div>{@(count)}</div>
|
|
39
|
+
<div>{!@(count)}</div>
|
|
40
|
+
<div>{!!@(count)}</div>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(App);
|
|
44
|
+
expect(container).toMatchSnapshot();
|
|
45
|
+
});
|
|
46
|
+
});
|