ripple 0.2.124 → 0.2.125
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 +1 -1
- package/src/compiler/index.js +1 -1
- package/src/compiler/phases/1-parse/index.js +1 -0
- package/src/compiler/phases/2-analyze/index.js +58 -32
- package/src/compiler/phases/3-transform/server/index.js +50 -37
- package/src/runtime/internal/server/index.js +20 -9
- package/src/utils/builders.js +2 -2
- package/tests/client/__snapshots__/for.test.ripple.snap +0 -80
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +0 -48
- package/tests/client/basic/basic.errors.test.ripple +16 -0
- package/tests/server/await.test.ripple +61 -0
- package/tests/server/for.test.ripple +44 -0
- package/tests/server/if.test.ripple +21 -1
- package/tests/utils/escaping.test.js +102 -0
- package/tests/utils/events.test.js +147 -0
- package/tests/utils/normalize_css_property_name.test.js +43 -0
- package/tests/utils/patterns.test.js +382 -0
- package/tests/utils/sanitize_template_string.test.js +51 -0
|
@@ -63,4 +63,24 @@ describe('if statements in SSR', () => {
|
|
|
63
63
|
const { body } = await render(App);
|
|
64
64
|
expect(body).toBe('<div>Default Case</div>');
|
|
65
65
|
});
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
it('renders nested if-else blocks correctly', async () => {
|
|
68
|
+
component App() {
|
|
69
|
+
let outer = true;
|
|
70
|
+
let inner = false;
|
|
71
|
+
|
|
72
|
+
if (outer) {
|
|
73
|
+
if (inner) {
|
|
74
|
+
<div>{'Outer true, Inner true'}</div>
|
|
75
|
+
} else {
|
|
76
|
+
<div>{'Outer true, Inner false'}</div>
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
<div>{'Outer false'}</div>
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { body } = await render(App);
|
|
84
|
+
expect(body).toBe('<div>Outer true, Inner false</div>');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { escape } from '../../src/utils/escaping.js';
|
|
3
|
+
|
|
4
|
+
describe('escape utility', () => {
|
|
5
|
+
describe('content escaping (is_attr = false)', () => {
|
|
6
|
+
it('should escape & to &', () => {
|
|
7
|
+
expect(escape('foo & bar', false)).toBe('foo & bar');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should escape < to <', () => {
|
|
11
|
+
expect(escape('foo < bar', false)).toBe('foo < bar');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should escape multiple special characters', () => {
|
|
15
|
+
expect(escape('a & b < c', false)).toBe('a & b < c');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should not escape double quotes in content', () => {
|
|
19
|
+
expect(escape('foo "bar" baz', false)).toBe('foo "bar" baz');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle empty string', () => {
|
|
23
|
+
expect(escape('', false)).toBe('');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle string with no special characters', () => {
|
|
27
|
+
expect(escape('hello world', false)).toBe('hello world');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle null values', () => {
|
|
31
|
+
expect(escape(null, false)).toBe('');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle undefined values', () => {
|
|
35
|
+
expect(escape(undefined, false)).toBe('');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle numbers', () => {
|
|
39
|
+
expect(escape(123, false)).toBe('123');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should escape consecutive special characters', () => {
|
|
43
|
+
expect(escape('&&<<', false)).toBe('&&<<');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle special characters at start', () => {
|
|
47
|
+
expect(escape('&hello', false)).toBe('&hello');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle special characters at end', () => {
|
|
51
|
+
expect(escape('hello<', false)).toBe('hello<');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('attribute escaping (is_attr = true)', () => {
|
|
56
|
+
it('should escape & to &', () => {
|
|
57
|
+
expect(escape('foo & bar', true)).toBe('foo & bar');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should escape < to <', () => {
|
|
61
|
+
expect(escape('foo < bar', true)).toBe('foo < bar');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should escape " to "', () => {
|
|
65
|
+
expect(escape('foo "bar" baz', true)).toBe('foo "bar" baz');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should escape all three special characters', () => {
|
|
69
|
+
expect(escape('a & b < c "d"', true)).toBe('a & b < c "d"');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle empty string', () => {
|
|
73
|
+
expect(escape('', true)).toBe('');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle string with no special characters', () => {
|
|
77
|
+
expect(escape('hello world', true)).toBe('hello world');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle null values', () => {
|
|
81
|
+
expect(escape(null, true)).toBe('');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle undefined values', () => {
|
|
85
|
+
expect(escape(undefined, true)).toBe('');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should escape consecutive quotes', () => {
|
|
89
|
+
expect(escape('"""', true)).toBe('"""');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should handle mixed escaping', () => {
|
|
93
|
+
expect(escape('<div class="foo & bar">', true)).toBe('<div class="foo & bar">');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('default parameter behavior', () => {
|
|
98
|
+
it('should default to content escaping when is_attr is undefined', () => {
|
|
99
|
+
expect(escape('foo "bar"')).toBe('foo "bar"');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
is_delegated,
|
|
4
|
+
is_event_attribute,
|
|
5
|
+
is_capture_event,
|
|
6
|
+
get_attribute_event_name,
|
|
7
|
+
is_passive_event
|
|
8
|
+
} from '../../src/utils/events.js';
|
|
9
|
+
|
|
10
|
+
describe('events utility', () => {
|
|
11
|
+
describe('is_delegated', () => {
|
|
12
|
+
it('should return true for delegated events', () => {
|
|
13
|
+
expect(is_delegated('click')).toBe(true);
|
|
14
|
+
expect(is_delegated('input')).toBe(true);
|
|
15
|
+
expect(is_delegated('change')).toBe(true);
|
|
16
|
+
expect(is_delegated('mousedown')).toBe(true);
|
|
17
|
+
expect(is_delegated('keydown')).toBe(true);
|
|
18
|
+
expect(is_delegated('pointerdown')).toBe(true);
|
|
19
|
+
expect(is_delegated('touchstart')).toBe(true);
|
|
20
|
+
expect(is_delegated('focusin')).toBe(true);
|
|
21
|
+
expect(is_delegated('focusout')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return false for non-delegated events', () => {
|
|
25
|
+
expect(is_delegated('focus')).toBe(false);
|
|
26
|
+
expect(is_delegated('blur')).toBe(false);
|
|
27
|
+
expect(is_delegated('scroll')).toBe(false);
|
|
28
|
+
expect(is_delegated('load')).toBe(false);
|
|
29
|
+
expect(is_delegated('resize')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be case-sensitive', () => {
|
|
33
|
+
expect(is_delegated('Click')).toBe(false);
|
|
34
|
+
expect(is_delegated('CLICK')).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('is_event_attribute', () => {
|
|
39
|
+
it('should return true for valid event attributes', () => {
|
|
40
|
+
expect(is_event_attribute('onClick')).toBe(true);
|
|
41
|
+
expect(is_event_attribute('onInput')).toBe(true);
|
|
42
|
+
expect(is_event_attribute('onChange')).toBe(true);
|
|
43
|
+
expect(is_event_attribute('onMouseDown')).toBe(true);
|
|
44
|
+
expect(is_event_attribute('onKeyPress')).toBe(true);
|
|
45
|
+
expect(is_event_attribute('on-click')).toBe(true);
|
|
46
|
+
expect(is_event_attribute('on_click')).toBe(true);
|
|
47
|
+
expect(is_event_attribute('on$click')).toBe(true);
|
|
48
|
+
expect(is_event_attribute('on$')).toBe(true);
|
|
49
|
+
expect(is_event_attribute('on-')).toBe(true);
|
|
50
|
+
expect(is_event_attribute('on_')).toBe(true);
|
|
51
|
+
expect(is_event_attribute('on1')).toBe(true);
|
|
52
|
+
expect(is_event_attribute('on1click')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return false for non-event attributes', () => {
|
|
56
|
+
expect(is_event_attribute('on')).toBe(false);
|
|
57
|
+
expect(is_event_attribute('onclick')).toBe(false);
|
|
58
|
+
expect(is_event_attribute('class')).toBe(false);
|
|
59
|
+
expect(is_event_attribute('id')).toBe(false);
|
|
60
|
+
expect(is_event_attribute('value')).toBe(false);
|
|
61
|
+
expect(is_event_attribute('aria-label')).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should require uppercase third character', () => {
|
|
65
|
+
expect(is_event_attribute('onabc')).toBe(false);
|
|
66
|
+
expect(is_event_attribute('onAbc')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should require at least 3 characters', () => {
|
|
70
|
+
expect(is_event_attribute('onA')).toBe(true);
|
|
71
|
+
expect(is_event_attribute('on')).toBe(false);
|
|
72
|
+
expect(is_event_attribute('o')).toBe(false);
|
|
73
|
+
expect(is_event_attribute('')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('is_capture_event', () => {
|
|
78
|
+
it('should return true for capture events', () => {
|
|
79
|
+
expect(is_capture_event('clickCapture')).toBe(true);
|
|
80
|
+
expect(is_capture_event('mousedownCapture')).toBe(true);
|
|
81
|
+
expect(is_capture_event('keydownCapture')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false for non-capture events', () => {
|
|
85
|
+
expect(is_capture_event('click')).toBe(false);
|
|
86
|
+
expect(is_capture_event('mousedown')).toBe(false);
|
|
87
|
+
expect(is_capture_event('mousedown')).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should exclude gotpointercapture and lostpointercapture', () => {
|
|
91
|
+
expect(is_capture_event('gotpointercapture')).toBe(false);
|
|
92
|
+
expect(is_capture_event('lostpointercapture')).toBe(false);
|
|
93
|
+
expect(is_capture_event('gotPointerCapture')).toBe(false);
|
|
94
|
+
expect(is_capture_event('lostPointerCapture')).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should be case-insensitive for pointer capture events', () => {
|
|
98
|
+
expect(is_capture_event('GOTPOINTERCAPTURE')).toBe(false);
|
|
99
|
+
expect(is_capture_event('LOSTPOINTERCAPTURE')).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should be case-sensitive for other events', () => {
|
|
103
|
+
expect(is_capture_event('clickCapture')).toBe(true);
|
|
104
|
+
expect(is_capture_event('keypressCapture')).toBe(true);
|
|
105
|
+
expect(is_capture_event('clickcapture')).toBe(false);
|
|
106
|
+
expect(is_capture_event('keypresscapture')).toBe(false);
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('get_attribute_event_name', () => {
|
|
111
|
+
it('should convert event attribute names to lowercase and strip "on" prefix', () => {
|
|
112
|
+
expect(get_attribute_event_name('onClick')).toBe('click');
|
|
113
|
+
expect(get_attribute_event_name('onInput')).toBe('input');
|
|
114
|
+
expect(get_attribute_event_name('onMouseDown')).toBe('mousedown');
|
|
115
|
+
expect(get_attribute_event_name('onKeyPress')).toBe('keypress');
|
|
116
|
+
expect(get_attribute_event_name('onChange')).toBe('change');
|
|
117
|
+
expect(get_attribute_event_name('onFocus')).toBe('focus');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle capture events and strip both "on" and "Capture"', () => {
|
|
121
|
+
expect(get_attribute_event_name('onClickCapture')).toBe('click');
|
|
122
|
+
expect(get_attribute_event_name('onMouseDownCapture')).toBe('mousedown');
|
|
123
|
+
expect(get_attribute_event_name('onMouseDownCapture')).toBe('mousedown');
|
|
124
|
+
expect(get_attribute_event_name('onGotPointerCapture')).toBe('gotpointercapture');
|
|
125
|
+
expect(get_attribute_event_name('onLostPointerCapture')).toBe('lostpointercapture');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('is_passive_event', () => {
|
|
130
|
+
it('should return true for passive events', () => {
|
|
131
|
+
expect(is_passive_event('touchstart')).toBe(true);
|
|
132
|
+
expect(is_passive_event('touchmove')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return false for non-passive events', () => {
|
|
136
|
+
expect(is_passive_event('click')).toBe(false);
|
|
137
|
+
expect(is_passive_event('mousedown')).toBe(false);
|
|
138
|
+
expect(is_passive_event('touchend')).toBe(false);
|
|
139
|
+
expect(is_passive_event('scroll')).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should be case-sensitive', () => {
|
|
143
|
+
expect(is_passive_event('TouchStart')).toBe(false);
|
|
144
|
+
expect(is_passive_event('TOUCHMOVE')).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { normalize_css_property_name } from '../../src/utils/normalize_css_property_name.js';
|
|
3
|
+
|
|
4
|
+
describe('normalize_css_property_name utility', () => {
|
|
5
|
+
it('should convert camelCase to kebab-case', () => {
|
|
6
|
+
expect(normalize_css_property_name('backgroundColor')).toBe('background-color');
|
|
7
|
+
expect(normalize_css_property_name('fontSize')).toBe('font-size');
|
|
8
|
+
expect(normalize_css_property_name('marginTop')).toBe('margin-top');
|
|
9
|
+
expect(normalize_css_property_name('borderRadius')).toBe('border-radius');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should handle multiple uppercase letters', () => {
|
|
13
|
+
expect(normalize_css_property_name('WebkitTransform')).toBe('-webkit-transform');
|
|
14
|
+
expect(normalize_css_property_name('MozAppearance')).toBe('-moz-appearance');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should preserve CSS custom properties (starting with --)', () => {
|
|
18
|
+
expect(normalize_css_property_name('--custom-property')).toBe('--custom-property');
|
|
19
|
+
expect(normalize_css_property_name('--myColor')).toBe('--myColor');
|
|
20
|
+
expect(normalize_css_property_name('--themePrimary')).toBe('--themePrimary');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle already lowercase properties', () => {
|
|
24
|
+
expect(normalize_css_property_name('color')).toBe('color');
|
|
25
|
+
expect(normalize_css_property_name('display')).toBe('display');
|
|
26
|
+
expect(normalize_css_property_name('position')).toBe('position');
|
|
27
|
+
expect(normalize_css_property_name('z-index')).toBe('z-index');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle consecutive uppercase letters', () => {
|
|
31
|
+
expect(normalize_css_property_name('HTMLElement')).toBe('-h-t-m-l-element');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle empty string', () => {
|
|
35
|
+
expect(normalize_css_property_name('')).toBe('');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle complex property names', () => {
|
|
39
|
+
expect(normalize_css_property_name('borderTopLeftRadius')).toBe('border-top-left-radius');
|
|
40
|
+
expect(normalize_css_property_name('textDecorationColor')).toBe('text-decoration-color');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|