mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
chore(root): migrate build scripts and plugins to TypeScript (#1052)
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
import { inlineTemplatePlugin } from '../inline-template-plugin.ts';
|
||||
|
||||
// Helper: run the plugin's transform on a code string.
|
||||
function transform(code: string): string {
|
||||
const plugin = inlineTemplatePlugin({ minify: true });
|
||||
const result = plugin.transform?.(code, 'test.ts');
|
||||
return result?.code ?? code;
|
||||
}
|
||||
|
||||
// Helper: extract the minified template body (between the backticks).
|
||||
function minifyHTML(template: string): string {
|
||||
const code = `/*html*/ \`${template}\``;
|
||||
const result = transform(code);
|
||||
const match = result.match(/`([\s\S]*)`/);
|
||||
return match?.[1] ?? '';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HTML minification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('minifyHtmlQuasis', () => {
|
||||
// ------- whitespace -------
|
||||
|
||||
it('collapses whitespace-only text content between tags', () => {
|
||||
// Whitespace-only text between > and < is removed entirely
|
||||
// (step 2 collapses to single space, step 3 removes inter-tag space).
|
||||
assert.equal(minifyHTML('<div> </div>'), '<div></div>');
|
||||
});
|
||||
|
||||
it('removes whitespace between tags', () => {
|
||||
assert.equal(minifyHTML('<div></div> <span></span>'), '<div></div><span></span>');
|
||||
});
|
||||
|
||||
it('trims leading whitespace from the template', () => {
|
||||
assert.equal(minifyHTML(' <div></div>'), '<div></div>');
|
||||
});
|
||||
|
||||
it('trims trailing whitespace from the template', () => {
|
||||
assert.equal(minifyHTML('<div></div> '), '<div></div>');
|
||||
});
|
||||
|
||||
it('collapses newlines and indentation between tags', () => {
|
||||
const input = `
|
||||
<div>
|
||||
<span></span>
|
||||
</div>
|
||||
`;
|
||||
assert.equal(minifyHTML(input), '<div><span></span></div>');
|
||||
});
|
||||
|
||||
// ------- HTML comments -------
|
||||
|
||||
it('strips HTML comments', () => {
|
||||
assert.equal(minifyHTML('<!-- comment --><div></div>'), '<div></div>');
|
||||
});
|
||||
|
||||
it('strips multiline HTML comments', () => {
|
||||
const input = `
|
||||
<!--
|
||||
multi
|
||||
line
|
||||
comment
|
||||
-->
|
||||
<div></div>
|
||||
`;
|
||||
assert.equal(minifyHTML(input), '<div></div>');
|
||||
});
|
||||
|
||||
it('strips comments between elements', () => {
|
||||
assert.equal(minifyHTML('<div></div><!-- separator --><span></span>'), '<div></div><span></span>');
|
||||
});
|
||||
|
||||
// ------- empty elements -------
|
||||
|
||||
it('preserves empty elements', () => {
|
||||
assert.equal(minifyHTML('<slot></slot>'), '<slot></slot>');
|
||||
});
|
||||
|
||||
it('preserves empty elements with attributes', () => {
|
||||
assert.equal(minifyHTML('<slot name="media"></slot>'), '<slot name="media"></slot>');
|
||||
});
|
||||
|
||||
it('preserves adjacent empty slot elements', () => {
|
||||
const input = `
|
||||
<slot name="media"></slot>
|
||||
<slot></slot>
|
||||
`;
|
||||
assert.equal(minifyHTML(input), '<slot name="media"></slot><slot></slot>');
|
||||
});
|
||||
|
||||
it('preserves the default slot between named slots', () => {
|
||||
const input = `
|
||||
<slot name="media"></slot>
|
||||
<slot></slot>
|
||||
<slot name="poster"></slot>
|
||||
`;
|
||||
assert.equal(minifyHTML(input), '<slot name="media"></slot><slot></slot><slot name="poster"></slot>');
|
||||
});
|
||||
|
||||
it('preserves empty void-like elements', () => {
|
||||
assert.equal(minifyHTML('<div></div>'), '<div></div>');
|
||||
});
|
||||
|
||||
// ------- attribute values with special characters -------
|
||||
|
||||
it('does not corrupt attribute values containing > <', () => {
|
||||
assert.equal(minifyHTML('<div data-expr="a > b < c"></div>'), '<div data-expr="a > b < c"></div>');
|
||||
});
|
||||
|
||||
it('does not corrupt single-quoted attribute values containing > <', () => {
|
||||
assert.equal(minifyHTML("<div data-expr='a > b < c'></div>"), "<div data-expr='a > b < c'></div>");
|
||||
});
|
||||
|
||||
it('handles attribute values with > followed by space and <', () => {
|
||||
assert.equal(minifyHTML('<div title="test > <value"></div>'), '<div title="test > <value"></div>');
|
||||
});
|
||||
|
||||
// ------- text content -------
|
||||
|
||||
it('preserves text content inside elements', () => {
|
||||
assert.equal(minifyHTML('<span>hello world</span>'), '<span>hello world</span>');
|
||||
});
|
||||
|
||||
it('preserves single space in text content between tags', () => {
|
||||
// Text-only whitespace after > that is NOT followed by < should be kept.
|
||||
assert.equal(minifyHTML('<span>hello</span> world'), '<span>hello</span> world');
|
||||
});
|
||||
|
||||
// ------- realistic skin template -------
|
||||
|
||||
it('correctly minifies a skin-like template', () => {
|
||||
const input = `
|
||||
<media-container class="media-default-skin">
|
||||
<!-- @deprecated slot="media" is no longer required -->
|
||||
<slot name="media"></slot>
|
||||
<slot></slot>
|
||||
|
||||
<media-poster>
|
||||
<slot name="poster"></slot>
|
||||
</media-poster>
|
||||
</media-container>
|
||||
`;
|
||||
|
||||
assert.equal(
|
||||
minifyHTML(input),
|
||||
'<media-container class="media-default-skin">' +
|
||||
'<slot name="media"></slot>' +
|
||||
'<slot></slot>' +
|
||||
'<media-poster><slot name="poster"></slot></media-poster>' +
|
||||
'</media-container>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Template expressions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('processTemplates (expressions)', () => {
|
||||
it('preserves template expressions', () => {
|
||||
const code = 'const html = /*html*/ `<div>${expr}</div>`;';
|
||||
const result = transform(code);
|
||||
assert.equal(result, 'const html = /*html*/ `<div>${expr}</div>`;');
|
||||
});
|
||||
|
||||
it('handles multiple expressions', () => {
|
||||
const code = 'const html = /*html*/ `<div>${a}</div><span>${b}</span>`;';
|
||||
const result = transform(code);
|
||||
assert.equal(result, 'const html = /*html*/ `<div>${a}</div><span>${b}</span>`;');
|
||||
});
|
||||
|
||||
it('handles expressions with nested braces', () => {
|
||||
const code = "const html = /*html*/ `<div>${fn({ class: 'icon' })}</div>`;";
|
||||
const result = transform(code);
|
||||
assert.equal(result, "const html = /*html*/ `<div>${fn({ class: 'icon' })}</div>`;");
|
||||
});
|
||||
|
||||
it('handles expressions with nested template literals', () => {
|
||||
const code = 'const html = /*html*/ `<div>${`nested ${value}`}</div>`;';
|
||||
const result = transform(code);
|
||||
assert.equal(result, 'const html = /*html*/ `<div>${`nested ${value}`}</div>`;');
|
||||
});
|
||||
|
||||
it('collapses whitespace around expressions', () => {
|
||||
const code = `const html = /*html*/ \`
|
||||
<div>
|
||||
\${expr}
|
||||
</div>
|
||||
\`;`;
|
||||
const result = transform(code);
|
||||
assert.equal(result, 'const html = /*html*/ `<div> ${expr} </div>`;');
|
||||
});
|
||||
|
||||
it('removes inter-tag whitespace around expressions', () => {
|
||||
const code = `const html = /*html*/ \`
|
||||
<div>
|
||||
\${expr}
|
||||
</div>
|
||||
<span></span>
|
||||
\`;`;
|
||||
const result = transform(code);
|
||||
assert.equal(result, 'const html = /*html*/ `<div> ${expr} </div><span></span>`;');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CSS minification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('minifyCssQuasis', () => {
|
||||
it('minifies simple CSS', () => {
|
||||
const code = 'const css = /* css */ ` .foo { color: red; } `;';
|
||||
const result = transform(code);
|
||||
assert.match(result, /\.foo\s*\{/);
|
||||
assert.match(result, /color:\s*red/);
|
||||
});
|
||||
|
||||
it('preserves CSS expressions', () => {
|
||||
const code = 'const css = /* css */ `.foo { color: ${color}; }`;';
|
||||
const result = transform(code);
|
||||
assert.match(result, /\$\{color\}/);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Edge cases
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('returns null when minify is disabled', () => {
|
||||
const plugin = inlineTemplatePlugin({ minify: false });
|
||||
assert.equal(plugin.transform?.('/*html*/ `<div></div>`', 'test.ts'), null);
|
||||
});
|
||||
|
||||
it('returns null when code contains no markers', () => {
|
||||
const plugin = inlineTemplatePlugin({ minify: true });
|
||||
assert.equal(plugin.transform?.('const x = 1;', 'test.ts'), null);
|
||||
});
|
||||
|
||||
it('returns null when marker is not followed by a template literal', () => {
|
||||
const code = 'const marker = "/*html*/"; const x = 1;';
|
||||
const result = transform(code);
|
||||
assert.equal(result, code);
|
||||
});
|
||||
|
||||
it('handles multiple HTML templates in one file', () => {
|
||||
const code = ['const a = /*html*/ `<div> </div>`;', 'const b = /*html*/ `<span> </span>`;'].join('\n');
|
||||
const result = transform(code);
|
||||
assert.match(result, /`<div><\/div>`/);
|
||||
assert.match(result, /`<span><\/span>`/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user