ripple 0.2.115 → 0.2.118

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.
Files changed (93) hide show
  1. package/package.json +16 -16
  2. package/src/compiler/index.js +20 -1
  3. package/src/compiler/phases/1-parse/index.js +79 -0
  4. package/src/compiler/phases/3-transform/client/index.js +54 -8
  5. package/src/compiler/phases/3-transform/segments.js +107 -60
  6. package/src/compiler/phases/3-transform/server/index.js +21 -11
  7. package/src/compiler/types/index.d.ts +16 -0
  8. package/src/runtime/index-client.js +19 -185
  9. package/src/runtime/index-server.js +24 -0
  10. package/src/runtime/internal/client/bindings.js +443 -0
  11. package/src/runtime/internal/client/index.js +4 -0
  12. package/src/runtime/internal/client/runtime.js +10 -0
  13. package/src/runtime/internal/client/utils.js +0 -8
  14. package/src/runtime/map.js +11 -1
  15. package/src/runtime/set.js +11 -1
  16. package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
  17. package/tests/client/_etc.test.ripple +5 -0
  18. package/tests/client/array/array.copy-within.test.ripple +120 -0
  19. package/tests/client/array/array.derived.test.ripple +495 -0
  20. package/tests/client/array/array.iteration.test.ripple +115 -0
  21. package/tests/client/array/array.mutations.test.ripple +385 -0
  22. package/tests/client/array/array.static.test.ripple +237 -0
  23. package/tests/client/array/array.to-methods.test.ripple +93 -0
  24. package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
  25. package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
  26. package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
  27. package/tests/client/basic/basic.attributes.test.ripple +474 -0
  28. package/tests/client/basic/basic.collections.test.ripple +94 -0
  29. package/tests/client/basic/basic.components.test.ripple +225 -0
  30. package/tests/client/basic/basic.errors.test.ripple +126 -0
  31. package/tests/client/basic/basic.events.test.ripple +222 -0
  32. package/tests/client/basic/basic.reactivity.test.ripple +476 -0
  33. package/tests/client/basic/basic.rendering.test.ripple +204 -0
  34. package/tests/client/basic/basic.styling.test.ripple +63 -0
  35. package/tests/client/basic/basic.utilities.test.ripple +25 -0
  36. package/tests/client/boundaries.test.ripple +2 -21
  37. package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
  38. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
  39. package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
  40. package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
  41. package/tests/client/compiler/compiler.basic.test.ripple +203 -0
  42. package/tests/client/compiler/compiler.regex.test.ripple +87 -0
  43. package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
  44. package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
  45. package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
  46. package/tests/client/composite/composite.generics.test.ripple +211 -0
  47. package/tests/client/composite/composite.props.test.ripple +106 -0
  48. package/tests/client/composite/composite.reactivity.test.ripple +184 -0
  49. package/tests/client/composite/composite.render.test.ripple +84 -0
  50. package/tests/client/computed-properties.test.ripple +2 -21
  51. package/tests/client/context.test.ripple +5 -22
  52. package/tests/client/date.test.ripple +1 -20
  53. package/tests/client/dynamic-elements.test.ripple +16 -24
  54. package/tests/client/for.test.ripple +4 -23
  55. package/tests/client/head.test.ripple +11 -23
  56. package/tests/client/html.test.ripple +1 -20
  57. package/tests/client/input-value.test.ripple +11 -31
  58. package/tests/client/map.test.ripple +82 -20
  59. package/tests/client/media-query.test.ripple +10 -23
  60. package/tests/client/object.test.ripple +5 -24
  61. package/tests/client/portal.test.ripple +2 -19
  62. package/tests/client/ref.test.ripple +8 -26
  63. package/tests/client/set.test.ripple +84 -22
  64. package/tests/client/svg.test.ripple +1 -22
  65. package/tests/client/switch.test.ripple +6 -25
  66. package/tests/client/tracked-expression.test.ripple +2 -21
  67. package/tests/client/typescript-generics.test.ripple +0 -21
  68. package/tests/client/url/url.derived.test.ripple +83 -0
  69. package/tests/client/url/url.parsing.test.ripple +165 -0
  70. package/tests/client/url/url.partial-removal.test.ripple +198 -0
  71. package/tests/client/url/url.reactivity.test.ripple +449 -0
  72. package/tests/client/url/url.serialization.test.ripple +50 -0
  73. package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
  74. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
  75. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
  76. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
  77. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
  78. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
  79. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
  80. package/tests/client.d.ts +12 -0
  81. package/tests/server/if.test.ripple +66 -0
  82. package/tests/setup-client.js +28 -0
  83. package/tsconfig.json +4 -2
  84. package/types/index.d.ts +92 -46
  85. package/LICENSE +0 -21
  86. package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
  87. package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
  88. package/tests/client/array.test.ripple +0 -1455
  89. package/tests/client/basic.test.ripple +0 -1892
  90. package/tests/client/compiler.test.ripple +0 -541
  91. package/tests/client/composite.test.ripple +0 -692
  92. package/tests/client/url-search-params.test.ripple +0 -912
  93. package/tests/client/url.test.ripple +0 -954
@@ -0,0 +1,63 @@
1
+ import { compile } from 'ripple/compiler';
2
+
3
+ describe('basic client > styling', () => {
4
+ it('renders with styling scoped to component', () => {
5
+ component Basic() {
6
+ <div class='styled-container'>
7
+ <h1>{'Styled heading'}</h1>
8
+ <p class='text'>{'Styled paragraph'}</p>
9
+ </div>
10
+
11
+ <style>
12
+ .styled-container {
13
+ background-color: rgb(0, 0, 255);
14
+ padding: 16px;
15
+ }
16
+
17
+ h1 {
18
+ color: rgb(255, 255, 255);
19
+ font-size: 32px;
20
+ }
21
+
22
+ .text {
23
+ color: rgb(200, 200, 200);
24
+ font-size: 14px;
25
+ }
26
+ </style>
27
+ }
28
+
29
+ render(Basic);
30
+
31
+ const styledContainer = container.querySelector('.styled-container');
32
+ const heading = styledContainer.querySelector('h1');
33
+ const paragraph = styledContainer.querySelector('.text');
34
+
35
+ expect(styledContainer).toBeTruthy();
36
+ expect(heading.textContent).toBe('Styled heading');
37
+ expect(paragraph.textContent).toBe('Styled paragraph');
38
+ });
39
+
40
+ it('renders with keyframes in styling scoped to component', () => {
41
+ const source = `export component Basic() {
42
+ <div>
43
+ <p>{'Styled paragraph'}</p>
44
+ </div>
45
+
46
+ <style>
47
+ div {
48
+ animation-name: anim;
49
+ }
50
+
51
+ @keyframes anim {}
52
+
53
+ p {
54
+ animation-name: anim;
55
+ }
56
+ </style>
57
+ }`;
58
+
59
+ const { css } = compile(source, 'test.ripple');
60
+ const name = css.match(/@keyframes\s+([a-zA-Z0-9_-]+)\s*\{/)[1];
61
+ expect(css.match(new RegExp(name, 'g'))?.length).toEqual(3);
62
+ });
63
+ });
@@ -0,0 +1,25 @@
1
+ import { track, effect, untrack, tick } from 'ripple';
2
+
3
+ describe('basic client > utilities', () => {
4
+ it('tick function', async () => {
5
+ let resolve: () => void;
6
+ const promise = new Promise<void>((res) => (resolve = res));
7
+
8
+ component Basic() {
9
+ let value = track(0);
10
+ effect(() => {
11
+ untrack(() => {
12
+ @value++;
13
+ tick().then(() => resolve());
14
+ });
15
+ });
16
+ <p>{@value}</p>
17
+ }
18
+ render(Basic);
19
+
20
+ const p = container.querySelector('p');
21
+ expect(p.textContent).toBe('0');
22
+ await promise;
23
+ expect(p.textContent).toBe('1');
24
+ });
25
+ });
@@ -1,25 +1,6 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, effect, track } from 'ripple';
1
+ import { flushSync, effect, track } from 'ripple';
3
2
 
4
3
  describe('passing reactivity between boundaries 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
4
  it('can pass reactivity between functions with simple arrays and destructuring', () => {
24
5
  let log: string[] = [];
25
6
 
@@ -66,7 +47,7 @@ describe('passing reactivity between boundaries tests', () => {
66
47
  it('can pass reactivity between functions with simple objects and destructuring', () => {
67
48
  let log: string[] = [];
68
49
 
69
- function createDouble({ count }) {
50
+ function createDouble({ count }: { count: Tracked<number> }) {
70
51
  const double = track(() => @count * 2);
71
52
 
72
53
  effect(() => {
@@ -0,0 +1,12 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`compiler > assignments > compiles tracked values in effect with assignment expression 1`] = `"state.count = _$_.get(count);"`;
4
+
5
+ exports[`compiler > assignments > compiles tracked values in effect with update expressions 1`] = `
6
+ "_$_.with_scope(__block, () => untrack(() => {
7
+ state.preIncrement = _$_.update_pre(count, __block);
8
+ state.postIncrement = _$_.update(count, __block);
9
+ state.preDecrement = _$_.update_pre(count, __block, -1);
10
+ state.postDecrement = _$_.update(count, __block, -1);
11
+ }));"
12
+ `;
@@ -0,0 +1,22 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`compiler > typescript > compiles TSInstantiationExpression 1`] = `
4
+ "import * as _$_ from 'ripple/internal/client';
5
+
6
+ function makeBox(value) {
7
+ return { value };
8
+ }
9
+
10
+ const makeStringBox = (makeBox);
11
+ const stringBox = makeStringBox('abc');
12
+ const ErrorMap = (Map);
13
+ const errorMap = new ErrorMap();"
14
+ `;
15
+
16
+ exports[`compiler > typescript > removes type assertions from function parameters and leaves default values 1`] = `
17
+ "import * as _$_ from 'ripple/internal/client';
18
+
19
+ function getString(e = 'test') {
20
+ return e;
21
+ }"
22
+ `;
@@ -0,0 +1,112 @@
1
+ import { track, TrackedArray } from 'ripple';
2
+ import { compile } from 'ripple/compiler';
3
+
4
+ describe('compiler > assignments', () => {
5
+ it('properly handles JS assignments, reads and updates to array indices', () => {
6
+ const logs: (number | undefined)[] = [];
7
+
8
+ component App() {
9
+ let items: number[] = [];
10
+ let tracked_items = track<number[]>([]);
11
+ let items2 = new Array();
12
+ let items3 = new TrackedArray<number>();
13
+ let i = 0;
14
+
15
+ logs.push(items[0]);
16
+ logs.push(items[i]);
17
+ logs.push(@tracked_items[0]);
18
+ logs.push(@tracked_items[i]);
19
+ logs.push(items2[0]);
20
+ logs.push(items2[i]);
21
+ logs.push(items3[0]);
22
+ logs.push(items3[i]);
23
+
24
+ items[0] = 123;
25
+ items[i] = 123;
26
+ @tracked_items[0] = 123;
27
+ @tracked_items[i] = 123;
28
+ items2[0] = 123;
29
+ items2[i] = 123;
30
+ items3[0] = 123;
31
+ items3[i] = 123;
32
+
33
+ logs.push(items[0]);
34
+ logs.push(items[i]);
35
+ logs.push(@tracked_items[0]);
36
+ logs.push(@tracked_items[i]);
37
+ logs.push(items2[0]);
38
+ logs.push(items2[i]);
39
+ logs.push(items3[0]);
40
+ logs.push(items3[i]);
41
+
42
+ items[0]++;
43
+ items[i]++;
44
+ @tracked_items[0]++;
45
+ @tracked_items[i]++;
46
+ items2[0]++;
47
+ items2[i]++;
48
+ items3[0]++;
49
+ items3[i]++;
50
+
51
+ logs.push(items[0]);
52
+ logs.push(items[i]);
53
+ logs.push(@tracked_items[0]);
54
+ logs.push(@tracked_items[i]);
55
+ logs.push(items2[0]);
56
+ logs.push(items2[i]);
57
+ logs.push(items3[0]);
58
+ logs.push(items3[i]);
59
+
60
+ logs.push(--items[0]);
61
+ logs.push(--items[i]);
62
+ logs.push(--@tracked_items[0]);
63
+ logs.push(--@tracked_items[i]);
64
+ logs.push(--items2[0]);
65
+ logs.push(--items2[i]);
66
+ logs.push(--items3[0]);
67
+ logs.push(--items3[i]);
68
+ }
69
+
70
+ render(App);
71
+
72
+ expect(logs).toEqual([
73
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
74
+ 123, 123, 123, 123, 123, 123, 123, 123,
75
+ 125, 125, 125, 125, 125, 125, 125, 125,
76
+ 124, 123, 124, 123, 124, 123, 124, 123
77
+ ]);
78
+ });
79
+
80
+ it('compiles tracked values in effect with assignment expression', () => {
81
+ const source = `component App() {
82
+ let count = track(0);
83
+
84
+ effect(() => {
85
+ state.count = @count;
86
+ })
87
+ }`;
88
+ const result = compile(source, 'test.ripple');
89
+ // Extract just the effect callback body
90
+ const effectMatch = result.js.code.match(/effect\(\(\) => \{([^}]+)\}\)/s);
91
+ expect(effectMatch?.[1].trim()).toMatchSnapshot();
92
+ });
93
+
94
+ it('compiles tracked values in effect with update expressions', () => {
95
+ const source = `component App() {
96
+ let count = track(5);
97
+
98
+ effect(() => {
99
+ untrack(() => {
100
+ state.preIncrement = ++@count;
101
+ state.postIncrement = @count++;
102
+ state.preDecrement = --@count;
103
+ state.postDecrement = @count--;
104
+ });
105
+ })
106
+ }`;
107
+ const result = compile(source, 'test.ripple');
108
+ // Extract just the effect callback body
109
+ const effectMatch = result.js.code.match(/effect\(\(\) => \{([\s\S]+?)\n\t\}\)\)/);
110
+ expect(effectMatch?.[1].trim()).toMatchSnapshot();
111
+ });
112
+ });
@@ -0,0 +1,95 @@
1
+ import { parse, compile } from 'ripple/compiler';
2
+
3
+ describe('compiler > attributes', () => {
4
+ it('generates valid JavaScript for component props with hyphenated attributes', () => {
5
+ const source = `
6
+ component Child(props) {
7
+ <div />
8
+ }
9
+
10
+ export default component App() {
11
+ <Child data-scope="test" aria-label="accessible" class="valid" />
12
+ }`;
13
+
14
+ const result = compile(source, 'test.ripple', { mode: 'client' });
15
+
16
+ // Should contain properly quoted hyphenated properties and unquoted valid identifiers
17
+ expect(result.js.code).toMatch(/'data-scope': "test"/);
18
+ expect(result.js.code).toMatch(/'aria-label': "accessible"/);
19
+ expect(result.js.code).toMatch(/class: "valid"/);
20
+ });
21
+
22
+ it('generates valid JavaScript for all types of hyphenated attributes', () => {
23
+ const testCases = [
24
+ { attr: 'data-testid="value"', expected: /'data-testid': "value"/ },
25
+ { attr: 'aria-label="label"', expected: /'aria-label': "label"/ },
26
+ { attr: 'data-custom-attr="custom"', expected: /'data-custom-attr': "custom"/ },
27
+ { attr: 'ng-if="condition"', expected: /'ng-if': "condition"/ },
28
+ ];
29
+
30
+ testCases.forEach(({ attr, expected }) => {
31
+ const source = `
32
+ component Child(props) { <div /> }
33
+ export default component App() { <Child ${attr} /> }`;
34
+
35
+ const result = compile(source, 'test.ripple', { mode: 'client' });
36
+ expect(result.js.code).toMatch(expected);
37
+ });
38
+ });
39
+
40
+ it('handles mixed valid and invalid attribute identifiers correctly', () => {
41
+ const source = `
42
+ component Child(props) {
43
+ <div />
44
+ }
45
+
46
+ export default component App() {
47
+ <Child
48
+ validProp="valid"
49
+ class="valid"
50
+ id="valid"
51
+ data-invalid="invalid"
52
+ aria-invalid="invalid"
53
+ custom-prop="invalid"
54
+ />
55
+ }`;
56
+
57
+ const result = compile(source, 'test.ripple', { mode: 'client' });
58
+
59
+ // Valid identifiers should not be quoted
60
+ expect(result.js.code).toMatch(/validProp: "valid"/);
61
+ expect(result.js.code).toMatch(/class: "valid"/);
62
+ expect(result.js.code).toMatch(/id: "valid"/);
63
+
64
+ // Invalid identifiers (with hyphens) should be quoted
65
+ expect(result.js.code).toMatch(/'data-invalid': "invalid"/);
66
+ expect(result.js.code).toMatch(/'aria-invalid': "invalid"/);
67
+ expect(result.js.code).toMatch(/'custom-prop': "invalid"/);
68
+ });
69
+
70
+ it('ensures generated code is syntactically valid JavaScript', () => {
71
+ const source = `
72
+ component Child(props) {
73
+ <div />
74
+ }
75
+
76
+ export default component App() {
77
+ <Child data-scope="test" />
78
+ }`;
79
+
80
+ const result = compile(source, 'test.ripple', { mode: 'client' });
81
+
82
+ // Extract the props object from the generated code and test it's valid JavaScript
83
+ const match = result.js.code.match(/Child\([^,]+,\s*(\{[^}]+\})/);
84
+ expect(match).toBeTruthy();
85
+
86
+ const propsObject = match?.[1];
87
+ expect(() => {
88
+ // Test that the object literal is syntactically valid
89
+ new Function(`return ${propsObject}`);
90
+ }).not.toThrow();
91
+
92
+ // Also verify it contains the expected quoted property
93
+ expect(propsObject).toMatch(/'data-scope': "test"/);
94
+ });
95
+ });
@@ -0,0 +1,203 @@
1
+ import { parse, compile } from 'ripple/compiler';
2
+
3
+ describe('compiler > basics', () => {
4
+ it('parses style content correctly', () => {
5
+ const source = `export component App() {
6
+ <div id="myid" class="myclass">{"Hello World"}</div>
7
+
8
+ <style>#style</style>
9
+ }`;
10
+ const style1 = '.myid {color: green }';
11
+ const style2 = '#myid {color: green }';
12
+ const style3 = 'div {color: green }';
13
+
14
+ let input = source.replace('#style', style1);
15
+ let ast = parse(input);
16
+ expect(ast.body[0].declaration.css.source).toEqual(style1);
17
+
18
+ input = source.replace('#style', style2);
19
+ ast = parse(input);
20
+ expect(ast.body[0].declaration.css.source).toEqual(style2);
21
+
22
+ input = source.replace('#style', style3);
23
+ ast = parse(input);
24
+ expect(ast.body[0].declaration.css.source).toEqual(style3);
25
+ });
26
+
27
+ it('renders without crashing', () => {
28
+ component App() {
29
+ let foo;
30
+ let bar;
31
+ let baz;
32
+
33
+ foo = {};
34
+ foo = {'test': 0};
35
+ foo['abc'] = 123;
36
+
37
+ bar = { 'def': 456 };
38
+
39
+ baz = { 'ghi': 789 };
40
+ baz['jkl'] = 987;
41
+ }
42
+
43
+ render(App);
44
+ });
45
+
46
+ it('renders without crashing using < character', () => {
47
+ component App() {
48
+ function bar() {
49
+ for (let i = 0; i < 10; i++) {
50
+ // do nothing
51
+ }
52
+ const x = 1 < 1;
53
+ }
54
+
55
+ let x = 5 < 10
56
+
57
+ <div>{x}</div>
58
+ }
59
+
60
+ render(App);
61
+ });
62
+
63
+ it('renders lexical blocks without crashing', () => {
64
+ component App() {
65
+ <div>
66
+ const a = 1;
67
+ <div>
68
+ const b = 1;
69
+ </div>
70
+ <div>
71
+ const b = 1;
72
+ </div>
73
+ </div>
74
+ <div>
75
+ const a = 2;
76
+ <div>
77
+ const b = 1;
78
+ </div>
79
+ </div>
80
+ }
81
+
82
+ render(App);
83
+ });
84
+
85
+ it('renders without crashing using mapped types', () => {
86
+ component App() {
87
+ type RecordKey = 'test';
88
+ type RecordValue = { a: string, b: number };
89
+
90
+ const config: Record<RecordKey, RecordValue> = {
91
+ test: {
92
+ a: 'test',
93
+ b: 1
94
+ },
95
+ };
96
+
97
+ const config2: { [key in RecordKey]: RecordValue } = {
98
+ test: {
99
+ a: 'test2',
100
+ b: 2
101
+ }
102
+ }
103
+
104
+ const config3: { [key: RecordKey]: RecordValue } = {
105
+ test: {
106
+ a: 'test3',
107
+ b: 3
108
+ }
109
+ }
110
+ }
111
+
112
+ render(App);
113
+ });
114
+
115
+ it('renders without crashing using object destructuring', () => {
116
+ component App() {
117
+ const obj = { a: 1, b: 2, c: 3 };
118
+ const { a, b, ...rest } = obj;
119
+
120
+ <div>
121
+ {'a '}{a} {'b '} {b} {'rest '} {JSON.stringify(rest)}
122
+
123
+ <div>
124
+
125
+ </div>
126
+ </div>
127
+ }
128
+
129
+ render(App);
130
+ });
131
+
132
+ it('renders without crashing using object destructuring #2', () => {
133
+ component App() {
134
+ const obj = { a: 1, b: 2, c: 3 };
135
+ const { a, b, ...rest } = obj;
136
+
137
+ {'a '}{a} {'b '} {b} {'rest '} {JSON.stringify(rest)}
138
+
139
+ <div>
140
+
141
+ </div>
142
+ }
143
+
144
+ render(App);
145
+ });
146
+
147
+ it('should not fail with random TS syntax', () => {
148
+ function tagFn() {
149
+ return null;
150
+ }
151
+
152
+ function Wrapper() {
153
+ return {
154
+ unwrap: function<T>() {
155
+ return null as unknown as T;
156
+ }
157
+ }
158
+ }
159
+
160
+ component App() {
161
+ let x: number[] = [] as number[];
162
+
163
+ const n = new Wrapper<number>().unwrap<string>();
164
+
165
+ const tagResult = tagFn`value`;
166
+
167
+ interface Node<T> {
168
+ value: T;
169
+ }
170
+
171
+ class Box<T> {
172
+ value: T;
173
+
174
+ method<T>(): T {
175
+ return this.value;
176
+ }
177
+ }
178
+
179
+ let flag = true;
180
+
181
+ const s = flag ? new Box<number>() : new Box<string>();
182
+ }
183
+
184
+ render(App);
185
+ });
186
+
187
+ it('compiles without needing semicolons between statements and JSX', () => {
188
+ const source = `export component App() {
189
+ <div>const code4 = 4
190
+
191
+ const code3 = 3
192
+ <div>
193
+ <div>
194
+ const code = 1
195
+ </div>
196
+ const code2 = 2
197
+ </div>
198
+ </div>
199
+ }`;
200
+
201
+ const result = compile(source, 'test.ripple', { mode: 'client' });
202
+ });
203
+ });
@@ -0,0 +1,87 @@
1
+ describe('compiler > regex', () => {
2
+ it('renders without crashing using regex literals in method calls', () => {
3
+ component App() {
4
+ let text = 'Hello <span>world</span> and <div>content</div>';
5
+
6
+ // Test various regex patterns in method calls that previously failed
7
+ let matchResult = text.match(/<span>/);
8
+ let replaceResult = text.replace(/<div>/g, '[DIV]');
9
+ let searchResult = text.search(/<span>/);
10
+
11
+ // Test regex literals in variable assignments (should work)
12
+ let spanRegex = /<span>/g;
13
+ let divRegex = /<div.*?>/;
14
+
15
+ // Test more complex regex patterns
16
+ let complexMatch = text.match(/<[^>]*>/g);
17
+ let htmlTags = text.replace(/<(\/*)(\w+)[^>]*>/g, '[$1$2]');
18
+
19
+ // Test edge cases with multiple angle brackets
20
+ let multiAngle = '<<test>> <span>content</span>'.match(/<span>/);
21
+
22
+ <div>
23
+ <span>{String(matchResult)}</span>
24
+ <span>{replaceResult}</span>
25
+ <span>{String(searchResult)}</span>
26
+ <span>{String(spanRegex)}</span>
27
+ <span>{String(divRegex)}</span>
28
+ <span>{String(complexMatch)}</span>
29
+ <span>{htmlTags}</span>
30
+ <span>{String(multiAngle)}</span>
31
+ </div>
32
+ }
33
+
34
+ render(App);
35
+
36
+ const matchResult = container.querySelectorAll('span')[0];
37
+ const replaceResult = container.querySelectorAll('span')[1];
38
+ const searchResult = container.querySelectorAll('span')[2];
39
+ const spanRegex = container.querySelectorAll('span')[3];
40
+ const divRegex = container.querySelectorAll('span')[4];
41
+ const complexMatch = container.querySelectorAll('span')[5];
42
+ const htmlTags = container.querySelectorAll('span')[6];
43
+ const multiAngle = container.querySelectorAll('span')[7];
44
+
45
+ expect(matchResult.textContent).toBe('<span>');
46
+ expect(replaceResult.textContent).toBe('Hello <span>world</span> and [DIV]content</div>');
47
+ expect(searchResult.textContent).toBe('6');
48
+ expect(spanRegex.textContent).toBe('/<span>/g');
49
+ expect(divRegex.textContent).toBe('/<div.*?>/');
50
+ expect(complexMatch.textContent).toBe('<span>,</span>,<div>,</div>');
51
+ expect(htmlTags.textContent).toBe('Hello [span]world[/span] and [div]content[/div]');
52
+ expect(multiAngle.textContent).toBe('<span>');
53
+ });
54
+
55
+ it('renders without crashing mixing regex and JSX syntax', () => {
56
+ component App() {
57
+ let htmlString = '<p>Paragraph</p><div>Content</div>';
58
+
59
+ // Mix of regex parsing and legitimate JSX
60
+ let paragraphs = htmlString.match(/<p[^>]*>.*?<\/p>/g);
61
+ let cleaned = htmlString.replace(/<\/?[^>]+>/g, '');
62
+ let splitArray = htmlString.split(/<\/?\w+>/g).filter(s => s.trim());
63
+
64
+ <div class='container'>
65
+ <span class='result'>{String(paragraphs)}</span>
66
+ <span class='cleaned'>{cleaned}</span>
67
+ <p>{'This is real JSX'}</p>
68
+ <div><span>
69
+ {'Split result: '}
70
+ {splitArray.join(', ')}
71
+ </span></div>
72
+ </div>
73
+ }
74
+
75
+ render(App);
76
+
77
+ const result = container.querySelector('.result');
78
+ const cleaned = container.querySelector('.cleaned');
79
+ const jsxParagraph = container.querySelector('p');
80
+ const splitResult = container.querySelector('.container > div > span');
81
+
82
+ expect(result.textContent).toBe('<p>Paragraph</p>');
83
+ expect(cleaned.textContent).toBe('ParagraphContent');
84
+ expect(jsxParagraph.textContent).toBe('This is real JSX');
85
+ expect(splitResult.textContent).toBe('Split result: Paragraph, Content');
86
+ });
87
+ });
@@ -0,0 +1,29 @@
1
+ import { compile } from 'ripple/compiler';
2
+
3
+ describe('compiler > typescript', () => {
4
+ it('compiles TSInstantiationExpression', () => {
5
+ const source =
6
+ `function makeBox<T>(value: T) {
7
+ return { value };
8
+ }
9
+ const makeStringBox = makeBox<string>;
10
+ const stringBox = makeStringBox('abc');
11
+ const ErrorMap = Map<string, Error>;
12
+ const errorMap = new ErrorMap();`;
13
+
14
+ const result = compile(source, 'test.ripple', { mode: 'client' });
15
+
16
+ expect(result.js.code).toMatchSnapshot();
17
+ });
18
+
19
+ it('removes type assertions from function parameters and leaves default values', () => {
20
+ const source = `
21
+ function getString(e: string = 'test') {
22
+ return e;
23
+ }`;
24
+
25
+ const result = compile(source, 'test.ripple', { mode: 'client' });
26
+
27
+ expect(result.js.code).toMatchSnapshot();
28
+ });
29
+ });