ripple 0.2.124 → 0.2.126
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/client/index.js +63 -33
- package/src/compiler/phases/3-transform/segments.js +36 -0
- package/src/compiler/phases/3-transform/server/index.js +50 -37
- package/src/compiler/utils.js +8 -8
- package/src/runtime/internal/client/render.js +5 -1
- package/src/runtime/internal/client/runtime.js +20 -5
- 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/client/function-overload-import.ripple +10 -0
- package/tests/client/function-overload.test.ripple +40 -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
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
TRY_BLOCK,
|
|
25
25
|
UNINITIALIZED,
|
|
26
26
|
REF_PROP,
|
|
27
|
+
TRACKED_OBJECT,
|
|
27
28
|
} from './constants.js';
|
|
28
29
|
import { capture, suspend } from './try.js';
|
|
29
30
|
import {
|
|
@@ -412,7 +413,7 @@ function is_tracking_dirty(tracking) {
|
|
|
412
413
|
var tracked = tracking.t;
|
|
413
414
|
|
|
414
415
|
if ((tracked.f & DERIVED) !== 0) {
|
|
415
|
-
update_derived(/** @type {Derived} **/
|
|
416
|
+
update_derived(/** @type {Derived} **/(tracked));
|
|
416
417
|
}
|
|
417
418
|
|
|
418
419
|
if (tracked.c > tracking.c) {
|
|
@@ -472,7 +473,7 @@ export function async_computed(fn, block) {
|
|
|
472
473
|
}
|
|
473
474
|
|
|
474
475
|
promise.then((v) => {
|
|
475
|
-
if (parent && is_destroyed(/** @type {Block} */
|
|
476
|
+
if (parent && is_destroyed(/** @type {Block} */(parent))) {
|
|
476
477
|
return;
|
|
477
478
|
}
|
|
478
479
|
if (promise === current && t.v !== v) {
|
|
@@ -746,7 +747,7 @@ export function get(tracked) {
|
|
|
746
747
|
}
|
|
747
748
|
|
|
748
749
|
return (tracked.f & DERIVED) !== 0
|
|
749
|
-
? get_derived(/** @type {Derived} */
|
|
750
|
+
? get_derived(/** @type {Derived} */(tracked))
|
|
750
751
|
: get_tracked(tracked);
|
|
751
752
|
}
|
|
752
753
|
|
|
@@ -872,7 +873,7 @@ export function flush_sync(fn) {
|
|
|
872
873
|
* @returns {Object}
|
|
873
874
|
*/
|
|
874
875
|
export function spread_props(fn, block) {
|
|
875
|
-
|
|
876
|
+
var computed = derived(fn, block);
|
|
876
877
|
|
|
877
878
|
return new Proxy(
|
|
878
879
|
{},
|
|
@@ -882,9 +883,23 @@ export function spread_props(fn, block) {
|
|
|
882
883
|
return obj[property];
|
|
883
884
|
},
|
|
884
885
|
has(target, property) {
|
|
886
|
+
if (property === TRACKED_OBJECT) {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
885
889
|
const obj = get_derived(computed);
|
|
886
890
|
return property in obj;
|
|
887
891
|
},
|
|
892
|
+
getOwnPropertyDescriptor(target, key) {
|
|
893
|
+
const obj = get_derived(computed);
|
|
894
|
+
|
|
895
|
+
if (key in obj) {
|
|
896
|
+
return {
|
|
897
|
+
enumerable: true,
|
|
898
|
+
configurable: true,
|
|
899
|
+
value: obj[key],
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
},
|
|
888
903
|
ownKeys() {
|
|
889
904
|
const obj = get_derived(computed);
|
|
890
905
|
return Reflect.ownKeys(obj);
|
|
@@ -1164,7 +1179,7 @@ export async function maybe_tracked(v) {
|
|
|
1164
1179
|
} else {
|
|
1165
1180
|
value = await async_computed(async () => {
|
|
1166
1181
|
return await get_tracked(v);
|
|
1167
|
-
}, /** @type {Block} */
|
|
1182
|
+
}, /** @type {Block} */(active_block));
|
|
1168
1183
|
}
|
|
1169
1184
|
} else {
|
|
1170
1185
|
value = await v;
|
|
@@ -30,6 +30,8 @@ class Output {
|
|
|
30
30
|
body = '';
|
|
31
31
|
/** @type {Set<string>} */
|
|
32
32
|
css = new Set();
|
|
33
|
+
/** @type {Promise<any>[]} */
|
|
34
|
+
promises = [];
|
|
33
35
|
/** @type {Output | null} */
|
|
34
36
|
#parent = null;
|
|
35
37
|
|
|
@@ -60,18 +62,27 @@ class Output {
|
|
|
60
62
|
/** @type {render} */
|
|
61
63
|
export async function render(component) {
|
|
62
64
|
const output = new Output(null);
|
|
65
|
+
let head, body, css;
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
try {
|
|
68
|
+
if (component.async) {
|
|
69
|
+
await component(output, {});
|
|
70
|
+
} else {
|
|
71
|
+
component(output, {});
|
|
72
|
+
}
|
|
73
|
+
if (output.promises.length > 0) {
|
|
74
|
+
await Promise.all(output.promises);
|
|
75
|
+
}
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
head = output.head
|
|
78
|
+
body = output.body
|
|
79
|
+
css = output.css
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.log(error)
|
|
83
|
+
}
|
|
84
|
+
return { head, body, css }
|
|
73
85
|
}
|
|
74
|
-
|
|
75
86
|
/**
|
|
76
87
|
* @returns {void}
|
|
77
88
|
*/
|
package/src/utils/builders.js
CHANGED
|
@@ -237,14 +237,14 @@ export function export_builder(declaration, specifiers = [], attributes = [], so
|
|
|
237
237
|
* @param {ESTree.BlockStatement} body
|
|
238
238
|
* @returns {ESTree.FunctionDeclaration}
|
|
239
239
|
*/
|
|
240
|
-
export function function_declaration(id, params, body) {
|
|
240
|
+
export function function_declaration(id, params, body, async = false) {
|
|
241
241
|
return {
|
|
242
242
|
type: 'FunctionDeclaration',
|
|
243
243
|
id,
|
|
244
244
|
params,
|
|
245
245
|
body,
|
|
246
246
|
generator: false,
|
|
247
|
-
async
|
|
247
|
+
async,
|
|
248
248
|
metadata: /** @type {any} */ (null), // should not be used by codegen
|
|
249
249
|
};
|
|
250
250
|
}
|
|
@@ -238,86 +238,6 @@ exports[`for statements > handles updating with new objects with same key 2`] =
|
|
|
238
238
|
</div>
|
|
239
239
|
`;
|
|
240
240
|
|
|
241
|
-
exports[`for statements > render a simple dynamic array 1`] = `
|
|
242
|
-
<div>
|
|
243
|
-
<!---->
|
|
244
|
-
<div
|
|
245
|
-
class="Item 1"
|
|
246
|
-
>
|
|
247
|
-
Item 1
|
|
248
|
-
</div>
|
|
249
|
-
<div
|
|
250
|
-
class="Item 2"
|
|
251
|
-
>
|
|
252
|
-
Item 2
|
|
253
|
-
</div>
|
|
254
|
-
<div
|
|
255
|
-
class="Item 3"
|
|
256
|
-
>
|
|
257
|
-
Item 3
|
|
258
|
-
</div>
|
|
259
|
-
<!---->
|
|
260
|
-
<button>
|
|
261
|
-
Add Item
|
|
262
|
-
</button>
|
|
263
|
-
|
|
264
|
-
</div>
|
|
265
|
-
`;
|
|
266
|
-
|
|
267
|
-
exports[`for statements > render a simple dynamic array 2`] = `
|
|
268
|
-
<div>
|
|
269
|
-
<!---->
|
|
270
|
-
<div
|
|
271
|
-
class="Item 1"
|
|
272
|
-
>
|
|
273
|
-
Item 1
|
|
274
|
-
</div>
|
|
275
|
-
<div
|
|
276
|
-
class="Item 2"
|
|
277
|
-
>
|
|
278
|
-
Item 2
|
|
279
|
-
</div>
|
|
280
|
-
<div
|
|
281
|
-
class="Item 3"
|
|
282
|
-
>
|
|
283
|
-
Item 3
|
|
284
|
-
</div>
|
|
285
|
-
<div
|
|
286
|
-
class="Item 4"
|
|
287
|
-
>
|
|
288
|
-
Item 4
|
|
289
|
-
</div>
|
|
290
|
-
<!---->
|
|
291
|
-
<button>
|
|
292
|
-
Add Item
|
|
293
|
-
</button>
|
|
294
|
-
|
|
295
|
-
</div>
|
|
296
|
-
`;
|
|
297
|
-
|
|
298
|
-
exports[`for statements > render a simple static array 1`] = `
|
|
299
|
-
<div>
|
|
300
|
-
<!---->
|
|
301
|
-
<div
|
|
302
|
-
class="Item 1"
|
|
303
|
-
>
|
|
304
|
-
Item 1
|
|
305
|
-
</div>
|
|
306
|
-
<div
|
|
307
|
-
class="Item 2"
|
|
308
|
-
>
|
|
309
|
-
Item 2
|
|
310
|
-
</div>
|
|
311
|
-
<div
|
|
312
|
-
class="Item 3"
|
|
313
|
-
>
|
|
314
|
-
Item 3
|
|
315
|
-
</div>
|
|
316
|
-
<!---->
|
|
317
|
-
|
|
318
|
-
</div>
|
|
319
|
-
`;
|
|
320
|
-
|
|
321
241
|
exports[`for statements > renders a simple dynamic array 1`] = `
|
|
322
242
|
<div>
|
|
323
243
|
<!---->
|
|
@@ -56,51 +56,3 @@ exports[`basic client > rendering & text > should handle lexical scopes correctl
|
|
|
56
56
|
|
|
57
57
|
</div>
|
|
58
58
|
`;
|
|
59
|
-
|
|
60
|
-
exports[`basic client > text rendering > basic operations 1`] = `
|
|
61
|
-
<div>
|
|
62
|
-
<div>
|
|
63
|
-
0
|
|
64
|
-
</div>
|
|
65
|
-
<div>
|
|
66
|
-
2
|
|
67
|
-
</div>
|
|
68
|
-
<div>
|
|
69
|
-
5
|
|
70
|
-
</div>
|
|
71
|
-
<div>
|
|
72
|
-
2
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
</div>
|
|
76
|
-
`;
|
|
77
|
-
|
|
78
|
-
exports[`basic client > text rendering > renders semi-dynamic text 1`] = `
|
|
79
|
-
<div>
|
|
80
|
-
<div>
|
|
81
|
-
Hello World
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
</div>
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
exports[`basic client > text rendering > renders simple JS expression logic correctly 1`] = `
|
|
88
|
-
<div>
|
|
89
|
-
<div>
|
|
90
|
-
{"0":"Test"}
|
|
91
|
-
</div>
|
|
92
|
-
<div>
|
|
93
|
-
1
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
</div>
|
|
97
|
-
`;
|
|
98
|
-
|
|
99
|
-
exports[`basic client > text rendering > renders static text 1`] = `
|
|
100
|
-
<div>
|
|
101
|
-
<div>
|
|
102
|
-
Hello World
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
</div>
|
|
106
|
-
`;
|
|
@@ -123,4 +123,20 @@ describe('basic client > errors', () => {
|
|
|
123
123
|
|
|
124
124
|
expect(error).toBe('Assignments or updates to tracked values are not allowed during computed "track(() => ...)" evaluation');
|
|
125
125
|
});
|
|
126
|
+
|
|
127
|
+
it('should throw error for await in client-side control-flow statements', () => {
|
|
128
|
+
const code = `
|
|
129
|
+
export default component App() {
|
|
130
|
+
let data = 'initial';
|
|
131
|
+
if (true) {
|
|
132
|
+
await new Promise(r => setTimeout(r, 100));
|
|
133
|
+
data = 'loaded';
|
|
134
|
+
}
|
|
135
|
+
<div>{data}</div>
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
expect(() => {
|
|
139
|
+
compile(code, 'test.ripple', { mode: 'client' });
|
|
140
|
+
}).toThrow('`await` is not allowed in client-side control-flow statements');
|
|
141
|
+
});
|
|
126
142
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync } from 'ripple';
|
|
3
|
+
|
|
4
|
+
import { test } from './function-overload-import.ripple';
|
|
5
|
+
|
|
6
|
+
describe('function overload import tests', () => {
|
|
7
|
+
describe('function overloads', () => {
|
|
8
|
+
it('test function with string argument returns the string', () => {
|
|
9
|
+
const result = test('hello');
|
|
10
|
+
expect(result).toBe('hello');
|
|
11
|
+
expect(typeof result).toBe('string');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('test function with number argument returns string representation', () => {
|
|
15
|
+
const result = test(42);
|
|
16
|
+
expect(result).toBe('42');
|
|
17
|
+
expect(typeof result).toBe('string');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('test function with zero returns "0"', () => {
|
|
21
|
+
const result = test(0);
|
|
22
|
+
expect(result).toBe('0');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('test function with negative number returns string representation', () => {
|
|
26
|
+
const result = test(-100);
|
|
27
|
+
expect(result).toBe('-100');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('test function with empty string returns empty string', () => {
|
|
31
|
+
const result = test('');
|
|
32
|
+
expect(result).toBe('');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('test function with decimal number returns string representation', () => {
|
|
36
|
+
const result = test(3.14159);
|
|
37
|
+
expect(result).toBe('3.14159');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ripple/server';
|
|
3
|
+
import { track, set, get } from 'ripple';
|
|
4
|
+
|
|
5
|
+
describe('await in control flow', () => {
|
|
6
|
+
it('should handle await inside if statement', async () => {
|
|
7
|
+
component App() {
|
|
8
|
+
let condition = true;
|
|
9
|
+
let data = track('loading');
|
|
10
|
+
|
|
11
|
+
if (condition) {
|
|
12
|
+
await new Promise(resolve => setTimeout(() => {
|
|
13
|
+
@data = 'loaded';
|
|
14
|
+
resolve();
|
|
15
|
+
}, 10));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
<div>{@data}</div>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { body } = await render(App);
|
|
22
|
+
expect(body).toBe('<div>loaded</div>');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should handle await inside for...of loop', async () => {
|
|
26
|
+
component App() {
|
|
27
|
+
const items = [1, 2, 3];
|
|
28
|
+
let result = '';
|
|
29
|
+
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
32
|
+
result += item;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
<div>{result}</div>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { body } = await render(App);
|
|
39
|
+
expect(body).toBe('<div>123</div>');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle await inside switch statement', async () => {
|
|
43
|
+
component App() {
|
|
44
|
+
let value = 'b';
|
|
45
|
+
|
|
46
|
+
switch (value) {
|
|
47
|
+
case 'a':
|
|
48
|
+
<div>{'Case A'}</div>
|
|
49
|
+
break;
|
|
50
|
+
case 'b':
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
52
|
+
<div>{'Case B'}</div>
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
<div>{'Default Case'}</div>
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { body } = await render(App);
|
|
60
|
+
expect(body).toBe('<div>Case B</div>');
|
|
61
|
+
});});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ripple/server';
|
|
3
|
+
|
|
4
|
+
describe('for statements in SSR', () => {
|
|
5
|
+
it('renders a simple static array', async () => {
|
|
6
|
+
component App() {
|
|
7
|
+
const items = ['Item 1', 'Item 2', 'Item 3'];
|
|
8
|
+
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
<div class={item}>{item}</div>
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { body } = await render(App);
|
|
15
|
+
expect(body).toBe('<div class="Item 1">Item 1</div><div class="Item 2">Item 2</div><div class="Item 3">Item 3</div>');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders nested for...of loops', async () => {
|
|
19
|
+
component App() {
|
|
20
|
+
const groups = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Group 1',
|
|
23
|
+
items: ['Item 1.1', 'Item 1.2']
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'Group 2',
|
|
27
|
+
items: ['Item 2.1', 'Item 2.2']
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const group of groups) {
|
|
32
|
+
<h1>{group.name}</h1>
|
|
33
|
+
<ul>
|
|
34
|
+
for (const item of group.items) {
|
|
35
|
+
<li>{item}</li>
|
|
36
|
+
}
|
|
37
|
+
</ul>
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { body } = await render(App);
|
|
42
|
+
expect(body).toBe('<h1>Group 1</h1><ul><li>Item 1.1</li><li>Item 1.2</li></ul><h1>Group 2</h1><ul><li>Item 2.1</li><li>Item 2.2</li></ul>');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -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
|
+
});
|