317 lines
9.4 KiB
JavaScript
317 lines
9.4 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert");
|
|
const { test } = require("node:test");
|
|
const { formatWrn } = require("../src/formatter");
|
|
|
|
test("formats compact props blocks with one prop per line", () => {
|
|
const source = `component Header {\nprops { eyebrow = "" title = "" description = "" align = "start" class = "" }\nview { <header>{title}</header> }\n}\n`;
|
|
const expected = `component Header {\n props {\n eyebrow = ""\n title = ""\n description = ""\n align = "start"\n class = ""\n }\n view { <header>{title}</header> }\n}\n`;
|
|
|
|
assert.equal(formatWrn(source, { insertSpaces: true, tabSize: 2 }), expected);
|
|
});
|
|
|
|
test("preserves nested prop defaults while formatting", () => {
|
|
const source = `component Grid {\nprops { items = [{ label: "A" }, { label: "B" }] options = { gap: 4, dense: false } class = "" }\nview { <div/> }\n}\n`;
|
|
const formatted = formatWrn(source, { insertSpaces: true, tabSize: 2 });
|
|
|
|
assert.match(formatted, /items = \[\{ label: "A" \}, \{ label: "B" \}\]/);
|
|
assert.match(formatted, /options = \{ gap: 4, dense: false \}/);
|
|
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2 }), formatted);
|
|
});
|
|
|
|
test("formats large typed JSON prop defaults with the bundled formatter", () => {
|
|
const source = `component Catalog {
|
|
props {
|
|
categories: unknown[] = [{"label":"Platform & Core","apps":[{"label":"Home","href":"/home"},{"label":"SSO","href":"/sso"}]},{"label":"Sales","apps":[{"label":"CRM","href":"/crm"}]}]
|
|
}
|
|
view { <div></div> }
|
|
}`;
|
|
const formatted = formatWrn(source, { insertSpaces: true, tabSize: 2, printWidth: 70 });
|
|
|
|
assert.match(formatted, /categories: unknown\[\] = \[\n \{/);
|
|
assert.match(formatted, /"apps": \[\n \{/);
|
|
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2, printWidth: 70 }), formatted);
|
|
});
|
|
|
|
test("formats native JSON state values with readable indentation", () => {
|
|
const source = `component Footer {
|
|
state footerItems = [{"label":"Accessibility","href":"/accessibility","value":"accessibility"},{"label":"Privacy","href":"/privacy","value":"privacy"}]
|
|
view { <footer></footer> }
|
|
}
|
|
`;
|
|
const expected = `component Footer {
|
|
state footerItems = [
|
|
{
|
|
"label": "Accessibility",
|
|
"href": "/accessibility",
|
|
"value": "accessibility"
|
|
},
|
|
{
|
|
"label": "Privacy",
|
|
"href": "/privacy",
|
|
"value": "privacy"
|
|
}
|
|
]
|
|
view { <footer></footer> }
|
|
}
|
|
`;
|
|
const options = { insertSpaces: true, tabSize: 2 };
|
|
|
|
assert.equal(formatWrn(source, options), expected);
|
|
assert.equal(formatWrn(expected, options), expected);
|
|
});
|
|
|
|
test("formats direct structured component props and preserves expression props", () => {
|
|
const source = `component Demo {
|
|
view {
|
|
<Footer items={footerItems} options={{"dense":true,"theme":"public"}} links={[{"label":"Privacy","href":"/privacy"},{"label":"Contact","href":"/contact"}]} />
|
|
}
|
|
}
|
|
`;
|
|
const expected = `component Demo {
|
|
view {
|
|
<Footer
|
|
items={footerItems}
|
|
options={{
|
|
"dense": true,
|
|
"theme": "public"
|
|
}}
|
|
links={[
|
|
{
|
|
"label": "Privacy",
|
|
"href": "/privacy"
|
|
},
|
|
{
|
|
"label": "Contact",
|
|
"href": "/contact"
|
|
}
|
|
]}
|
|
/>
|
|
}
|
|
}
|
|
`;
|
|
const options = { insertSpaces: true, tabSize: 2, multilineAttributes: true };
|
|
|
|
assert.equal(formatWrn(source, options), expected);
|
|
assert.equal(formatWrn(expected, options), expected);
|
|
});
|
|
|
|
test("formats public event declarations as individual props members", () => {
|
|
const source = `component Search {\nprops { value = "" @event search = function @event clear = function }\nview { <input/> }\n}\n`;
|
|
const formatted = formatWrn(source, { insertSpaces: true, tabSize: 2 });
|
|
|
|
assert.match(formatted, / {4}@event search = function/);
|
|
assert.match(formatted, / {4}@event clear = function/);
|
|
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2 }), formatted);
|
|
});
|
|
|
|
test("keeps long inline-closing tags idempotent after multiline expansion", () => {
|
|
const source = `component Status {
|
|
view {
|
|
<section class="overflow-hidden rounded-3xl border border-[var(--wrn-color-border,#e2e8f0)] bg-[var(--wrn-color-surface,#ffffff)]">
|
|
<span class="inline-flex items-center gap-2 rounded-full bg-[var(--wrn-color-surface-2,#f8fafc)] px-3 py-1.5 text-xs font-semibold">
|
|
<span class="h-2 w-2 animate-pulse rounded-full bg-[var(--wrn-color-primary,#059669)]"></span>Loading
|
|
</span>
|
|
</section>
|
|
}
|
|
}
|
|
`;
|
|
const options = { insertSpaces: true, tabSize: 2, printWidth: 100 };
|
|
const formatted = formatWrn(source, options);
|
|
|
|
assert.equal(formatWrn(formatted, options), formatted);
|
|
assert.match(formatted, /<\/span>Loading/);
|
|
});
|
|
|
|
test("formats inline elements with one attribute per line", () => {
|
|
const source = `component Button {
|
|
view {
|
|
<button type="button" class="primary" @click='save()'>Save</button>
|
|
}
|
|
}
|
|
`;
|
|
|
|
const expected = `component Button {
|
|
view {
|
|
<button
|
|
type="button"
|
|
class="primary"
|
|
@click='save()'
|
|
>
|
|
Save
|
|
</button>
|
|
}
|
|
}
|
|
`;
|
|
|
|
const options = {
|
|
insertSpaces: true,
|
|
tabSize: 2,
|
|
printWidth: 100,
|
|
multilineAttributes: true,
|
|
};
|
|
|
|
assert.equal(formatWrn(source, options), expected);
|
|
|
|
assert.equal(formatWrn(expected, options), expected);
|
|
});
|
|
|
|
test("formats self-closing component props", () => {
|
|
const source = `component Demo {
|
|
view {
|
|
<FeatureList items='{[{ label: "A" }]}' emptyLabel="No items" />
|
|
}
|
|
}
|
|
`;
|
|
|
|
const formatted = formatWrn(source, {
|
|
insertSpaces: true,
|
|
tabSize: 2,
|
|
printWidth: 100,
|
|
multilineAttributes: true,
|
|
});
|
|
|
|
assert.match(formatted, /<FeatureList\n/);
|
|
|
|
assert.match(formatted, /items='\{\[\{ label: "A" \}\]\}'/);
|
|
|
|
assert.match(formatted, /^\s*\/>$/m);
|
|
});
|
|
|
|
test("indents if and each blocks", () => {
|
|
const source = `page Demo {
|
|
view {
|
|
{#if open}
|
|
{#each items as item}
|
|
<span>{item.label}</span>
|
|
{:empty}
|
|
<p>Empty</p>
|
|
{/each}
|
|
{:else}
|
|
<p>Closed</p>
|
|
{/if}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const formatted = formatWrn(source, {
|
|
insertSpaces: true,
|
|
tabSize: 2,
|
|
multilineAttributes: true,
|
|
});
|
|
|
|
assert.match(formatted, /^ {6}\{#each items as item\}$/m);
|
|
|
|
assert.match(formatted, /^ {8}<span>/m);
|
|
|
|
assert.equal(
|
|
formatWrn(formatted, {
|
|
insertSpaces: true,
|
|
tabSize: 2,
|
|
multilineAttributes: true,
|
|
}),
|
|
formatted,
|
|
);
|
|
});
|
|
|
|
test("expands inline if blocks around HTML", () => {
|
|
const source = `component Button {
|
|
view {
|
|
<button>
|
|
{#if loading}<span class="wrn-spinner wrn-spinner--inline" aria-hidden="true"></span>{/if}
|
|
{#if icon}<span class="{icon}"></span>{:else}<span>Fallback</span>{/if}
|
|
</button>
|
|
}
|
|
}
|
|
`;
|
|
|
|
const expected = `component Button {
|
|
view {
|
|
<button>
|
|
{#if loading}
|
|
<span
|
|
class="wrn-spinner wrn-spinner--inline"
|
|
aria-hidden="true"
|
|
>
|
|
</span>
|
|
{/if}
|
|
{#if icon}
|
|
<span
|
|
class="{icon}"
|
|
>
|
|
</span>
|
|
{:else}
|
|
<span>Fallback</span>
|
|
{/if}
|
|
</button>
|
|
}
|
|
}
|
|
`;
|
|
|
|
const options = {
|
|
insertSpaces: true,
|
|
tabSize: 2,
|
|
printWidth: 100,
|
|
multilineAttributes: true,
|
|
};
|
|
|
|
assert.equal(formatWrn(source, options), expected);
|
|
assert.equal(formatWrn(expected, options), expected);
|
|
});
|
|
|
|
test("formats v0.6 grouped state and outputs blocks", () => {
|
|
const source = `component Demo {\nprops { title: string open: boolean = false }\nstate { count: number = 0 loading = false }\noutputs { confirm(payload: ConfirmPayload) cancel() }\nview { <div/> }\n}`;
|
|
const formatted = formatWrn(source, { tabSize: 2, insertSpaces: true });
|
|
assert.match(formatted, /\n {2}state \{\n {4}count: number = 0\n {4}loading = false\n {2}\}/);
|
|
assert.match(
|
|
formatted,
|
|
/\n {2}outputs \{\n {4}confirm\(payload: ConfirmPayload\)\n {4}cancel\(\)\n {2}\}/,
|
|
);
|
|
});
|
|
|
|
test("preserves preformatted WRN code examples", () => {
|
|
const source = `page Docs {
|
|
view {
|
|
<pre><code><span class="code-muted">// One project, one language</span>
|
|
<span class="code-key">page</span> ProductDashboard {
|
|
<span class="code-key">state</span> count: number = 0
|
|
<button @click='count++'>Ship {count}</button>
|
|
}</code></pre>
|
|
}
|
|
}
|
|
`;
|
|
const options = { insertSpaces: true, tabSize: 2, printWidth: 100 };
|
|
const formatted = formatWrn(source, options);
|
|
assert.match(
|
|
formatted,
|
|
/<pre><code><span class="code-muted">\/\/ One project, one language<\/span>\n<span class="code-key">page<\/span>/,
|
|
);
|
|
assert.equal(formatWrn(formatted, options), formatted);
|
|
});
|
|
|
|
test("keeps attribute-free opening tags intact when content is long", () => {
|
|
const source = `page Docs {
|
|
view {
|
|
<p>This documentation sentence is deliberately long enough to exceed the formatter print width without splitting the p opening delimiter.</p>
|
|
}
|
|
}
|
|
`;
|
|
const options = { insertSpaces: true, tabSize: 2, printWidth: 70 };
|
|
const formatted = formatWrn(source, options);
|
|
assert.match(formatted, /^ {4}<p>$/m);
|
|
assert.doesNotMatch(formatted, /^ {4}<p\n {4}>$/m);
|
|
assert.equal(formatWrn(formatted, options), formatted);
|
|
});
|
|
|
|
test("preserves balanced compact sibling markup without indentation drift", () => {
|
|
const source = `page Compact {
|
|
view {
|
|
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
|
|
}
|
|
}\n`;
|
|
const formatted = formatWrn(source, { tabSize: 2, printWidth: 80 });
|
|
assert.match(formatted, /<div class="strip"><span>Page<\/span><b>→<\/b><span>API<\/span><\/div>/);
|
|
assert.equal(formatWrn(formatted, { tabSize: 2, printWidth: 80 }), formatted);
|
|
});
|