@mrhenry/prettier-twig 0.1.0
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/LICENSE +24 -0
- package/package.json +22 -0
- package/src/index.js +73 -0
- package/src/printer.js +1145 -0
- package/test/prettier-twig.test.js +1896 -0
|
@@ -0,0 +1,1896 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* End-to-end tests for the `@mrhenry/prettier-twig` plugin.
|
|
4
|
+
*
|
|
5
|
+
* Covers the formatting rules (tabs, multi-line tags, conditional attributes,
|
|
6
|
+
* twig blocks), Prettier's report-only / range / cursor modes (which rely on
|
|
7
|
+
* node offsets being exact positions in the original source) and directed
|
|
8
|
+
* tests for edge cases (raw-text elements, void/self-closing tags, attribute
|
|
9
|
+
* forms, control flow).
|
|
10
|
+
*
|
|
11
|
+
* @module test
|
|
12
|
+
*/
|
|
13
|
+
import { test } from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import { format, check, formatWithCursor } from 'prettier';
|
|
16
|
+
import * as pluginModule from '@mrhenry/prettier-twig';
|
|
17
|
+
|
|
18
|
+
const plugin = /** @type {any} */ (/** @type {any} */ (pluginModule).default ?? pluginModule);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} source
|
|
22
|
+
* @param {any} [options]
|
|
23
|
+
* @returns {Promise<string>}
|
|
24
|
+
*/
|
|
25
|
+
function fmt(source, options = {}) {
|
|
26
|
+
return format(source, { parser: 'twig-html', plugins: [plugin], ...options });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test('formats a simple element with tabs', async () => {
|
|
30
|
+
const out = await fmt(`<div>
|
|
31
|
+
<span>x</span>
|
|
32
|
+
</div>`);
|
|
33
|
+
assert.equal(
|
|
34
|
+
out,
|
|
35
|
+
`<div>
|
|
36
|
+
<span>x</span>
|
|
37
|
+
</div>
|
|
38
|
+
`,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('expands elements with multiple attributes', async () => {
|
|
43
|
+
{
|
|
44
|
+
const out = await fmt(`<div class="a" data-x="1"><br></div>`);
|
|
45
|
+
assert.equal(
|
|
46
|
+
out,
|
|
47
|
+
`<div
|
|
48
|
+
class="a"
|
|
49
|
+
data-x="1"
|
|
50
|
+
>
|
|
51
|
+
<br>
|
|
52
|
+
</div>
|
|
53
|
+
`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
// no line breaks around the text: its exact whitespace is significant
|
|
59
|
+
const out = await fmt(`<div class="a" data-x="1">text</div>`);
|
|
60
|
+
assert.equal(
|
|
61
|
+
out,
|
|
62
|
+
`<div
|
|
63
|
+
class="a"
|
|
64
|
+
data-x="1"
|
|
65
|
+
>text</div>
|
|
66
|
+
`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('keeps a single-attribute element on one line', async () => {
|
|
72
|
+
const out = await fmt(`<div class="a">text</div>`);
|
|
73
|
+
assert.equal(
|
|
74
|
+
out,
|
|
75
|
+
`<div class="a">text</div>
|
|
76
|
+
`,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('keeps html, link and meta tags on a single line', async () => {
|
|
81
|
+
{
|
|
82
|
+
const out = await fmt(`<html lang="en" dir="rtl">
|
|
83
|
+
<head></head>
|
|
84
|
+
</html>`);
|
|
85
|
+
assert.equal(
|
|
86
|
+
out,
|
|
87
|
+
`<html lang="en" dir="rtl">
|
|
88
|
+
<head></head>
|
|
89
|
+
</html>
|
|
90
|
+
`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
assert.equal(
|
|
96
|
+
await fmt(`<link rel="stylesheet" href="/style.css" media="screen">`),
|
|
97
|
+
`<link rel="stylesheet" href="/style.css" media="screen">\n`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
assert.equal(
|
|
103
|
+
await fmt(`<meta name="description" content="a b c">`),
|
|
104
|
+
`<meta name="description" content="a b c">\n`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
// even when the source breaks the start tag across lines
|
|
110
|
+
assert.equal(
|
|
111
|
+
await fmt(`<meta
|
|
112
|
+
name="a"
|
|
113
|
+
content="b"
|
|
114
|
+
>`),
|
|
115
|
+
`<meta name="a" content="b">\n`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
// a single space is kept before the closing slash of a self-closing tag
|
|
121
|
+
assert.equal(
|
|
122
|
+
await fmt(`<meta name="msapplication-TileColor" content="#ffffff" />`),
|
|
123
|
+
`<meta name="msapplication-TileColor" content="#ffffff" />\n`,
|
|
124
|
+
);
|
|
125
|
+
assert.equal(await fmt(`<link rel="a" href="/b"/>`), `<link rel="a" href="/b" />\n`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('does not close open-ended partials', async () => {
|
|
130
|
+
{
|
|
131
|
+
// a partial that opens the document, completed by another template
|
|
132
|
+
assert.equal(await fmt(`<html>\n <body>`), `<html>
|
|
133
|
+
<body>
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
{
|
|
138
|
+
assert.equal(
|
|
139
|
+
await fmt(`<div class="a" data-b="1">`),
|
|
140
|
+
`<div
|
|
141
|
+
class="a"
|
|
142
|
+
data-b="1"
|
|
143
|
+
>
|
|
144
|
+
`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
assert.equal(await fmt(`<div class="a">`), `<div class="a">\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
{
|
|
153
|
+
// closed elements still get their end tag
|
|
154
|
+
assert.equal(await fmt(`<div class="a">x</div>`), `<div class="a">x</div>\n`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('preserves the indentation of dangling end tags', async () => {
|
|
159
|
+
{
|
|
160
|
+
// a partial that closes the document, opened by another template
|
|
161
|
+
assert.equal(
|
|
162
|
+
await fmt(`{{ function('wp_footer') }}
|
|
163
|
+
</body>
|
|
164
|
+
</html>`),
|
|
165
|
+
` {{ function('wp_footer') }}
|
|
166
|
+
</body>
|
|
167
|
+
</html>
|
|
168
|
+
`,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// a subtle variation that does get formatted: extra spaces in the tag
|
|
172
|
+
assert.equal(
|
|
173
|
+
await fmt(`{{ function('wp_footer') }}
|
|
174
|
+
</body>
|
|
175
|
+
</html>`),
|
|
176
|
+
` {{ function('wp_footer') }}
|
|
177
|
+
</body>
|
|
178
|
+
</html>
|
|
179
|
+
`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
{
|
|
184
|
+
const source = ` {% include 'footer.twig' %}
|
|
185
|
+
</body>
|
|
186
|
+
</html>
|
|
187
|
+
`;
|
|
188
|
+
assert.equal(await fmt(source), source);
|
|
189
|
+
|
|
190
|
+
// a subtle variation that does get formatted: extra spaces in the tag
|
|
191
|
+
assert.equal(
|
|
192
|
+
await fmt(` {% include 'footer.twig' %}
|
|
193
|
+
</body>
|
|
194
|
+
</html>
|
|
195
|
+
`),
|
|
196
|
+
source,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('preserves significant whitespace around inline text', async () => {
|
|
202
|
+
{
|
|
203
|
+
// no line break: the exact whitespace is preserved
|
|
204
|
+
assert.equal(await fmt(`<span>foo</span>`), `<span>foo</span>\n`);
|
|
205
|
+
assert.equal(await fmt(`<span> foo</span>`), `<span> foo</span>\n`);
|
|
206
|
+
|
|
207
|
+
// a subtle variation that does get formatted: the start tag's space
|
|
208
|
+
assert.equal(await fmt(`<span >foo</span>`), `<span>foo</span>\n`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
{
|
|
212
|
+
// already spans lines: indentation may be normalized, close tag is kept
|
|
213
|
+
assert.equal(await fmt(`<span>\nfoo</span>`), `<span>\n\tfoo</span>\n`);
|
|
214
|
+
assert.equal(await fmt(`<span>\nfoo\n</span>`), `<span>\n\tfoo\n</span>\n`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('keeps inline text next to expressions in place', async () => {
|
|
219
|
+
{
|
|
220
|
+
// the `.` is not surrounded by newlines, so it stays on the same line
|
|
221
|
+
const source = `<h1 class="hero__title type-a">
|
|
222
|
+
{{ page.title }}.
|
|
223
|
+
</h1>
|
|
224
|
+
`;
|
|
225
|
+
assert.equal(await fmt(source), source);
|
|
226
|
+
|
|
227
|
+
// a subtle variation that does get formatted: spaces inside the tag
|
|
228
|
+
assert.equal(
|
|
229
|
+
await fmt(`<h1 class="hero__title type-a">
|
|
230
|
+
{{ page.title }}.
|
|
231
|
+
</h1>
|
|
232
|
+
`),
|
|
233
|
+
source,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
{
|
|
238
|
+
// the content is surrounded by newlines, so indentation may be adjusted
|
|
239
|
+
assert.equal(
|
|
240
|
+
await fmt(`<p>
|
|
241
|
+
{{ a }}, and {{ b }}!
|
|
242
|
+
</p>`),
|
|
243
|
+
`<p>
|
|
244
|
+
{{ a }}, and {{ b }}!
|
|
245
|
+
</p>
|
|
246
|
+
`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('keeps text around inline elements on one line', async () => {
|
|
252
|
+
const source = `<p class="l-error__text type-d">
|
|
253
|
+
Please try again later. Go back to the <a href="/">homepage</a>.
|
|
254
|
+
</p>
|
|
255
|
+
`;
|
|
256
|
+
assert.equal(await fmt(source), source);
|
|
257
|
+
|
|
258
|
+
// a subtle variation that does get formatted: a run of spaces in the text
|
|
259
|
+
assert.equal(
|
|
260
|
+
await fmt(`<p class="l-error__text type-d">
|
|
261
|
+
Please try again later. Go back to the <a href="/">homepage</a>.
|
|
262
|
+
</p>
|
|
263
|
+
`),
|
|
264
|
+
source,
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('places the closing > at the opening indent in multi-line tags', async () => {
|
|
269
|
+
{
|
|
270
|
+
const out = await fmt(`<div
|
|
271
|
+
class="a"
|
|
272
|
+
data-x
|
|
273
|
+
><br></div>`);
|
|
274
|
+
assert.equal(
|
|
275
|
+
out,
|
|
276
|
+
`<div
|
|
277
|
+
class="a"
|
|
278
|
+
data-x
|
|
279
|
+
>
|
|
280
|
+
<br>
|
|
281
|
+
</div>
|
|
282
|
+
`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
{
|
|
287
|
+
const out = await fmt(`<div
|
|
288
|
+
class="a"
|
|
289
|
+
data-x
|
|
290
|
+
>body</div>`);
|
|
291
|
+
assert.equal(
|
|
292
|
+
out,
|
|
293
|
+
`<div
|
|
294
|
+
class="a"
|
|
295
|
+
data-x
|
|
296
|
+
>body</div>
|
|
297
|
+
`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
{
|
|
302
|
+
const out = await fmt(`<div
|
|
303
|
+
class="a"
|
|
304
|
+
data-x><br></div>`);
|
|
305
|
+
assert.equal(
|
|
306
|
+
out,
|
|
307
|
+
`<div
|
|
308
|
+
class="a"
|
|
309
|
+
data-x
|
|
310
|
+
>
|
|
311
|
+
<br>
|
|
312
|
+
</div>
|
|
313
|
+
`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
const out = await fmt(`<div
|
|
319
|
+
class="a"
|
|
320
|
+
data-x>body</div>`);
|
|
321
|
+
assert.equal(
|
|
322
|
+
out,
|
|
323
|
+
`<div
|
|
324
|
+
class="a"
|
|
325
|
+
data-x
|
|
326
|
+
>body</div>
|
|
327
|
+
`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
{
|
|
332
|
+
// no content: the end tag closes on the same line as the opening `>`
|
|
333
|
+
const out = await fmt(`<div
|
|
334
|
+
class="a"
|
|
335
|
+
data-x></div>`);
|
|
336
|
+
assert.equal(
|
|
337
|
+
out,
|
|
338
|
+
`<div
|
|
339
|
+
class="a"
|
|
340
|
+
data-x
|
|
341
|
+
></div>
|
|
342
|
+
`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
const out = await fmt(`<div
|
|
348
|
+
class="store-locator__map"
|
|
349
|
+
data-store-locator-map
|
|
350
|
+
></div>`);
|
|
351
|
+
assert.equal(
|
|
352
|
+
out,
|
|
353
|
+
`<div
|
|
354
|
+
class="store-locator__map"
|
|
355
|
+
data-store-locator-map
|
|
356
|
+
></div>
|
|
357
|
+
`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
{
|
|
362
|
+
const out = await fmt(`<span class="foo" aria-hidden="true">· </span>`);
|
|
363
|
+
assert.equal(
|
|
364
|
+
out,
|
|
365
|
+
`<span
|
|
366
|
+
class="foo"
|
|
367
|
+
aria-hidden="true"
|
|
368
|
+
>· </span>
|
|
369
|
+
`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('formats conditional attributes', async () => {
|
|
375
|
+
{
|
|
376
|
+
const source = `<a
|
|
377
|
+
href="{{ url }}"
|
|
378
|
+
{% if current %}aria-current="page"{% endif %}
|
|
379
|
+
><br></a>`;
|
|
380
|
+
const out = await fmt(source);
|
|
381
|
+
assert.equal(
|
|
382
|
+
out,
|
|
383
|
+
`<a
|
|
384
|
+
href="{{ url }}"
|
|
385
|
+
{% if current %}
|
|
386
|
+
aria-current="page"
|
|
387
|
+
{% endif %}
|
|
388
|
+
>
|
|
389
|
+
<br>
|
|
390
|
+
</a>
|
|
391
|
+
`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
{
|
|
396
|
+
const source = `<a
|
|
397
|
+
href="{{ url }}"
|
|
398
|
+
{% if current %}aria-current="page"{% endif %}
|
|
399
|
+
>text</a>`;
|
|
400
|
+
const out = await fmt(source);
|
|
401
|
+
assert.equal(
|
|
402
|
+
out,
|
|
403
|
+
`<a
|
|
404
|
+
href="{{ url }}"
|
|
405
|
+
{% if current %}
|
|
406
|
+
aria-current="page"
|
|
407
|
+
{% endif %}
|
|
408
|
+
>text</a>
|
|
409
|
+
`,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
{
|
|
414
|
+
const source = `<a href="{{ url }}" {% if current %}aria-current="page"{% endif %}><br></a>`;
|
|
415
|
+
const out = await fmt(source);
|
|
416
|
+
assert.equal(
|
|
417
|
+
out,
|
|
418
|
+
`<a
|
|
419
|
+
href="{{ url }}"
|
|
420
|
+
{% if current %}
|
|
421
|
+
aria-current="page"
|
|
422
|
+
{% endif %}
|
|
423
|
+
>
|
|
424
|
+
<br>
|
|
425
|
+
</a>
|
|
426
|
+
`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
{
|
|
431
|
+
const source = `<a href="{{ url }}" {% if current %}aria-current="page"{% endif %}>text</a>`;
|
|
432
|
+
const out = await fmt(source);
|
|
433
|
+
assert.equal(
|
|
434
|
+
out,
|
|
435
|
+
`<a
|
|
436
|
+
href="{{ url }}"
|
|
437
|
+
{% if current %}
|
|
438
|
+
aria-current="page"
|
|
439
|
+
{% endif %}
|
|
440
|
+
>text</a>
|
|
441
|
+
`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
{
|
|
446
|
+
const source = `<a
|
|
447
|
+
href="{{ url }}"
|
|
448
|
+
{% if current %}
|
|
449
|
+
aria-current="page"
|
|
450
|
+
{% endif %}
|
|
451
|
+
><br></a>`;
|
|
452
|
+
const out = await fmt(source);
|
|
453
|
+
assert.equal(
|
|
454
|
+
out,
|
|
455
|
+
`<a
|
|
456
|
+
href="{{ url }}"
|
|
457
|
+
{% if current %}
|
|
458
|
+
aria-current="page"
|
|
459
|
+
{% endif %}
|
|
460
|
+
>
|
|
461
|
+
<br>
|
|
462
|
+
</a>
|
|
463
|
+
`,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
{
|
|
468
|
+
const source = `<a
|
|
469
|
+
href="{{ url }}"
|
|
470
|
+
{% if current %}
|
|
471
|
+
aria-current="page"
|
|
472
|
+
{% endif %}
|
|
473
|
+
>text</a>`;
|
|
474
|
+
const out = await fmt(source);
|
|
475
|
+
assert.equal(
|
|
476
|
+
out,
|
|
477
|
+
`<a
|
|
478
|
+
href="{{ url }}"
|
|
479
|
+
{% if current %}
|
|
480
|
+
aria-current="page"
|
|
481
|
+
{% endif %}
|
|
482
|
+
>text</a>
|
|
483
|
+
`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test('indents twig block bodies', async () => {
|
|
489
|
+
{
|
|
490
|
+
const out = await fmt(`{% if x %}
|
|
491
|
+
<div>a</div>
|
|
492
|
+
{% endif %}`);
|
|
493
|
+
assert.equal(
|
|
494
|
+
out,
|
|
495
|
+
`{% if x %}
|
|
496
|
+
<div>a</div>
|
|
497
|
+
{% endif %}
|
|
498
|
+
`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
const out = await fmt(`{% if x %}<div>a</div>{% endif %}`);
|
|
504
|
+
assert.equal(
|
|
505
|
+
out,
|
|
506
|
+
`{% if x %}
|
|
507
|
+
<div>a</div>
|
|
508
|
+
{% endif %}
|
|
509
|
+
`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
{
|
|
514
|
+
const out = await fmt(`{% if x %}
|
|
515
|
+
<div>a</div>
|
|
516
|
+
{% endif %}`);
|
|
517
|
+
assert.equal(
|
|
518
|
+
out,
|
|
519
|
+
`{% if x %}
|
|
520
|
+
<div>a</div>
|
|
521
|
+
{% endif %}
|
|
522
|
+
`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
{
|
|
527
|
+
const out = await fmt(`{% if
|
|
528
|
+
x
|
|
529
|
+
%}
|
|
530
|
+
<div>a</div>
|
|
531
|
+
{%
|
|
532
|
+
endif %}`);
|
|
533
|
+
assert.equal(
|
|
534
|
+
out,
|
|
535
|
+
`{% if x %}
|
|
536
|
+
<div>a</div>
|
|
537
|
+
{% endif %}
|
|
538
|
+
`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
{
|
|
543
|
+
const out = await fmt(`{% if x %}
|
|
544
|
+
<div>a</div>
|
|
545
|
+
{% endif
|
|
546
|
+
%}`);
|
|
547
|
+
assert.equal(
|
|
548
|
+
out,
|
|
549
|
+
`{% if x %}
|
|
550
|
+
<div>a</div>
|
|
551
|
+
{% endif %}
|
|
552
|
+
`,
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('formats an include with a multiline array', async () => {
|
|
558
|
+
{
|
|
559
|
+
const out = await fmt(`{% include 'x.twig' with { a: 1, b: 2 } %}`);
|
|
560
|
+
assert.equal(
|
|
561
|
+
out,
|
|
562
|
+
`{% include 'x.twig' with {
|
|
563
|
+
a: 1,
|
|
564
|
+
b: 2,
|
|
565
|
+
} %}
|
|
566
|
+
`,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
{
|
|
571
|
+
const out = await fmt(`{% include 'x.twig' with {
|
|
572
|
+
a: 1,
|
|
573
|
+
b: 2,
|
|
574
|
+
} %}
|
|
575
|
+
`);
|
|
576
|
+
assert.equal(
|
|
577
|
+
out,
|
|
578
|
+
`{% include 'x.twig' with {
|
|
579
|
+
a: 1,
|
|
580
|
+
b: 2,
|
|
581
|
+
} %}
|
|
582
|
+
`,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
{
|
|
587
|
+
const out = await fmt(`{% include 'x.twig'
|
|
588
|
+
with {
|
|
589
|
+
a: 1,
|
|
590
|
+
b: 2,
|
|
591
|
+
}
|
|
592
|
+
%}
|
|
593
|
+
`);
|
|
594
|
+
assert.equal(
|
|
595
|
+
out,
|
|
596
|
+
`{% include 'x.twig' with {
|
|
597
|
+
a: 1,
|
|
598
|
+
b: 2,
|
|
599
|
+
} %}
|
|
600
|
+
`,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test('formats a set with a multiline array', async () => {
|
|
606
|
+
{
|
|
607
|
+
const out = await fmt(`{% set image = ensure_timber_image(teaser.thumbnail) %}
|
|
608
|
+
{% if image %}
|
|
609
|
+
{% set url = image.imgix_src|add_query_arg({
|
|
610
|
+
ar: '4:5',
|
|
611
|
+
fit: 'crop',
|
|
612
|
+
crop: 'faces,edges',
|
|
613
|
+
auto: 'format',
|
|
614
|
+
q: 60,
|
|
615
|
+
}, false) %}
|
|
616
|
+
{% endif %}`);
|
|
617
|
+
assert.equal(
|
|
618
|
+
out,
|
|
619
|
+
`{% set image = ensure_timber_image(teaser.thumbnail) %}
|
|
620
|
+
{% if image %}
|
|
621
|
+
{% set url = image.imgix_src|add_query_arg({
|
|
622
|
+
ar: '4:5',
|
|
623
|
+
fit: 'crop',
|
|
624
|
+
crop: 'faces,edges',
|
|
625
|
+
auto: 'format',
|
|
626
|
+
q: 60,
|
|
627
|
+
}, false) %}
|
|
628
|
+
{% endif %}
|
|
629
|
+
`,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
{
|
|
634
|
+
const out = await fmt(`{% set url = image.imgix_src|add_query_arg({ ar: '4:5', fit: 'crop', crop: 'faces,edges', auto: 'format', q: 60 }, false) %}`);
|
|
635
|
+
assert.equal(
|
|
636
|
+
out,
|
|
637
|
+
`{% set url = image.imgix_src|add_query_arg({
|
|
638
|
+
ar: '4:5',
|
|
639
|
+
fit: 'crop',
|
|
640
|
+
crop: 'faces,edges',
|
|
641
|
+
auto: 'format',
|
|
642
|
+
q: 60,
|
|
643
|
+
}, false) %}
|
|
644
|
+
`,
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
{
|
|
649
|
+
const out = await fmt(`{% set url =
|
|
650
|
+
image.imgix_src|add_query_arg(
|
|
651
|
+
{
|
|
652
|
+
ar: '4:5',
|
|
653
|
+
fit: 'crop',
|
|
654
|
+
crop: 'faces,edges',
|
|
655
|
+
auto: 'format',
|
|
656
|
+
q: 60,
|
|
657
|
+
},
|
|
658
|
+
false
|
|
659
|
+
)
|
|
660
|
+
%}
|
|
661
|
+
`);
|
|
662
|
+
assert.equal(
|
|
663
|
+
out,
|
|
664
|
+
`{% set url = image.imgix_src|add_query_arg({
|
|
665
|
+
ar: '4:5',
|
|
666
|
+
fit: 'crop',
|
|
667
|
+
crop: 'faces,edges',
|
|
668
|
+
auto: 'format',
|
|
669
|
+
q: 60,
|
|
670
|
+
}, false) %}
|
|
671
|
+
`,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test('keeps source multi-line objects expanded regardless of printWidth', async () => {
|
|
677
|
+
const source = `{% set url = image.imgix_src|add_query_arg({
|
|
678
|
+
ar: '1:1',
|
|
679
|
+
fit: 'fill',
|
|
680
|
+
fill: solid,
|
|
681
|
+
'fill-color': 'fff',
|
|
682
|
+
auto: 'format',
|
|
683
|
+
q: 70,
|
|
684
|
+
}, false) %}
|
|
685
|
+
`;
|
|
686
|
+
assert.equal(await fmt(source, { printWidth: 200 }), source);
|
|
687
|
+
|
|
688
|
+
// a subtle variation that does get formatted: extra spaces around colons
|
|
689
|
+
assert.equal(
|
|
690
|
+
await fmt(
|
|
691
|
+
`{% set url = image.imgix_src|add_query_arg({
|
|
692
|
+
ar: '1:1',
|
|
693
|
+
fit: 'fill',
|
|
694
|
+
fill: solid,
|
|
695
|
+
'fill-color': 'fff',
|
|
696
|
+
auto: 'format',
|
|
697
|
+
q: 70,
|
|
698
|
+
}, false) %}
|
|
699
|
+
`,
|
|
700
|
+
{ printWidth: 200 },
|
|
701
|
+
),
|
|
702
|
+
source,
|
|
703
|
+
);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test('formats a set with a multiline sequence', async () => {
|
|
707
|
+
const source = `{% set footer_menus = [
|
|
708
|
+
{ menu_items: menus.footer_a_navigation_items },
|
|
709
|
+
{ menu_items: menus.footer_b_navigation_items },
|
|
710
|
+
{ menu_items: menus.footer_c_navigation_items },
|
|
711
|
+
] %}
|
|
712
|
+
`;
|
|
713
|
+
assert.equal(await fmt(source, { printWidth: 200 }), source);
|
|
714
|
+
|
|
715
|
+
// a subtle variation that does get formatted: extra spaces after colons
|
|
716
|
+
assert.equal(
|
|
717
|
+
await fmt(
|
|
718
|
+
`{% set footer_menus = [
|
|
719
|
+
{ menu_items: menus.footer_a_navigation_items },
|
|
720
|
+
{ menu_items: menus.footer_b_navigation_items },
|
|
721
|
+
{ menu_items: menus.footer_c_navigation_items },
|
|
722
|
+
] %}
|
|
723
|
+
`,
|
|
724
|
+
{ printWidth: 200 },
|
|
725
|
+
),
|
|
726
|
+
source,
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test('preserves only a single blank line between siblings', async () => {
|
|
731
|
+
{
|
|
732
|
+
const source = `<div>
|
|
733
|
+
<p>a</p>
|
|
734
|
+
|
|
735
|
+
<p>b</p>
|
|
736
|
+
</div>`;
|
|
737
|
+
const out = await fmt(source);
|
|
738
|
+
assert.equal(
|
|
739
|
+
out,
|
|
740
|
+
`<div>
|
|
741
|
+
<p>a</p>
|
|
742
|
+
|
|
743
|
+
<p>b</p>
|
|
744
|
+
</div>
|
|
745
|
+
`,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
{
|
|
750
|
+
const source = `<div>
|
|
751
|
+
<p>a</p>
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
<p>b</p>
|
|
755
|
+
</div>`;
|
|
756
|
+
const out = await fmt(source);
|
|
757
|
+
assert.equal(
|
|
758
|
+
out,
|
|
759
|
+
`<div>
|
|
760
|
+
<p>a</p>
|
|
761
|
+
|
|
762
|
+
<p>b</p>
|
|
763
|
+
</div>
|
|
764
|
+
`,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test('drops blank lines at the start of the document', async () => {
|
|
770
|
+
assert.equal(await fmt(`\n\n\n<div>x</div>\n\n\n`), `<div>x</div>\n`);
|
|
771
|
+
assert.equal(
|
|
772
|
+
await fmt(`\n\n{% if a %}\n\t<p>x</p>\n{% endif %}`),
|
|
773
|
+
`{% if a %}
|
|
774
|
+
<p>x</p>
|
|
775
|
+
{% endif %}
|
|
776
|
+
`,
|
|
777
|
+
);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test('preserves blank lines between root-level siblings', async () => {
|
|
781
|
+
const source = `<p>a</p>
|
|
782
|
+
|
|
783
|
+
<p>b</p>
|
|
784
|
+
|
|
785
|
+
<p>c</p>
|
|
786
|
+
`;
|
|
787
|
+
assert.equal(await fmt(source), source);
|
|
788
|
+
|
|
789
|
+
// a subtle variation that does get formatted: extra blank lines collapse
|
|
790
|
+
assert.equal(
|
|
791
|
+
await fmt(`<p>a</p>
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
<p>b</p>
|
|
795
|
+
|
|
796
|
+
<p>c</p>
|
|
797
|
+
`),
|
|
798
|
+
source,
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
test('preserves blank lines around twig section heads and bodies', async () => {
|
|
803
|
+
const source = `{% if a %}
|
|
804
|
+
|
|
805
|
+
<p>x</p>
|
|
806
|
+
|
|
807
|
+
{% else %}
|
|
808
|
+
|
|
809
|
+
<p>y</p>
|
|
810
|
+
{% endif %}
|
|
811
|
+
`;
|
|
812
|
+
assert.equal(await fmt(source), source);
|
|
813
|
+
|
|
814
|
+
// a subtle variation that does get formatted: extra blank lines collapse
|
|
815
|
+
assert.equal(
|
|
816
|
+
await fmt(`{% if a %}
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
<p>x</p>
|
|
820
|
+
|
|
821
|
+
{% else %}
|
|
822
|
+
|
|
823
|
+
<p>y</p>
|
|
824
|
+
{% endif %}
|
|
825
|
+
`),
|
|
826
|
+
source,
|
|
827
|
+
);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
test('keeps blank lines between statements bordered by whitespace trim markers', async () => {
|
|
831
|
+
const source = `{% if foo %}
|
|
832
|
+
{%- endif -%}
|
|
833
|
+
|
|
834
|
+
{%- if bar -%}
|
|
835
|
+
{% endif %}
|
|
836
|
+
`;
|
|
837
|
+
assert.equal(await fmt(source), source);
|
|
838
|
+
|
|
839
|
+
// a subtle variation that does get formatted: extra spaces in the tag
|
|
840
|
+
assert.equal(
|
|
841
|
+
await fmt(`{% if foo %}
|
|
842
|
+
{%- endif -%}
|
|
843
|
+
|
|
844
|
+
{%- if bar -%}
|
|
845
|
+
{% endif %}
|
|
846
|
+
`),
|
|
847
|
+
source,
|
|
848
|
+
);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test('keeps twig comments', async () => {
|
|
852
|
+
const out = await fmt(`{# note #}
|
|
853
|
+
<div>x</div>`);
|
|
854
|
+
assert.equal(
|
|
855
|
+
out,
|
|
856
|
+
`{# note #}
|
|
857
|
+
<div>x</div>
|
|
858
|
+
`,
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
// a subtle variation that does get formatted: extra spaces in the comment
|
|
862
|
+
assert.equal(
|
|
863
|
+
await fmt(`{# note #}
|
|
864
|
+
<div>x</div>`),
|
|
865
|
+
out,
|
|
866
|
+
);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test('format twig comments', async () => {
|
|
870
|
+
{
|
|
871
|
+
const out = await fmt(`{# note #}
|
|
872
|
+
<div>x</div>`);
|
|
873
|
+
assert.equal(
|
|
874
|
+
out,
|
|
875
|
+
`{# note #}
|
|
876
|
+
<div>x</div>
|
|
877
|
+
`,
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
{
|
|
882
|
+
const out = await fmt(`{# note #}
|
|
883
|
+
<div>x</div>`);
|
|
884
|
+
assert.equal(
|
|
885
|
+
out,
|
|
886
|
+
`{# note #}
|
|
887
|
+
<div>x</div>
|
|
888
|
+
`,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
{
|
|
893
|
+
const out = await fmt(`{#
|
|
894
|
+
long note
|
|
895
|
+
seconde line
|
|
896
|
+
#}
|
|
897
|
+
<div>x</div>`);
|
|
898
|
+
assert.equal(
|
|
899
|
+
out,
|
|
900
|
+
`{#
|
|
901
|
+
long note
|
|
902
|
+
seconde line
|
|
903
|
+
#}
|
|
904
|
+
<div>x</div>
|
|
905
|
+
`,
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
// a subtle variation that does get formatted: a run of spaces inside
|
|
909
|
+
const normalized = await fmt(`{#
|
|
910
|
+
long note
|
|
911
|
+
seconde line
|
|
912
|
+
#}
|
|
913
|
+
<div>x</div>`);
|
|
914
|
+
assert.equal(
|
|
915
|
+
normalized,
|
|
916
|
+
`{#
|
|
917
|
+
long note
|
|
918
|
+
seconde line
|
|
919
|
+
#}
|
|
920
|
+
<div>x</div>
|
|
921
|
+
`,
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
{
|
|
926
|
+
const out = await fmt(`{# long note
|
|
927
|
+
seconde line
|
|
928
|
+
#}
|
|
929
|
+
<div>x</div>`);
|
|
930
|
+
assert.equal(
|
|
931
|
+
out,
|
|
932
|
+
`{#
|
|
933
|
+
long note
|
|
934
|
+
seconde line
|
|
935
|
+
#}
|
|
936
|
+
<div>x</div>
|
|
937
|
+
`,
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
{
|
|
942
|
+
const out = await fmt(`{#
|
|
943
|
+
long note
|
|
944
|
+
seconde line #}
|
|
945
|
+
<div>x</div>`);
|
|
946
|
+
assert.equal(
|
|
947
|
+
out,
|
|
948
|
+
`{#
|
|
949
|
+
long note
|
|
950
|
+
seconde line
|
|
951
|
+
#}
|
|
952
|
+
<div>x</div>
|
|
953
|
+
`,
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
test('keeps html comments and doctype', async () => {
|
|
959
|
+
const out = await fmt(`<!doctype html>
|
|
960
|
+
<!-- c -->
|
|
961
|
+
<p>x</p>`);
|
|
962
|
+
assert.equal(
|
|
963
|
+
out,
|
|
964
|
+
`<!doctype html>
|
|
965
|
+
<!-- c -->
|
|
966
|
+
<p>x</p>
|
|
967
|
+
`,
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
// a subtle variation that does get formatted: a space in the element tag
|
|
971
|
+
assert.equal(
|
|
972
|
+
await fmt(`<!doctype html>
|
|
973
|
+
<!-- c -->
|
|
974
|
+
<p >x</p>`),
|
|
975
|
+
out,
|
|
976
|
+
);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test('report-only mode: check returns false when formatting differs', async () => {
|
|
980
|
+
const unformatted = '<div class="a" data-b="1">x</div>';
|
|
981
|
+
const ok = await check(unformatted, { parser: 'twig-html', plugins: [plugin] });
|
|
982
|
+
assert.equal(ok, false);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test('report-only mode: check returns true when already formatted', async () => {
|
|
986
|
+
const formatted = await fmt('<div class="a" data-b="1">x</div>');
|
|
987
|
+
const ok = await check(formatted, { parser: 'twig-html', plugins: [plugin] });
|
|
988
|
+
assert.equal(ok, true);
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
test('range formatting does not corrupt output', async () => {
|
|
992
|
+
// Prettier's range machinery (`calculateRange` -> `isSourceElement`) only
|
|
993
|
+
// recognises built-in parser names, so a custom parser gets a no-op range.
|
|
994
|
+
// The guarantee we can make, and test, is that requesting a range returns
|
|
995
|
+
// the original source untouched (no offset drift or corruption).
|
|
996
|
+
const source = `<div class="a">keep</div>
|
|
997
|
+
<p class="x" data-y="1">z</p>`;
|
|
998
|
+
const rangeStart = source.indexOf('<p');
|
|
999
|
+
const rangeEnd = source.length;
|
|
1000
|
+
const out = await fmt(source, { rangeStart, rangeEnd });
|
|
1001
|
+
assert.equal(out, source);
|
|
1002
|
+
|
|
1003
|
+
// a subtle variation that does get formatted: without a range the same
|
|
1004
|
+
// source has its multi-attribute tag expanded
|
|
1005
|
+
assert.equal(
|
|
1006
|
+
await fmt(source),
|
|
1007
|
+
`<div class="a">keep</div>
|
|
1008
|
+
<p
|
|
1009
|
+
class="x"
|
|
1010
|
+
data-y="1"
|
|
1011
|
+
>z</p>
|
|
1012
|
+
`,
|
|
1013
|
+
);
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
test('cursor offset is preserved', async () => {
|
|
1017
|
+
const source = `<div>
|
|
1018
|
+
<p>a</p>
|
|
1019
|
+
<span>b</span>
|
|
1020
|
+
</div>`;
|
|
1021
|
+
const cursorOffset = source.indexOf('<span');
|
|
1022
|
+
const res = await formatWithCursor(source, {
|
|
1023
|
+
parser: 'twig-html',
|
|
1024
|
+
plugins: [plugin],
|
|
1025
|
+
cursorOffset,
|
|
1026
|
+
});
|
|
1027
|
+
assert.equal(res.formatted, `${source}\n`);
|
|
1028
|
+
// the cursor still sits on the opening `<` of the `<span>` element
|
|
1029
|
+
assert.equal(res.formatted[res.cursorOffset], '<');
|
|
1030
|
+
assert.equal(res.formatted.slice(res.cursorOffset, res.cursorOffset + 5), '<span');
|
|
1031
|
+
|
|
1032
|
+
// a subtle variation that does get formatted: the mis-indented paragraph is
|
|
1033
|
+
// re-indented while the cursor still tracks the `<span>` element
|
|
1034
|
+
const unformatted = `<div>
|
|
1035
|
+
<p>a</p>
|
|
1036
|
+
<span>b</span>
|
|
1037
|
+
</div>`;
|
|
1038
|
+
const res2 = await formatWithCursor(unformatted, {
|
|
1039
|
+
parser: 'twig-html',
|
|
1040
|
+
plugins: [plugin],
|
|
1041
|
+
cursorOffset: unformatted.indexOf('<span'),
|
|
1042
|
+
});
|
|
1043
|
+
assert.equal(
|
|
1044
|
+
res2.formatted,
|
|
1045
|
+
`<div>
|
|
1046
|
+
<p>a</p>
|
|
1047
|
+
<span>b</span>
|
|
1048
|
+
</div>
|
|
1049
|
+
`,
|
|
1050
|
+
);
|
|
1051
|
+
assert.equal(res2.formatted.slice(res2.cursorOffset, res2.cursorOffset + 5), '<span');
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test('formats idempotently', async () => {
|
|
1055
|
+
const samples = [
|
|
1056
|
+
`<div class="a">x</div>`,
|
|
1057
|
+
`<div
|
|
1058
|
+
class="a"
|
|
1059
|
+
data-x="1"
|
|
1060
|
+
>x</div>`,
|
|
1061
|
+
`{% if x %}
|
|
1062
|
+
<p>a</p>
|
|
1063
|
+
{% else %}
|
|
1064
|
+
<p>b</p>
|
|
1065
|
+
{% endif %}`,
|
|
1066
|
+
`<ul>
|
|
1067
|
+
{% for i in items %}
|
|
1068
|
+
<li>{{ i }}</li>
|
|
1069
|
+
{% endfor %}
|
|
1070
|
+
</ul>`,
|
|
1071
|
+
];
|
|
1072
|
+
for (const s of samples) {
|
|
1073
|
+
const once = await fmt(s);
|
|
1074
|
+
const twice = await fmt(once);
|
|
1075
|
+
assert.equal(once, twice, `not idempotent for ${JSON.stringify(s)}`);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
test('preserves non-embedded raw-text element content verbatim', async () => {
|
|
1080
|
+
const source = `<textarea>
|
|
1081
|
+
keep spacing
|
|
1082
|
+
indented
|
|
1083
|
+
</textarea>
|
|
1084
|
+
`;
|
|
1085
|
+
assert.equal(await fmt(source), source);
|
|
1086
|
+
|
|
1087
|
+
// a subtle variation that does get formatted: a space in the start tag
|
|
1088
|
+
// (the raw content is still untouched)
|
|
1089
|
+
assert.equal(
|
|
1090
|
+
await fmt(`<textarea >
|
|
1091
|
+
keep spacing
|
|
1092
|
+
indented
|
|
1093
|
+
</textarea>
|
|
1094
|
+
`),
|
|
1095
|
+
source,
|
|
1096
|
+
);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
test('formats embedded JavaScript and keeps twig constructs', async () => {
|
|
1100
|
+
const source = `<script>
|
|
1101
|
+
var config = {{ config_json|json_encode|raw }};
|
|
1102
|
+
if (config) { console.log('{{ message }}'); }
|
|
1103
|
+
</script>
|
|
1104
|
+
`;
|
|
1105
|
+
assert.equal(
|
|
1106
|
+
await fmt(source),
|
|
1107
|
+
`<script>
|
|
1108
|
+
var config = {{ config_json|json_encode|raw }};
|
|
1109
|
+
if (config) {
|
|
1110
|
+
console.log("{{ message }}");
|
|
1111
|
+
}
|
|
1112
|
+
</script>
|
|
1113
|
+
`,
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
test('formats embedded CSS and keeps twig constructs', async () => {
|
|
1118
|
+
const source = `<style>
|
|
1119
|
+
.card { color: {{ color }}; background: url({{ background_url }}); }
|
|
1120
|
+
</style>
|
|
1121
|
+
`;
|
|
1122
|
+
assert.equal(
|
|
1123
|
+
await fmt(source),
|
|
1124
|
+
`<style>
|
|
1125
|
+
.card {
|
|
1126
|
+
color: {{ color }};
|
|
1127
|
+
background: url({{ background_url }});
|
|
1128
|
+
}
|
|
1129
|
+
</style>
|
|
1130
|
+
`,
|
|
1131
|
+
);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
test('formats scripts with inline twig control flow', async () => {
|
|
1135
|
+
const source = `<script>
|
|
1136
|
+
(function () {
|
|
1137
|
+
window.Mr = {
|
|
1138
|
+
gtagConfigured: {% if options.google_metrics.gtag_id %}true{% else %}false{% endif %}
|
|
1139
|
+
};
|
|
1140
|
+
})();
|
|
1141
|
+
</script>
|
|
1142
|
+
`;
|
|
1143
|
+
assert.equal(
|
|
1144
|
+
await fmt(source, { singleQuote: true }),
|
|
1145
|
+
`<script>
|
|
1146
|
+
(function () {
|
|
1147
|
+
window.Mr = {
|
|
1148
|
+
gtagConfigured: {% if options.google_metrics.gtag_id %}true{% else %}false{% endif %}
|
|
1149
|
+
};
|
|
1150
|
+
})();
|
|
1151
|
+
</script>
|
|
1152
|
+
`,
|
|
1153
|
+
);
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
test('keeps multi-line twig blocks indented inside embedded JavaScript', async () => {
|
|
1157
|
+
const source = `<script>
|
|
1158
|
+
gtag("config", {{ options.google_metrics.gtag_id|json_encode }}, {
|
|
1159
|
+
{% if options.google_metrics.debug_mode %}
|
|
1160
|
+
'debug_mode': true
|
|
1161
|
+
{% endif %}
|
|
1162
|
+
});
|
|
1163
|
+
</script>
|
|
1164
|
+
`;
|
|
1165
|
+
assert.equal(await fmt(source), source);
|
|
1166
|
+
|
|
1167
|
+
// a subtle variation that does get formatted: a space in the JavaScript,
|
|
1168
|
+
// while the multi-line twig block keeps its indentation
|
|
1169
|
+
assert.equal(
|
|
1170
|
+
await fmt(`<script>
|
|
1171
|
+
gtag( "config", {{ options.google_metrics.gtag_id|json_encode }}, {
|
|
1172
|
+
{% if options.google_metrics.debug_mode %}
|
|
1173
|
+
'debug_mode': true
|
|
1174
|
+
{% endif %}
|
|
1175
|
+
});
|
|
1176
|
+
</script>
|
|
1177
|
+
`),
|
|
1178
|
+
source,
|
|
1179
|
+
);
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
test('drops trailing commas in embedded JavaScript only', async () => {
|
|
1183
|
+
const source = `<script>
|
|
1184
|
+
var config = {
|
|
1185
|
+
a: 1,
|
|
1186
|
+
b: 2,
|
|
1187
|
+
};
|
|
1188
|
+
gtag("config", "x", {
|
|
1189
|
+
c: 3,
|
|
1190
|
+
});
|
|
1191
|
+
</script>
|
|
1192
|
+
<style>
|
|
1193
|
+
.a { color: red; }
|
|
1194
|
+
</style>
|
|
1195
|
+
{% include 'x.twig' with { a: 1, b: 2 } %}`;
|
|
1196
|
+
assert.equal(
|
|
1197
|
+
await fmt(source),
|
|
1198
|
+
`<script>
|
|
1199
|
+
var config = {
|
|
1200
|
+
a: 1,
|
|
1201
|
+
b: 2
|
|
1202
|
+
};
|
|
1203
|
+
gtag("config", "x", {
|
|
1204
|
+
c: 3
|
|
1205
|
+
});
|
|
1206
|
+
</script>
|
|
1207
|
+
<style>
|
|
1208
|
+
.a {
|
|
1209
|
+
color: red;
|
|
1210
|
+
}
|
|
1211
|
+
</style>
|
|
1212
|
+
{% include 'x.twig' with {
|
|
1213
|
+
a: 1,
|
|
1214
|
+
b: 2,
|
|
1215
|
+
} %}
|
|
1216
|
+
`,
|
|
1217
|
+
);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
test('honours the trailingComma option for twig constructs only', async () => {
|
|
1221
|
+
const source = `{% include 'partials/teaser-list-c.twig' with {
|
|
1222
|
+
teasers: format_teasers_data(posts)
|
|
1223
|
+
} %}
|
|
1224
|
+
<script>
|
|
1225
|
+
var config = {
|
|
1226
|
+
a: 1,
|
|
1227
|
+
};
|
|
1228
|
+
</script>
|
|
1229
|
+
<style>
|
|
1230
|
+
.a { color: red; }
|
|
1231
|
+
</style>`;
|
|
1232
|
+
assert.equal(
|
|
1233
|
+
await fmt(source),
|
|
1234
|
+
`{% include 'partials/teaser-list-c.twig' with {
|
|
1235
|
+
teasers: format_teasers_data(posts),
|
|
1236
|
+
} %}
|
|
1237
|
+
<script>
|
|
1238
|
+
var config = {
|
|
1239
|
+
a: 1
|
|
1240
|
+
};
|
|
1241
|
+
</script>
|
|
1242
|
+
<style>
|
|
1243
|
+
.a {
|
|
1244
|
+
color: red;
|
|
1245
|
+
}
|
|
1246
|
+
</style>
|
|
1247
|
+
`,
|
|
1248
|
+
);
|
|
1249
|
+
assert.equal(
|
|
1250
|
+
await fmt(source, { trailingComma: 'none' }),
|
|
1251
|
+
`{% include 'partials/teaser-list-c.twig' with {
|
|
1252
|
+
teasers: format_teasers_data(posts)
|
|
1253
|
+
} %}
|
|
1254
|
+
<script>
|
|
1255
|
+
var config = {
|
|
1256
|
+
a: 1
|
|
1257
|
+
};
|
|
1258
|
+
</script>
|
|
1259
|
+
<style>
|
|
1260
|
+
.a {
|
|
1261
|
+
color: red;
|
|
1262
|
+
}
|
|
1263
|
+
</style>
|
|
1264
|
+
`,
|
|
1265
|
+
);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
test('does not touch empty or external scripts and styles', async () => {
|
|
1269
|
+
assert.equal(await fmt('<script src="/x.js"></script>'), '<script src="/x.js"></script>\n');
|
|
1270
|
+
assert.equal(await fmt('<script></script>'), '<script></script>\n');
|
|
1271
|
+
assert.equal(await fmt('<style></style>'), '<style></style>\n');
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
test('preserves significant whitespace inside <pre>', async () => {
|
|
1275
|
+
const source = `<pre>
|
|
1276
|
+
two spaces
|
|
1277
|
+
tab
|
|
1278
|
+
more
|
|
1279
|
+
</pre>
|
|
1280
|
+
`;
|
|
1281
|
+
assert.equal(await fmt(source), source);
|
|
1282
|
+
|
|
1283
|
+
// a subtle variation that does get formatted: a space in the start tag
|
|
1284
|
+
// (the whitespace-significant content is still untouched)
|
|
1285
|
+
assert.equal(
|
|
1286
|
+
await fmt(`<pre >
|
|
1287
|
+
two spaces
|
|
1288
|
+
tab
|
|
1289
|
+
more
|
|
1290
|
+
</pre>
|
|
1291
|
+
`),
|
|
1292
|
+
source,
|
|
1293
|
+
);
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
test('keeps void elements without end tags', async () => {
|
|
1297
|
+
assert.equal(await fmt('<br>'), '<br>\n');
|
|
1298
|
+
assert.equal(await fmt('<hr>'), '<hr>\n');
|
|
1299
|
+
assert.equal(await fmt('<img src="a.png">'), '<img src="a.png">\n');
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
test('expands multi-attribute void elements but never adds an end tag', async () => {
|
|
1303
|
+
assert.equal(
|
|
1304
|
+
await fmt('<img src="a.png" alt="a">'),
|
|
1305
|
+
`<img
|
|
1306
|
+
src="a.png"
|
|
1307
|
+
alt="a"
|
|
1308
|
+
>
|
|
1309
|
+
`,
|
|
1310
|
+
);
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
test('keeps self-closing syntax for custom elements', async () => {
|
|
1314
|
+
assert.equal(await fmt('<custom-element />'), '<custom-element />\n');
|
|
1315
|
+
assert.equal(
|
|
1316
|
+
await fmt(`<custom-element />
|
|
1317
|
+
<p>x</p>`),
|
|
1318
|
+
`<custom-element />
|
|
1319
|
+
<p>x</p>
|
|
1320
|
+
`,
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
// subtle variations that do get formatted: the space before the slash is
|
|
1324
|
+
// normalised to a single one
|
|
1325
|
+
assert.equal(await fmt('<custom-element />'), '<custom-element />\n');
|
|
1326
|
+
assert.equal(
|
|
1327
|
+
await fmt(`<custom-element/>
|
|
1328
|
+
<p>x</p>`),
|
|
1329
|
+
`<custom-element />
|
|
1330
|
+
<p>x</p>
|
|
1331
|
+
`,
|
|
1332
|
+
);
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
test('does not invent an end tag for a self-closing single-attribute tag', async () => {
|
|
1336
|
+
// regression: the single-line start tag used to drop the `/`
|
|
1337
|
+
assert.equal(await fmt('<img src="a" />'), '<img src="a" />\n');
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
test('ends the document with exactly one newline', async () => {
|
|
1341
|
+
assert.equal(await fmt('<div>x</div>'), '<div>x</div>\n');
|
|
1342
|
+
assert.equal(
|
|
1343
|
+
await fmt(`<div>x</div>
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
`),
|
|
1347
|
+
'<div>x</div>\n',
|
|
1348
|
+
);
|
|
1349
|
+
assert.equal(await fmt(' ', { parser: 'twig-html', plugins: [plugin] }), '');
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
test('normalizes unquoted, single-quoted and interpolated attribute forms', async () => {
|
|
1353
|
+
const out = await fmt(`<input type=text disabled value='a b' data-label="{{ label }}">`);
|
|
1354
|
+
assert.equal(
|
|
1355
|
+
out,
|
|
1356
|
+
`<input
|
|
1357
|
+
type=text
|
|
1358
|
+
disabled
|
|
1359
|
+
value='a b'
|
|
1360
|
+
data-label="{{ label }}"
|
|
1361
|
+
>
|
|
1362
|
+
`,
|
|
1363
|
+
);
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
test('keeps event-handler attribute values verbatim', async () => {
|
|
1367
|
+
const source = `<form
|
|
1368
|
+
class="x"
|
|
1369
|
+
onsubmit="
|
|
1370
|
+
;(function (e) {
|
|
1371
|
+
var element = document.getElementById('{{ id }}-select');
|
|
1372
|
+
if (!element) return;
|
|
1373
|
+
|
|
1374
|
+
window.location = href;
|
|
1375
|
+
}(event));
|
|
1376
|
+
"
|
|
1377
|
+
>
|
|
1378
|
+
</form>
|
|
1379
|
+
`;
|
|
1380
|
+
assert.equal(await fmt(source), source);
|
|
1381
|
+
|
|
1382
|
+
// a subtle variation that does get formatted: the ordinary attribute is
|
|
1383
|
+
// normalised while the event-handler value stays verbatim
|
|
1384
|
+
assert.equal(
|
|
1385
|
+
await fmt(`<form
|
|
1386
|
+
class = "x"
|
|
1387
|
+
onsubmit="
|
|
1388
|
+
;(function (e) {
|
|
1389
|
+
var element = document.getElementById('{{ id }}-select');
|
|
1390
|
+
if (!element) return;
|
|
1391
|
+
|
|
1392
|
+
window.location = href;
|
|
1393
|
+
}(event));
|
|
1394
|
+
"
|
|
1395
|
+
>
|
|
1396
|
+
</form>
|
|
1397
|
+
`),
|
|
1398
|
+
source,
|
|
1399
|
+
);
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
test('expands the start tag when an attribute value is multi-line', async () => {
|
|
1403
|
+
const source = `<div style="
|
|
1404
|
+
background-color: paleturquoise;
|
|
1405
|
+
box-sizing: border-box;
|
|
1406
|
+
font-family: monospace;
|
|
1407
|
+
min-height: 6.25rem;
|
|
1408
|
+
">
|
|
1409
|
+
<p>
|
|
1410
|
+
wrapper
|
|
1411
|
+
</p>
|
|
1412
|
+
</div>
|
|
1413
|
+
`;
|
|
1414
|
+
assert.equal(
|
|
1415
|
+
await fmt(source),
|
|
1416
|
+
`<div
|
|
1417
|
+
style="
|
|
1418
|
+
background-color: paleturquoise;
|
|
1419
|
+
box-sizing: border-box;
|
|
1420
|
+
font-family: monospace;
|
|
1421
|
+
min-height: 6.25rem;
|
|
1422
|
+
"
|
|
1423
|
+
>
|
|
1424
|
+
<p>
|
|
1425
|
+
wrapper
|
|
1426
|
+
</p>
|
|
1427
|
+
</div>
|
|
1428
|
+
`,
|
|
1429
|
+
);
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
test('aligns multi-line attribute values on one indent level', async () => {
|
|
1433
|
+
{
|
|
1434
|
+
const out = await fmt(`<img srcset="
|
|
1435
|
+
{{ url|add_query_arg({ w: 1600, q: 45 }) }} 2x,
|
|
1436
|
+
{{ url|add_query_arg({ w: 800 }) }}
|
|
1437
|
+
">`);
|
|
1438
|
+
assert.equal(
|
|
1439
|
+
out,
|
|
1440
|
+
`<img
|
|
1441
|
+
srcset="
|
|
1442
|
+
{{ url|add_query_arg({ w: 1600, q: 45 }) }} 2x,
|
|
1443
|
+
{{ url|add_query_arg({ w: 800 }) }}
|
|
1444
|
+
"
|
|
1445
|
+
>
|
|
1446
|
+
`,
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
{
|
|
1451
|
+
const out = await fmt(`<div class="x" id="y"><img
|
|
1452
|
+
srcset="
|
|
1453
|
+
{{ a }} 1x,
|
|
1454
|
+
{{ b }} 2x
|
|
1455
|
+
"
|
|
1456
|
+
></div>`);
|
|
1457
|
+
assert.equal(
|
|
1458
|
+
out,
|
|
1459
|
+
`<div
|
|
1460
|
+
class="x"
|
|
1461
|
+
id="y"
|
|
1462
|
+
>
|
|
1463
|
+
<img
|
|
1464
|
+
srcset="
|
|
1465
|
+
{{ a }} 1x,
|
|
1466
|
+
{{ b }} 2x
|
|
1467
|
+
"
|
|
1468
|
+
>
|
|
1469
|
+
</div>
|
|
1470
|
+
`,
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
test('indents twig block bodies while section heads stay at block indent', async () => {
|
|
1476
|
+
const source = `{% if a %}
|
|
1477
|
+
{% for x in y %}
|
|
1478
|
+
<p>{{ x }}</p>
|
|
1479
|
+
{% endfor %}
|
|
1480
|
+
{% elseif b %}
|
|
1481
|
+
<p>b</p>
|
|
1482
|
+
{% else %}
|
|
1483
|
+
<p>c</p>
|
|
1484
|
+
{% endif %}
|
|
1485
|
+
`;
|
|
1486
|
+
const out = await fmt(source);
|
|
1487
|
+
assert.equal(out, source);
|
|
1488
|
+
|
|
1489
|
+
// a subtle variation that does get formatted: spaces inside the block tags
|
|
1490
|
+
assert.equal(
|
|
1491
|
+
await fmt(`{% if a %}
|
|
1492
|
+
{% for x in y %}
|
|
1493
|
+
<p>{{ x }}</p>
|
|
1494
|
+
{% endfor %}
|
|
1495
|
+
{% elseif b %}
|
|
1496
|
+
<p>b</p>
|
|
1497
|
+
{% else %}
|
|
1498
|
+
<p>c</p>
|
|
1499
|
+
{% endif %}
|
|
1500
|
+
`),
|
|
1501
|
+
source,
|
|
1502
|
+
);
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
test('places else and elseif at the block indent in a flattened chain', async () => {
|
|
1506
|
+
{
|
|
1507
|
+
const out = await fmt(`{% if user %}<span>{{ user.name }}</span>{% endif %}`);
|
|
1508
|
+
assert.equal(
|
|
1509
|
+
out,
|
|
1510
|
+
`{% if user %}
|
|
1511
|
+
<span>{{ user.name }}</span>
|
|
1512
|
+
{% endif %}
|
|
1513
|
+
`,
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
{
|
|
1518
|
+
const out = await fmt(`{% if user %}
|
|
1519
|
+
<span>{{ user.name }}</span>
|
|
1520
|
+
{% endif %}`);
|
|
1521
|
+
assert.equal(
|
|
1522
|
+
out,
|
|
1523
|
+
`{% if user %}
|
|
1524
|
+
<span>{{ user.name }}</span>
|
|
1525
|
+
{% endif %}
|
|
1526
|
+
`,
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
{
|
|
1531
|
+
const out = await fmt(`{% if user %}<span>{{ user.name }}</span>{% else %}<span>N/A</span>{% endif %}`);
|
|
1532
|
+
assert.equal(
|
|
1533
|
+
out,
|
|
1534
|
+
`{% if user %}
|
|
1535
|
+
<span>{{ user.name }}</span>
|
|
1536
|
+
{% else %}
|
|
1537
|
+
<span>N/A</span>
|
|
1538
|
+
{% endif %}
|
|
1539
|
+
`,
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
{
|
|
1544
|
+
const out = await fmt(`{% if user %}
|
|
1545
|
+
<span>{{ user.name }}</span>
|
|
1546
|
+
{% else %}
|
|
1547
|
+
<span>N/A</span>
|
|
1548
|
+
{% endif %}`);
|
|
1549
|
+
assert.equal(
|
|
1550
|
+
out,
|
|
1551
|
+
`{% if user %}
|
|
1552
|
+
<span>{{ user.name }}</span>
|
|
1553
|
+
{% else %}
|
|
1554
|
+
<span>N/A</span>
|
|
1555
|
+
{% endif %}
|
|
1556
|
+
`,
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
test('does not mess with whitespace in html elements', async () => {
|
|
1562
|
+
{
|
|
1563
|
+
const out = await fmt(`<span>{% if user %}{{ user.name }}{% else %}N/A{% endif %}</span>`);
|
|
1564
|
+
assert.equal(
|
|
1565
|
+
out,
|
|
1566
|
+
`<span>{% if user %}{{ user.name }}{% else %}N/A{% endif %}</span>
|
|
1567
|
+
`,
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
{
|
|
1572
|
+
const out = await fmt(`<span>
|
|
1573
|
+
{% if user %} {{ user.name }}{% else %} N/A{% endif %}
|
|
1574
|
+
</span>`);
|
|
1575
|
+
assert.equal(
|
|
1576
|
+
out,
|
|
1577
|
+
`<span>
|
|
1578
|
+
{% if user %} {{ user.name }}{% else %} N/A{% endif %}
|
|
1579
|
+
</span>
|
|
1580
|
+
`,
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
test('drops redundant blank lines between siblings', async () => {
|
|
1586
|
+
const source = `<div>alpha</div>
|
|
1587
|
+
|
|
1588
|
+
<div>beta</div>
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
|
|
1592
|
+
|
|
1593
|
+
<div>gamma</div>`;
|
|
1594
|
+
const out = await fmt(source);
|
|
1595
|
+
assert.equal(
|
|
1596
|
+
out,
|
|
1597
|
+
`<div>alpha</div>
|
|
1598
|
+
|
|
1599
|
+
<div>beta</div>
|
|
1600
|
+
|
|
1601
|
+
<div>gamma</div>
|
|
1602
|
+
`,
|
|
1603
|
+
);
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
test('keeps twig and html comments, including inline and trimmed comments', async () => {
|
|
1607
|
+
const source = `<!doctype html>
|
|
1608
|
+
<!-- an html comment -->
|
|
1609
|
+
{# a twig comment #}
|
|
1610
|
+
{#- a trimmed twig comment -#}
|
|
1611
|
+
<div>{# inline #}text</div>
|
|
1612
|
+
`;
|
|
1613
|
+
const out = await fmt(source);
|
|
1614
|
+
assert.equal(out, source);
|
|
1615
|
+
|
|
1616
|
+
// a subtle variation that does get formatted: spaces in the standalone
|
|
1617
|
+
// comments are collapsed (the verbatim inline comment is left alone)
|
|
1618
|
+
assert.equal(
|
|
1619
|
+
await fmt(`<!doctype html>
|
|
1620
|
+
<!-- an html comment -->
|
|
1621
|
+
{# a twig comment #}
|
|
1622
|
+
{#- a trimmed twig comment -#}
|
|
1623
|
+
<div>{# inline #}text</div>
|
|
1624
|
+
`),
|
|
1625
|
+
source,
|
|
1626
|
+
);
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
test('indents deeply nested elements', async () => {
|
|
1630
|
+
const out = await fmt(`<div><section><article><p>deep</p></article></section></div>`);
|
|
1631
|
+
assert.equal(
|
|
1632
|
+
out,
|
|
1633
|
+
`<div>
|
|
1634
|
+
<section>
|
|
1635
|
+
<article>
|
|
1636
|
+
<p>deep</p>
|
|
1637
|
+
</article>
|
|
1638
|
+
</section>
|
|
1639
|
+
</div>
|
|
1640
|
+
`,
|
|
1641
|
+
);
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
test('indents nested loops inside elements', async () => {
|
|
1645
|
+
const source = `<table>
|
|
1646
|
+
<tbody>
|
|
1647
|
+
{% for row in rows %}
|
|
1648
|
+
<tr>
|
|
1649
|
+
{% for cell in row %}
|
|
1650
|
+
<td>{{ cell }}</td>
|
|
1651
|
+
{% endfor %}
|
|
1652
|
+
</tr>
|
|
1653
|
+
{% endfor %}
|
|
1654
|
+
</tbody>
|
|
1655
|
+
</table>
|
|
1656
|
+
`;
|
|
1657
|
+
assert.equal(await fmt(source), source);
|
|
1658
|
+
|
|
1659
|
+
// a subtle variation that does get formatted: spaces inside the loop tag
|
|
1660
|
+
assert.equal(
|
|
1661
|
+
await fmt(`<table>
|
|
1662
|
+
<tbody>
|
|
1663
|
+
{% for row in rows %}
|
|
1664
|
+
<tr>
|
|
1665
|
+
{% for cell in row %}
|
|
1666
|
+
<td>{{ cell }}</td>
|
|
1667
|
+
{% endfor %}
|
|
1668
|
+
</tr>
|
|
1669
|
+
{% endfor %}
|
|
1670
|
+
</tbody>
|
|
1671
|
+
</table>
|
|
1672
|
+
`),
|
|
1673
|
+
source,
|
|
1674
|
+
);
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
test('nests control structures inside elements', async () => {
|
|
1678
|
+
const out = await fmt(`{% for group in groups %}<div><h2>{{ group.name }}</h2><ul>{% for item in group.items %}<li>{{ item }}</li>{% endfor %}</ul></div>{% endfor %}`);
|
|
1679
|
+
assert.equal(
|
|
1680
|
+
out,
|
|
1681
|
+
`{% for group in groups %}
|
|
1682
|
+
<div>
|
|
1683
|
+
<h2>{{ group.name }}</h2>
|
|
1684
|
+
<ul>
|
|
1685
|
+
{% for item in group.items %}
|
|
1686
|
+
<li>{{ item }}</li>
|
|
1687
|
+
{% endfor %}
|
|
1688
|
+
</ul>
|
|
1689
|
+
</div>
|
|
1690
|
+
{% endfor %}
|
|
1691
|
+
`,
|
|
1692
|
+
);
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
test('indents nested if/else inside a loop with multi-attribute tags', async () => {
|
|
1696
|
+
const out = await fmt(`{% for x in items %}{% if x.active %}<li class="active" data-id="{{ x.id }}">{{ x.name }}</li>{% else %}<li>{{ x.name }}</li>{% endif %}{% else %}<li>empty</li>{% endfor %}`);
|
|
1697
|
+
assert.equal(
|
|
1698
|
+
out,
|
|
1699
|
+
`{% for x in items %}
|
|
1700
|
+
{% if x.active %}
|
|
1701
|
+
<li
|
|
1702
|
+
class="active"
|
|
1703
|
+
data-id="{{ x.id }}"
|
|
1704
|
+
>{{ x.name }}</li>
|
|
1705
|
+
{% else %}
|
|
1706
|
+
<li>{{ x.name }}</li>
|
|
1707
|
+
{% endif %}
|
|
1708
|
+
{% else %}
|
|
1709
|
+
<li>empty</li>
|
|
1710
|
+
{% endfor %}
|
|
1711
|
+
`,
|
|
1712
|
+
);
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
test('keeps an if/elseif/else chain with inline bodies verbatim', async () => {
|
|
1716
|
+
assert.equal(
|
|
1717
|
+
await fmt(`{% if a %}A{% elseif b %}B{% elseif c %}C{% else %}D{% endif %}`),
|
|
1718
|
+
`{% if a %}A{% elseif b %}B{% elseif c %}C{% else %}D{% endif %}
|
|
1719
|
+
`,
|
|
1720
|
+
);
|
|
1721
|
+
|
|
1722
|
+
// a subtle variation that does get formatted: a line break inside a body
|
|
1723
|
+
// means the chain is no longer inline, so it is expanded
|
|
1724
|
+
assert.equal(
|
|
1725
|
+
await fmt(`{% if a %}
|
|
1726
|
+
A{% elseif b %}B{% elseif c %}C{% else %}D{% endif %}`),
|
|
1727
|
+
`{% if a %}
|
|
1728
|
+
A
|
|
1729
|
+
{% elseif b %}
|
|
1730
|
+
B
|
|
1731
|
+
{% elseif c %}
|
|
1732
|
+
C
|
|
1733
|
+
{% else %}
|
|
1734
|
+
D
|
|
1735
|
+
{% endif %}
|
|
1736
|
+
`,
|
|
1737
|
+
);
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
test('indents nested twig blocks inside nested elements', async () => {
|
|
1741
|
+
const out = await fmt(`<div>
|
|
1742
|
+
<section>
|
|
1743
|
+
{% if a %}
|
|
1744
|
+
{% for x in y %}
|
|
1745
|
+
<p>{{ x }}</p>
|
|
1746
|
+
{% endfor %}
|
|
1747
|
+
{% else %}
|
|
1748
|
+
<p>none</p>
|
|
1749
|
+
{% endif %}
|
|
1750
|
+
</section>
|
|
1751
|
+
</div>`);
|
|
1752
|
+
assert.equal(
|
|
1753
|
+
out,
|
|
1754
|
+
`<div>
|
|
1755
|
+
<section>
|
|
1756
|
+
{% if a %}
|
|
1757
|
+
{% for x in y %}
|
|
1758
|
+
<p>{{ x }}</p>
|
|
1759
|
+
{% endfor %}
|
|
1760
|
+
{% else %}
|
|
1761
|
+
<p>none</p>
|
|
1762
|
+
{% endif %}
|
|
1763
|
+
</section>
|
|
1764
|
+
</div>
|
|
1765
|
+
`,
|
|
1766
|
+
);
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1769
|
+
test('nested indent for include blocks', async () => {
|
|
1770
|
+
const source = `<div class="row">
|
|
1771
|
+
<div class="wrapper">
|
|
1772
|
+
<div class="frame-detail">
|
|
1773
|
+
{% if page_fields.colorways|length > 0 %}
|
|
1774
|
+
<ul class="frame-detail__colorways">
|
|
1775
|
+
{% for colorway in page_fields.colorways %}
|
|
1776
|
+
{% set image = ensure_timber_image(colorway.image) %}
|
|
1777
|
+
{% if image and colorway.name %}
|
|
1778
|
+
{% set anchor = single_use_anchor(fn('sanitize_title', colorway.name)) %}
|
|
1779
|
+
<li
|
|
1780
|
+
id="{{ anchor }}"
|
|
1781
|
+
class="frame-detail__colorways__item"
|
|
1782
|
+
>
|
|
1783
|
+
{% include 'snippets/colorway.twig' with {
|
|
1784
|
+
lazy_loading: loop.index0 > 1,
|
|
1785
|
+
} %}
|
|
1786
|
+
</li>
|
|
1787
|
+
{% endif %}
|
|
1788
|
+
{% endfor %}
|
|
1789
|
+
</ul>
|
|
1790
|
+
{% endif %}
|
|
1791
|
+
</div>
|
|
1792
|
+
</div>
|
|
1793
|
+
</div>
|
|
1794
|
+
`;
|
|
1795
|
+
|
|
1796
|
+
const out = await fmt(source);
|
|
1797
|
+
|
|
1798
|
+
assert.equal(out, source);
|
|
1799
|
+
|
|
1800
|
+
// a subtle variation that does get formatted: spaces inside a block tag
|
|
1801
|
+
assert.equal(
|
|
1802
|
+
await fmt(`<div class="row">
|
|
1803
|
+
<div class="wrapper">
|
|
1804
|
+
<div class="frame-detail">
|
|
1805
|
+
{% if page_fields.colorways|length > 0 %}
|
|
1806
|
+
<ul class="frame-detail__colorways">
|
|
1807
|
+
{% for colorway in page_fields.colorways %}
|
|
1808
|
+
{% set image = ensure_timber_image(colorway.image) %}
|
|
1809
|
+
{% if image and colorway.name %}
|
|
1810
|
+
{% set anchor = single_use_anchor(fn('sanitize_title', colorway.name)) %}
|
|
1811
|
+
<li
|
|
1812
|
+
id="{{ anchor }}"
|
|
1813
|
+
class="frame-detail__colorways__item"
|
|
1814
|
+
>
|
|
1815
|
+
{% include 'snippets/colorway.twig' with {
|
|
1816
|
+
lazy_loading: loop.index0 > 1,
|
|
1817
|
+
} %}
|
|
1818
|
+
</li>
|
|
1819
|
+
{% endif %}
|
|
1820
|
+
{% endfor %}
|
|
1821
|
+
</ul>
|
|
1822
|
+
{% endif %}
|
|
1823
|
+
</div>
|
|
1824
|
+
</div>
|
|
1825
|
+
</div>
|
|
1826
|
+
`),
|
|
1827
|
+
source,
|
|
1828
|
+
);
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
test('nested blank link', async () => {
|
|
1832
|
+
const source = `<div class="row">
|
|
1833
|
+
<div class="wrapper">
|
|
1834
|
+
<div class="frame-detail">
|
|
1835
|
+
<div class="frame-detail__information">
|
|
1836
|
+
{% if page_fields.product_description %}
|
|
1837
|
+
<h2 class="frame-detail__information__title type-b">
|
|
1838
|
+
{{ __('Description', 'mr') }}
|
|
1839
|
+
</h2>
|
|
1840
|
+
|
|
1841
|
+
<div class="frame-detail__information__description type-d">
|
|
1842
|
+
{{ page_fields.product_description|wpautop }}
|
|
1843
|
+
</div>
|
|
1844
|
+
{% endif %}
|
|
1845
|
+
</div>
|
|
1846
|
+
</div>
|
|
1847
|
+
</div>
|
|
1848
|
+
</div>
|
|
1849
|
+
`;
|
|
1850
|
+
|
|
1851
|
+
const out = await fmt(source);
|
|
1852
|
+
|
|
1853
|
+
assert.equal(out, source);
|
|
1854
|
+
|
|
1855
|
+
// a subtle variation that does get formatted: an extra blank line collapses
|
|
1856
|
+
assert.equal(
|
|
1857
|
+
await fmt(`<div class="row">
|
|
1858
|
+
<div class="wrapper">
|
|
1859
|
+
<div class="frame-detail">
|
|
1860
|
+
<div class="frame-detail__information">
|
|
1861
|
+
{% if page_fields.product_description %}
|
|
1862
|
+
<h2 class="frame-detail__information__title type-b">
|
|
1863
|
+
{{ __('Description', 'mr') }}
|
|
1864
|
+
</h2>
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
<div class="frame-detail__information__description type-d">
|
|
1868
|
+
{{ page_fields.product_description|wpautop }}
|
|
1869
|
+
</div>
|
|
1870
|
+
{% endif %}
|
|
1871
|
+
</div>
|
|
1872
|
+
</div>
|
|
1873
|
+
</div>
|
|
1874
|
+
</div>
|
|
1875
|
+
`),
|
|
1876
|
+
source,
|
|
1877
|
+
);
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
test('multiple twig structures on a single line', async () => {
|
|
1881
|
+
const source = `{{ teaser.number_of_colorways }} {{ __('colours', 'mr') -}}
|
|
1882
|
+
`;
|
|
1883
|
+
|
|
1884
|
+
const out = await fmt(source);
|
|
1885
|
+
|
|
1886
|
+
assert.equal(out, source);
|
|
1887
|
+
|
|
1888
|
+
// a subtle variation that does get formatted: extra spaces inside and
|
|
1889
|
+
// between the expressions are normalised
|
|
1890
|
+
assert.equal(
|
|
1891
|
+
await fmt(`{{ teaser.number_of_colorways }} {{ __('colours', 'mr') -}}
|
|
1892
|
+
`),
|
|
1893
|
+
source,
|
|
1894
|
+
);
|
|
1895
|
+
});
|
|
1896
|
+
|