@dom-expressions/tagged-jsx 0.50.0-next.15
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 +96 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/index.d.mts +12544 -0
- package/dist/index.mjs +2 -0
- package/package.json +32 -0
- package/src/index.ts +2 -0
- package/src/parse.ts +257 -0
- package/src/tagged-jsx.ts +247 -0
- package/src/tokenize.ts +260 -0
- package/src/types.ts +69 -0
- package/tests/core.ts +27 -0
- package/tests/parse.test.ts +971 -0
- package/tests/tagged-jsx.test.ts +1119 -0
- package/tests/tokenize.test.ts +1019 -0
- package/tsconfig.json +15 -0
- package/tsdown.config.mjs +5 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
CLOSE_TAG_TOKEN,
|
|
4
|
+
EQUALS_TOKEN,
|
|
5
|
+
EXPRESSION_TOKEN,
|
|
6
|
+
IDENTIFIER_TOKEN,
|
|
7
|
+
OPEN_TAG_TOKEN,
|
|
8
|
+
SLASH_TOKEN,
|
|
9
|
+
STRING_TOKEN,
|
|
10
|
+
TEXT_TOKEN,
|
|
11
|
+
tokenize
|
|
12
|
+
} from "../src/tokenize";
|
|
13
|
+
import { RawTextElements } from "./core.js";
|
|
14
|
+
|
|
15
|
+
function tokenizeTemplate(strings: TemplateStringsArray, ...values: any[]) {
|
|
16
|
+
return tokenize(strings, RawTextElements);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("basic tags", () => {
|
|
20
|
+
it("should tokenize opening tag", () => {
|
|
21
|
+
const tokens = tokenizeTemplate`<div`;
|
|
22
|
+
|
|
23
|
+
expect(tokens).toEqual([
|
|
24
|
+
{
|
|
25
|
+
type: OPEN_TAG_TOKEN
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: IDENTIFIER_TOKEN,
|
|
29
|
+
value: "div"
|
|
30
|
+
}
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should tokenize complete tag", () => {
|
|
35
|
+
const tokens = tokenizeTemplate`<div>`;
|
|
36
|
+
|
|
37
|
+
expect(tokens).toEqual([
|
|
38
|
+
{
|
|
39
|
+
type: OPEN_TAG_TOKEN
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: IDENTIFIER_TOKEN,
|
|
43
|
+
value: "div"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: CLOSE_TAG_TOKEN
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should tokenize self-closing tag", () => {
|
|
52
|
+
const tokens = tokenizeTemplate`<div />`;
|
|
53
|
+
|
|
54
|
+
expect(tokens).toEqual([
|
|
55
|
+
{
|
|
56
|
+
type: OPEN_TAG_TOKEN
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: IDENTIFIER_TOKEN,
|
|
60
|
+
value: "div"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: SLASH_TOKEN
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: CLOSE_TAG_TOKEN
|
|
67
|
+
}
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should tokenize opening and closing tag", () => {
|
|
72
|
+
const tokens = tokenizeTemplate`<div></div>`;
|
|
73
|
+
|
|
74
|
+
expect(tokens).toEqual([
|
|
75
|
+
{
|
|
76
|
+
type: OPEN_TAG_TOKEN
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: IDENTIFIER_TOKEN,
|
|
80
|
+
value: "div"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: CLOSE_TAG_TOKEN
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: OPEN_TAG_TOKEN
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: SLASH_TOKEN
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: IDENTIFIER_TOKEN,
|
|
93
|
+
value: "div"
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
type: CLOSE_TAG_TOKEN
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("attribute values", () => {
|
|
104
|
+
it("should tokenize quoted string", () => {
|
|
105
|
+
const tokens = tokenizeTemplate`<div id="hello">`;
|
|
106
|
+
|
|
107
|
+
expect(tokens).toEqual([
|
|
108
|
+
{
|
|
109
|
+
type: OPEN_TAG_TOKEN
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: IDENTIFIER_TOKEN,
|
|
113
|
+
value: "div"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: IDENTIFIER_TOKEN,
|
|
117
|
+
value: "id"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: EQUALS_TOKEN
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: STRING_TOKEN,
|
|
124
|
+
value: "hello",
|
|
125
|
+
quote: '"'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: CLOSE_TAG_TOKEN
|
|
129
|
+
}
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should tokenize single quoted string", () => {
|
|
134
|
+
const tokens = tokenizeTemplate`<div id='hello'>`;
|
|
135
|
+
|
|
136
|
+
expect(tokens).toEqual([
|
|
137
|
+
{
|
|
138
|
+
type: OPEN_TAG_TOKEN
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: IDENTIFIER_TOKEN,
|
|
142
|
+
value: "div"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: IDENTIFIER_TOKEN,
|
|
146
|
+
value: "id"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: EQUALS_TOKEN
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
type: STRING_TOKEN,
|
|
153
|
+
value: "hello",
|
|
154
|
+
quote: "'"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: CLOSE_TAG_TOKEN
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should handle empty quoted string", () => {
|
|
163
|
+
const tokens = tokenizeTemplate`<div class="">`;
|
|
164
|
+
|
|
165
|
+
expect(tokens).toEqual([
|
|
166
|
+
{
|
|
167
|
+
type: OPEN_TAG_TOKEN
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: IDENTIFIER_TOKEN,
|
|
171
|
+
value: "div"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: IDENTIFIER_TOKEN,
|
|
175
|
+
value: "class"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: EQUALS_TOKEN
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: STRING_TOKEN,
|
|
182
|
+
value: "",
|
|
183
|
+
quote: '"'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: CLOSE_TAG_TOKEN
|
|
187
|
+
}
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should handle boolean like attribute", () => {
|
|
192
|
+
const tokens = tokenizeTemplate`<div enabled bool>`;
|
|
193
|
+
|
|
194
|
+
expect(tokens).toEqual([
|
|
195
|
+
{
|
|
196
|
+
type: OPEN_TAG_TOKEN
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: IDENTIFIER_TOKEN,
|
|
200
|
+
value: "div"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: IDENTIFIER_TOKEN,
|
|
204
|
+
value: "enabled"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
type: IDENTIFIER_TOKEN,
|
|
208
|
+
value: "bool"
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
{
|
|
212
|
+
type: CLOSE_TAG_TOKEN
|
|
213
|
+
}
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should handle deeply nested quotes", () => {
|
|
218
|
+
const tokens = tokenizeTemplate`<div data="value with 'nested' quotes">`;
|
|
219
|
+
|
|
220
|
+
expect(tokens).toContainEqual(
|
|
221
|
+
expect.objectContaining({
|
|
222
|
+
type: STRING_TOKEN,
|
|
223
|
+
value: "value with 'nested' quotes",
|
|
224
|
+
quote: '"'
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should handle attribute values with special characters", () => {
|
|
230
|
+
const tokens = tokenizeTemplate`<div data="!@#$%^&*()_+-=[]{}|;:,.<>?">`;
|
|
231
|
+
|
|
232
|
+
expect(tokens).toContainEqual(
|
|
233
|
+
expect.objectContaining({
|
|
234
|
+
type: STRING_TOKEN,
|
|
235
|
+
value: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
|
236
|
+
quote: '"'
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should handle URL-like attribute values", () => {
|
|
242
|
+
const tokens = tokenizeTemplate`<a href="https://example.com/path?query=value&other=test#section">`;
|
|
243
|
+
|
|
244
|
+
expect(tokens).toContainEqual(
|
|
245
|
+
expect.objectContaining({
|
|
246
|
+
type: STRING_TOKEN,
|
|
247
|
+
value: "https://example.com/path?query=value&other=test#section",
|
|
248
|
+
quote: '"'
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("attribute name doesnt trigger raw text", () => {
|
|
254
|
+
const tokens = tokenizeTemplate`
|
|
255
|
+
<h1 title=""></h1>
|
|
256
|
+
`;
|
|
257
|
+
|
|
258
|
+
expect(tokens).toEqual([
|
|
259
|
+
{ type: TEXT_TOKEN, value: "\n " },
|
|
260
|
+
{ type: OPEN_TAG_TOKEN },
|
|
261
|
+
{ type: IDENTIFIER_TOKEN, value: "h1" },
|
|
262
|
+
{ type: IDENTIFIER_TOKEN, value: "title" },
|
|
263
|
+
{ type: EQUALS_TOKEN },
|
|
264
|
+
{ type: STRING_TOKEN, value: "", quote: '"' },
|
|
265
|
+
|
|
266
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
267
|
+
{ type: OPEN_TAG_TOKEN },
|
|
268
|
+
{ type: SLASH_TOKEN },
|
|
269
|
+
{ type: IDENTIFIER_TOKEN, value: "h1" },
|
|
270
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
271
|
+
{ type: TEXT_TOKEN, value: "\n " }
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("expressions", () => {
|
|
277
|
+
it("should tokenize simple expression", () => {
|
|
278
|
+
const value = "test";
|
|
279
|
+
const tokens = tokenizeTemplate`${value}`;
|
|
280
|
+
|
|
281
|
+
expect(tokens).toEqual([
|
|
282
|
+
{
|
|
283
|
+
type: EXPRESSION_TOKEN,
|
|
284
|
+
value: 0
|
|
285
|
+
}
|
|
286
|
+
]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should tokenize multiple expressions", () => {
|
|
290
|
+
const a = "first";
|
|
291
|
+
const b = "second";
|
|
292
|
+
const tokens = tokenizeTemplate`${a}${b}`;
|
|
293
|
+
|
|
294
|
+
expect(tokens).toEqual([
|
|
295
|
+
{
|
|
296
|
+
type: EXPRESSION_TOKEN,
|
|
297
|
+
value: 0
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
type: EXPRESSION_TOKEN,
|
|
301
|
+
value: 1
|
|
302
|
+
}
|
|
303
|
+
]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should handle expression in unquoted attribute", () => {
|
|
307
|
+
const id = "my-id";
|
|
308
|
+
const tokens = tokenizeTemplate`<div id=${id}>`;
|
|
309
|
+
|
|
310
|
+
expect(tokens).toEqual([
|
|
311
|
+
{
|
|
312
|
+
type: OPEN_TAG_TOKEN
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
type: IDENTIFIER_TOKEN,
|
|
316
|
+
value: "div"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
type: IDENTIFIER_TOKEN,
|
|
320
|
+
value: "id"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
type: EQUALS_TOKEN
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
type: EXPRESSION_TOKEN,
|
|
327
|
+
value: 0
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: CLOSE_TAG_TOKEN
|
|
331
|
+
}
|
|
332
|
+
]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should handle mixed text and expressions", () => {
|
|
336
|
+
const name = "World";
|
|
337
|
+
const tokens = tokenizeTemplate`Hello ${name}!`;
|
|
338
|
+
|
|
339
|
+
expect(tokens).toEqual([
|
|
340
|
+
{
|
|
341
|
+
type: TEXT_TOKEN,
|
|
342
|
+
value: "Hello "
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
type: EXPRESSION_TOKEN,
|
|
346
|
+
value: 0
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: TEXT_TOKEN,
|
|
350
|
+
value: "!"
|
|
351
|
+
}
|
|
352
|
+
]);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("should handle data attributes with hyphens and underscores", () => {
|
|
356
|
+
const tokens = tokenizeTemplate`<div data-my_value="test" data_other-name="value">`;
|
|
357
|
+
|
|
358
|
+
const attrNames = tokens.filter(
|
|
359
|
+
t => t.type === IDENTIFIER_TOKEN && (t.value as string).includes("data")
|
|
360
|
+
);
|
|
361
|
+
expect(attrNames.length).toBeGreaterThanOrEqual(2);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("whitespace handling", () => {
|
|
366
|
+
it("should skip whitespace inside tags", () => {
|
|
367
|
+
const tokens = tokenizeTemplate`< \n div id = "app" >`;
|
|
368
|
+
|
|
369
|
+
// Should not have whitespace tokens in tag context
|
|
370
|
+
expect(tokens).toEqual([
|
|
371
|
+
{
|
|
372
|
+
type: OPEN_TAG_TOKEN
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: IDENTIFIER_TOKEN,
|
|
376
|
+
value: "div"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: IDENTIFIER_TOKEN,
|
|
380
|
+
value: "id"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
type: EQUALS_TOKEN
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
{
|
|
387
|
+
type: STRING_TOKEN,
|
|
388
|
+
value: "app",
|
|
389
|
+
quote: '"'
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
{
|
|
393
|
+
type: CLOSE_TAG_TOKEN
|
|
394
|
+
}
|
|
395
|
+
]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should preserve text content whitespace", () => {
|
|
399
|
+
const tokens = tokenizeTemplate` Hello World `;
|
|
400
|
+
|
|
401
|
+
expect(tokens).toEqual([
|
|
402
|
+
{
|
|
403
|
+
type: TEXT_TOKEN,
|
|
404
|
+
value: " Hello World "
|
|
405
|
+
}
|
|
406
|
+
]);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("should handle multiline content with preserved whitespace", () => {
|
|
410
|
+
const tokens = tokenizeTemplate`<div>
|
|
411
|
+
Hello
|
|
412
|
+
</div>`;
|
|
413
|
+
|
|
414
|
+
expect(tokens).toEqual([
|
|
415
|
+
{
|
|
416
|
+
type: OPEN_TAG_TOKEN
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
type: IDENTIFIER_TOKEN,
|
|
420
|
+
value: "div"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
type: CLOSE_TAG_TOKEN
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
type: TEXT_TOKEN,
|
|
427
|
+
value: "\n Hello\n "
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
type: OPEN_TAG_TOKEN
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
type: SLASH_TOKEN
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
type: IDENTIFIER_TOKEN,
|
|
437
|
+
value: "div"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
type: CLOSE_TAG_TOKEN
|
|
441
|
+
}
|
|
442
|
+
]);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("should handle tabs and mixed whitespace", () => {
|
|
446
|
+
const tokens = tokenizeTemplate`\tHello\nWorld `;
|
|
447
|
+
|
|
448
|
+
expect(tokens).toEqual([
|
|
449
|
+
{
|
|
450
|
+
type: TEXT_TOKEN,
|
|
451
|
+
value: "\tHello\nWorld "
|
|
452
|
+
}
|
|
453
|
+
]);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("should handle whitespace around expressions", () => {
|
|
457
|
+
const name = "test";
|
|
458
|
+
const tokens = tokenizeTemplate` ${name} `;
|
|
459
|
+
|
|
460
|
+
expect(tokens).toEqual([
|
|
461
|
+
{
|
|
462
|
+
type: TEXT_TOKEN,
|
|
463
|
+
value: " "
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
type: EXPRESSION_TOKEN,
|
|
467
|
+
value: 0
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
type: TEXT_TOKEN,
|
|
471
|
+
value: " "
|
|
472
|
+
}
|
|
473
|
+
]);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe("edge cases", () => {
|
|
478
|
+
it("should handle empty template", () => {
|
|
479
|
+
const tokens = tokenizeTemplate``;
|
|
480
|
+
|
|
481
|
+
expect(tokens).toEqual([]);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("should handle only whitespace", () => {
|
|
485
|
+
const tokens = tokenizeTemplate` `;
|
|
486
|
+
|
|
487
|
+
expect(tokens).toEqual([
|
|
488
|
+
{
|
|
489
|
+
type: TEXT_TOKEN,
|
|
490
|
+
value: " "
|
|
491
|
+
}
|
|
492
|
+
]);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("should handle special characters in text", () => {
|
|
496
|
+
const tokens = tokenizeTemplate`Hello & goodbye`;
|
|
497
|
+
|
|
498
|
+
expect(tokens).toEqual([
|
|
499
|
+
{
|
|
500
|
+
type: TEXT_TOKEN,
|
|
501
|
+
value: "Hello & goodbye"
|
|
502
|
+
}
|
|
503
|
+
]);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("should handle consecutive expressions", () => {
|
|
507
|
+
const a = "first";
|
|
508
|
+
const b = "second";
|
|
509
|
+
const tokens = tokenizeTemplate`${a}${b}`;
|
|
510
|
+
|
|
511
|
+
expect(tokens).toEqual([
|
|
512
|
+
{
|
|
513
|
+
type: EXPRESSION_TOKEN,
|
|
514
|
+
value: 0
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
type: EXPRESSION_TOKEN,
|
|
518
|
+
value: 1
|
|
519
|
+
}
|
|
520
|
+
]);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("should handle 1 letter tags", () => {
|
|
524
|
+
const tokens = tokenizeTemplate`<tr class=${0}>
|
|
525
|
+
<td class="col-md-1" textContent=${1} />
|
|
526
|
+
<td class="col-md-4">
|
|
527
|
+
<a onClick=${2} textContent=${3} />
|
|
528
|
+
</td>
|
|
529
|
+
<td class="col-md-1">
|
|
530
|
+
<a onClick=${4}>
|
|
531
|
+
<span class="glyphicon glyphicon-remove" aria-hidden="true" />
|
|
532
|
+
</a>
|
|
533
|
+
</td>
|
|
534
|
+
<td class="col-md-6" />
|
|
535
|
+
</tr>`;
|
|
536
|
+
expect(tokens.filter(t => t.type === IDENTIFIER_TOKEN && t.value === "a").length).toBe(3);
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
describe("special characters in names", () => {
|
|
541
|
+
it("should tokenize tag with hyphens", () => {
|
|
542
|
+
const tokens = tokenizeTemplate`<my-component />`;
|
|
543
|
+
|
|
544
|
+
expect(tokens).toEqual([
|
|
545
|
+
{
|
|
546
|
+
type: OPEN_TAG_TOKEN
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
type: IDENTIFIER_TOKEN,
|
|
550
|
+
value: "my-component"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
type: SLASH_TOKEN
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
type: CLOSE_TAG_TOKEN
|
|
557
|
+
}
|
|
558
|
+
]);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("should tokenize tag with periods", () => {
|
|
562
|
+
const tokens = tokenizeTemplate`<my.component />`;
|
|
563
|
+
|
|
564
|
+
expect(tokens).toEqual([
|
|
565
|
+
{
|
|
566
|
+
type: OPEN_TAG_TOKEN
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
type: IDENTIFIER_TOKEN,
|
|
570
|
+
value: "my.component"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
type: SLASH_TOKEN
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
type: CLOSE_TAG_TOKEN
|
|
577
|
+
}
|
|
578
|
+
]);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should tokenize tag with colons", () => {
|
|
582
|
+
const tokens = tokenizeTemplate`<svg:rect />`;
|
|
583
|
+
|
|
584
|
+
expect(tokens).toEqual([
|
|
585
|
+
{
|
|
586
|
+
type: OPEN_TAG_TOKEN
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
type: IDENTIFIER_TOKEN,
|
|
590
|
+
value: "svg:rect"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
type: SLASH_TOKEN
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
type: CLOSE_TAG_TOKEN
|
|
597
|
+
}
|
|
598
|
+
]);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("should tokenize tag with underscores", () => {
|
|
602
|
+
const tokens = tokenizeTemplate`<my_component />`;
|
|
603
|
+
|
|
604
|
+
expect(tokens).toEqual([
|
|
605
|
+
{
|
|
606
|
+
type: OPEN_TAG_TOKEN
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
type: IDENTIFIER_TOKEN,
|
|
610
|
+
value: "my_component"
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
type: SLASH_TOKEN
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
type: CLOSE_TAG_TOKEN
|
|
617
|
+
}
|
|
618
|
+
]);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("should tokenize attribute with -_.:$", () => {
|
|
622
|
+
const tokens = tokenizeTemplate`<div data-id data_id data.id data:id dataid$>`;
|
|
623
|
+
|
|
624
|
+
expect(tokens).toEqual([
|
|
625
|
+
{
|
|
626
|
+
type: OPEN_TAG_TOKEN
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
type: IDENTIFIER_TOKEN,
|
|
630
|
+
value: "div"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
type: IDENTIFIER_TOKEN,
|
|
634
|
+
value: "data-id"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
type: IDENTIFIER_TOKEN,
|
|
638
|
+
value: "data_id"
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
type: IDENTIFIER_TOKEN,
|
|
642
|
+
value: "data.id"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
type: IDENTIFIER_TOKEN,
|
|
646
|
+
value: "data:id"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
type: IDENTIFIER_TOKEN,
|
|
650
|
+
value: "dataid$"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
type: CLOSE_TAG_TOKEN
|
|
654
|
+
}
|
|
655
|
+
]);
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
describe("invalid syntax", () => {
|
|
660
|
+
it("should throw with extra <", () => {
|
|
661
|
+
expect(() => tokenizeTemplate`<<div / >`).toThrow();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("should throw with extra <", () => {
|
|
665
|
+
expect(() => tokenizeTemplate`<div / <>`).toThrow();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it("should throw on invalid identofier", () => {
|
|
669
|
+
expect(() => tokenizeTemplate`<.div />`).toThrow();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("should throw on invalid identofier", () => {
|
|
673
|
+
expect(() => tokenizeTemplate`<div @fa />`).toThrow();
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it("should throw on invalid identofier", () => {
|
|
677
|
+
expect(() => tokenizeTemplate`<div 0fa />`).toThrow();
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
describe("bad but valid syntaxes", () => {
|
|
682
|
+
it("should handle multiple attributes in tight syntax", () => {
|
|
683
|
+
const tokens = tokenizeTemplate`<div a="1"b="2"c="3">`;
|
|
684
|
+
|
|
685
|
+
const attrNames = tokens.filter(
|
|
686
|
+
t => t.type === IDENTIFIER_TOKEN && t.value && /^[abc]$/.test(t.value as string)
|
|
687
|
+
);
|
|
688
|
+
expect(attrNames).toHaveLength(3);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it("should handle attribute without value but with slash", () => {
|
|
692
|
+
const tokens = tokenizeTemplate`<div required/>`;
|
|
693
|
+
|
|
694
|
+
expect(tokens).toContainEqual(
|
|
695
|
+
expect.objectContaining({
|
|
696
|
+
type: IDENTIFIER_TOKEN,
|
|
697
|
+
value: "required"
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
expect(tokens).toContainEqual(
|
|
701
|
+
expect.objectContaining({
|
|
702
|
+
type: SLASH_TOKEN
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it("should handle whitespace variations", () => {
|
|
708
|
+
const tokens = tokenizeTemplate`<div id = "value" />`;
|
|
709
|
+
|
|
710
|
+
expect(tokens).toContainEqual(
|
|
711
|
+
expect.objectContaining({
|
|
712
|
+
type: IDENTIFIER_TOKEN,
|
|
713
|
+
value: "id"
|
|
714
|
+
})
|
|
715
|
+
);
|
|
716
|
+
expect(tokens).toContainEqual(
|
|
717
|
+
expect.objectContaining({
|
|
718
|
+
type: STRING_TOKEN,
|
|
719
|
+
value: "value"
|
|
720
|
+
})
|
|
721
|
+
);
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
describe("handling of raw text elements", () => {
|
|
726
|
+
it("should tokenize content inside <script> as text", () => {
|
|
727
|
+
const tokens = tokenizeTemplate`<script>const a = 5<10;</script>`;
|
|
728
|
+
|
|
729
|
+
expect(tokens).toEqual([
|
|
730
|
+
{ type: OPEN_TAG_TOKEN },
|
|
731
|
+
{ type: IDENTIFIER_TOKEN, value: "script" },
|
|
732
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
733
|
+
{ type: TEXT_TOKEN, value: "const a = 5<10;" },
|
|
734
|
+
{ type: OPEN_TAG_TOKEN },
|
|
735
|
+
{ type: SLASH_TOKEN },
|
|
736
|
+
{ type: IDENTIFIER_TOKEN, value: "script" },
|
|
737
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
738
|
+
]);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it("should tokenize content inside <style> as text", () => {
|
|
742
|
+
const tokens = tokenizeTemplate`<style>.class > span { color: red; }</style>`;
|
|
743
|
+
|
|
744
|
+
expect(tokens).toEqual([
|
|
745
|
+
{ type: OPEN_TAG_TOKEN },
|
|
746
|
+
{ type: IDENTIFIER_TOKEN, value: "style" },
|
|
747
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
748
|
+
{ type: TEXT_TOKEN, value: ".class > span { color: red; }" },
|
|
749
|
+
{ type: OPEN_TAG_TOKEN },
|
|
750
|
+
{ type: SLASH_TOKEN },
|
|
751
|
+
{ type: IDENTIFIER_TOKEN, value: "style" },
|
|
752
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
753
|
+
]);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it("should handle nested raw text elements ", () => {
|
|
757
|
+
const tokens = tokenizeTemplate`<div><style>.class > span { color: red; }</style></div>`;
|
|
758
|
+
|
|
759
|
+
expect(tokens).toEqual([
|
|
760
|
+
{ type: OPEN_TAG_TOKEN },
|
|
761
|
+
{ type: IDENTIFIER_TOKEN, value: "div" },
|
|
762
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
763
|
+
{ type: OPEN_TAG_TOKEN },
|
|
764
|
+
{ type: IDENTIFIER_TOKEN, value: "style" },
|
|
765
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
766
|
+
{ type: TEXT_TOKEN, value: ".class > span { color: red; }" },
|
|
767
|
+
{ type: OPEN_TAG_TOKEN },
|
|
768
|
+
{ type: SLASH_TOKEN },
|
|
769
|
+
{ type: IDENTIFIER_TOKEN, value: "style" },
|
|
770
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
771
|
+
{ type: OPEN_TAG_TOKEN },
|
|
772
|
+
{ type: SLASH_TOKEN },
|
|
773
|
+
{ type: IDENTIFIER_TOKEN, value: "div" },
|
|
774
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
775
|
+
]);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("should tokenize content inside <textarea> as text", () => {
|
|
779
|
+
const tokens = tokenizeTemplate`<textarea>This is <span>not parsed</span>.</textarea>`;
|
|
780
|
+
|
|
781
|
+
expect(tokens).toEqual([
|
|
782
|
+
{ type: OPEN_TAG_TOKEN },
|
|
783
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
784
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
785
|
+
{ type: TEXT_TOKEN, value: "This is <span>not parsed</span>." },
|
|
786
|
+
{ type: OPEN_TAG_TOKEN },
|
|
787
|
+
{ type: SLASH_TOKEN },
|
|
788
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
789
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
790
|
+
]);
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("should handle raw text elements with attributes and expressions", () => {
|
|
794
|
+
const tokens = tokenizeTemplate`<textarea type=${0}><span>${1}</span></textarea>`;
|
|
795
|
+
expect(tokens).toEqual([
|
|
796
|
+
{ type: OPEN_TAG_TOKEN },
|
|
797
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
798
|
+
{ type: IDENTIFIER_TOKEN, value: "type" },
|
|
799
|
+
{ type: EQUALS_TOKEN },
|
|
800
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
801
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
802
|
+
{ type: TEXT_TOKEN, value: "<span>" },
|
|
803
|
+
{ type: EXPRESSION_TOKEN, value: 1 },
|
|
804
|
+
{ type: TEXT_TOKEN, value: "</span>" },
|
|
805
|
+
{ type: OPEN_TAG_TOKEN },
|
|
806
|
+
{ type: SLASH_TOKEN },
|
|
807
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
808
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
809
|
+
]);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
it("should handle raw text elements and white space in tags", () => {
|
|
813
|
+
const tokens = tokenizeTemplate`< textarea > ${0}< / textarea >`;
|
|
814
|
+
expect(tokens).toEqual([
|
|
815
|
+
{ type: OPEN_TAG_TOKEN },
|
|
816
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
817
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
818
|
+
{ type: TEXT_TOKEN, value: ` ` },
|
|
819
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
820
|
+
{ type: OPEN_TAG_TOKEN },
|
|
821
|
+
{ type: SLASH_TOKEN },
|
|
822
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
823
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
824
|
+
]);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("should handle nested raw text elements", () => {
|
|
828
|
+
const tokens = tokenizeTemplate`<textarea><textarea>const a = 5;</textarea></textarea>`;
|
|
829
|
+
expect(tokens).toEqual([
|
|
830
|
+
{ type: OPEN_TAG_TOKEN },
|
|
831
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
832
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
833
|
+
{ type: TEXT_TOKEN, value: "<textarea>const a = 5;" },
|
|
834
|
+
{ type: OPEN_TAG_TOKEN },
|
|
835
|
+
{ type: SLASH_TOKEN },
|
|
836
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
837
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
838
|
+
{ type: OPEN_TAG_TOKEN },
|
|
839
|
+
{ type: SLASH_TOKEN },
|
|
840
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
841
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
842
|
+
]);
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it("should handle self-closing raw text elements", () => {
|
|
846
|
+
const tokens = tokenizeTemplate`<textarea ${0} />Text`;
|
|
847
|
+
expect(tokens).toEqual([
|
|
848
|
+
{ type: OPEN_TAG_TOKEN },
|
|
849
|
+
{ type: IDENTIFIER_TOKEN, value: "textarea" },
|
|
850
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
851
|
+
{ type: SLASH_TOKEN },
|
|
852
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
853
|
+
{ type: TEXT_TOKEN, value: "Text" }
|
|
854
|
+
]);
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
describe("dynamic component tags", () => {
|
|
859
|
+
it("should tokenize dynamic opening tag with expression", () => {
|
|
860
|
+
const Comp = "Comp";
|
|
861
|
+
const tokens = tokenizeTemplate`<${Comp}>`;
|
|
862
|
+
|
|
863
|
+
expect(tokens).toEqual([
|
|
864
|
+
{ type: OPEN_TAG_TOKEN },
|
|
865
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
866
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
867
|
+
]);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
it("should tokenize dynamic self-closing tag", () => {
|
|
871
|
+
const Comp = "Comp";
|
|
872
|
+
const tokens = tokenizeTemplate`<${Comp} />`;
|
|
873
|
+
|
|
874
|
+
expect(tokens).toEqual([
|
|
875
|
+
{ type: OPEN_TAG_TOKEN },
|
|
876
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
877
|
+
{ type: SLASH_TOKEN },
|
|
878
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
879
|
+
]);
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it("should tokenize dynamic closing tag", () => {
|
|
883
|
+
const Comp = "Comp";
|
|
884
|
+
const tokens = tokenizeTemplate`<${Comp}></${Comp}>`;
|
|
885
|
+
|
|
886
|
+
expect(tokens).toEqual([
|
|
887
|
+
{ type: OPEN_TAG_TOKEN },
|
|
888
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
889
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
890
|
+
{ type: OPEN_TAG_TOKEN },
|
|
891
|
+
{ type: SLASH_TOKEN },
|
|
892
|
+
{ type: EXPRESSION_TOKEN, value: 1 },
|
|
893
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
894
|
+
]);
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("should tokenize shorthand close tag", () => {
|
|
898
|
+
const tokens = tokenizeTemplate`<Comp>content<//>`;
|
|
899
|
+
|
|
900
|
+
expect(tokens).toEqual([
|
|
901
|
+
{ type: OPEN_TAG_TOKEN },
|
|
902
|
+
{ type: IDENTIFIER_TOKEN, value: "Comp" },
|
|
903
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
904
|
+
{ type: TEXT_TOKEN, value: "content" },
|
|
905
|
+
{ type: OPEN_TAG_TOKEN },
|
|
906
|
+
{ type: SLASH_TOKEN },
|
|
907
|
+
{ type: SLASH_TOKEN },
|
|
908
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
909
|
+
]);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it("should tokenize dynamic tag with attributes", () => {
|
|
913
|
+
const Comp = "Comp";
|
|
914
|
+
const tokens = tokenizeTemplate`<${Comp} id="test" />`;
|
|
915
|
+
|
|
916
|
+
expect(tokens).toEqual([
|
|
917
|
+
{ type: OPEN_TAG_TOKEN },
|
|
918
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
919
|
+
{ type: IDENTIFIER_TOKEN, value: "id" },
|
|
920
|
+
{ type: EQUALS_TOKEN },
|
|
921
|
+
{ type: STRING_TOKEN, value: "test", quote: '"' },
|
|
922
|
+
{ type: SLASH_TOKEN },
|
|
923
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
924
|
+
]);
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
describe("comments handling", () => {
|
|
929
|
+
it("should not tokenize comments", () => {
|
|
930
|
+
const tokens = tokenizeTemplate`<div><!-- This is a comment --></div>`;
|
|
931
|
+
expect(tokens).toEqual([
|
|
932
|
+
{ type: OPEN_TAG_TOKEN },
|
|
933
|
+
{ type: IDENTIFIER_TOKEN, value: "div" },
|
|
934
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
935
|
+
{ type: OPEN_TAG_TOKEN },
|
|
936
|
+
{ type: SLASH_TOKEN },
|
|
937
|
+
{ type: IDENTIFIER_TOKEN, value: "div" },
|
|
938
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
939
|
+
]);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it("should handle comments with special characters", () => {
|
|
943
|
+
const tokens = tokenizeTemplate`<!-- Special chars: <>&'" -->`;
|
|
944
|
+
expect(tokens).toEqual([]);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
it("should handle comments with expressions inside", () => {
|
|
948
|
+
const value = "test";
|
|
949
|
+
const tokens = tokenizeTemplate`<!-- Comment with ${value} inside -->`;
|
|
950
|
+
expect(tokens).toEqual([]);
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
it("should skip line comments in tags", () => {
|
|
954
|
+
const tokens = tokenizeTemplate`<button
|
|
955
|
+
disabled//comment
|
|
956
|
+
//handle with expression=${1}
|
|
957
|
+
class="btn"
|
|
958
|
+
/>`;
|
|
959
|
+
|
|
960
|
+
expect(tokens).toEqual([
|
|
961
|
+
{ type: OPEN_TAG_TOKEN },
|
|
962
|
+
{ type: IDENTIFIER_TOKEN, value: "button" },
|
|
963
|
+
{ type: IDENTIFIER_TOKEN, value: "disabled" },
|
|
964
|
+
{ type: IDENTIFIER_TOKEN, value: "class" },
|
|
965
|
+
{ type: EQUALS_TOKEN },
|
|
966
|
+
{ type: STRING_TOKEN, value: "btn", quote: '"' },
|
|
967
|
+
{ type: SLASH_TOKEN },
|
|
968
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
969
|
+
]);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
it("should skip line comments before the tag name", () => {
|
|
973
|
+
const tokens = tokenizeTemplate`<
|
|
974
|
+
//comment
|
|
975
|
+
button
|
|
976
|
+
/>`;
|
|
977
|
+
|
|
978
|
+
expect(tokens).toEqual([
|
|
979
|
+
{ type: OPEN_TAG_TOKEN },
|
|
980
|
+
{ type: IDENTIFIER_TOKEN, value: "button" },
|
|
981
|
+
{ type: SLASH_TOKEN },
|
|
982
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
983
|
+
]);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
it("should keep shorthand closing tags", () => {
|
|
987
|
+
const tokens = tokenizeTemplate`<${0}>Text<//>`;
|
|
988
|
+
|
|
989
|
+
expect(tokens).toEqual([
|
|
990
|
+
{ type: OPEN_TAG_TOKEN },
|
|
991
|
+
{ type: EXPRESSION_TOKEN, value: 0 },
|
|
992
|
+
{ type: CLOSE_TAG_TOKEN },
|
|
993
|
+
{ type: TEXT_TOKEN, value: "Text" },
|
|
994
|
+
{ type: OPEN_TAG_TOKEN },
|
|
995
|
+
{ type: SLASH_TOKEN },
|
|
996
|
+
{ type: SLASH_TOKEN },
|
|
997
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
998
|
+
]);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it("should skip block comments in tags", () => {
|
|
1002
|
+
const tokens = tokenizeTemplate`<button
|
|
1003
|
+
disabled /*comment
|
|
1004
|
+
//handle with expression=${1}
|
|
1005
|
+
*/class="btn"
|
|
1006
|
+
/>`;
|
|
1007
|
+
|
|
1008
|
+
expect(tokens).toEqual([
|
|
1009
|
+
{ type: OPEN_TAG_TOKEN },
|
|
1010
|
+
{ type: IDENTIFIER_TOKEN, value: "button" },
|
|
1011
|
+
{ type: IDENTIFIER_TOKEN, value: "disabled" },
|
|
1012
|
+
{ type: IDENTIFIER_TOKEN, value: "class" },
|
|
1013
|
+
{ type: EQUALS_TOKEN },
|
|
1014
|
+
{ type: STRING_TOKEN, value: "btn", quote: '"' },
|
|
1015
|
+
{ type: SLASH_TOKEN },
|
|
1016
|
+
{ type: CLOSE_TAG_TOKEN }
|
|
1017
|
+
]);
|
|
1018
|
+
});
|
|
1019
|
+
});
|