import assert from 'node:assert/strict' import fs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import test from 'node:test' import { applyIconSet, assignFamilies, buildIconSet, normalizeExportName, readIconSource, validatePathData, } from './lib.mts' async function fixture(t, lane, name, svg) { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'icon-codegen-')) t.after(() => fs.rm(root, {recursive: true, force: true})) await fs.mkdir(path.join(root, lane), {recursive: true}) await fs.writeFile(path.join(root, lane, name), svg) return {relative: path.join(lane, name), root} } test('validates SVG path grammar', () => { validatePathData('M0 0h2v2H0Z') validatePathData('M0,0 2,2') assert.throws(() => validatePathData('M0 0 L nope'), /L has no arguments/) assert.throws(() => validatePathData('M0'), /too few arguments/) assert.throws(() => validatePathData('M0 0A1 1 0 2 0 3 4'), /large-arc-flag/) assert.throws(() => validatePathData('M0,,0'), /misplaced comma/) assert.throws(() => validatePathData('M,0 0'), /misplaced comma/) assert.throws(() => validatePathData('M0 0,'), /misplaced comma/) }) test('normalizes accidental legacy export spelling', () => { assert.equal( normalizeExportName('Envelope_Open_Stoke2_Corner0_Rounded'), 'EnvelopeOpen_Stroke2_Corner0_Rounded', ) }) test('groups families deterministically', () => { const icons = [ {exportName: 'CircleCheck_Stroke2_Corner0_Rounded', namespace: ''}, {exportName: 'CircleInfo_Stroke2_Corner0_Rounded', namespace: ''}, {exportName: 'MagnifyingGlass_Stroke2_Corner0_Rounded', namespace: ''}, {exportName: 'MagnifyingGlassX_Stroke2_Corner0_Rounded', namespace: ''}, ] assignFamilies(icons) assert.deepEqual(icons.map(icon => icon.family), [ 'Circle', 'Circle', 'MagnifyingGlass', 'MagnifyingGlass', ]) }) test('accepts fill and supported stroke icons', async t => { const fill = await fixture( t, 'ui', 'Fill_Filled_Corner0_Rounded.svg', '', ) assert.equal((await readIconSource(fill.root, fill.relative)).description.strokeWidth, 0) const stroke = await fixture( t, 'ui', 'StrokeIcon_Stroke2_Corner0_Rounded.svg', '', ) assert.equal((await readIconSource(stroke.root, stroke.relative)).description.strokeWidth, 1.5) const nonstandard = await fixture( t, 'ui', 'Nonstandard_Stroke2_Corner0_Rounded.svg', '', ) assert.deepEqual((await readIconSource(nonstandard.root, nonstandard.relative)).warnings, [ `${nonstandard.relative}: non-standard viewBox 0 0 20 18; expected 24x24 or 64x64`, ]) }) test('rejects malformed and suspicious SVG input', async t => { const malformed = await fixture(t, 'ui', 'Bad.svg', '') await assert.rejects(readIconSource(malformed.root, malformed.relative), /malformed SVG/) const noViewBox = await fixture(t, 'ui', 'NoViewBox.svg', '') await assert.rejects(readIconSource(noViewBox.root, noViewBox.relative), /missing viewBox/) const invalidPath = await fixture( t, 'ui', 'InvalidPath.svg', '', ) await assert.rejects(readIconSource(invalidPath.root, invalidPath.relative), /L has no arguments/) const external = await fixture( t, 'ui', 'External.svg', '', ) await assert.rejects(readIconSource(external.root, external.relative), /unsupported /) const script = await fixture( t, 'custom', 'Raw.svg', '', ) await assert.rejects(readIconSource(script.root, script.relative), /unsupported