mirror of
https://github.com/gromlab-ru/svg-sprites.git
synced 2026-07-22 04:40:17 +03:00
feat: добавить поддержку 20 framework exact modes
Добавлены exact modes: - vue@vite и vue@webpack - nuxt@vite и nuxt@webpack - svelte@vite, svelte@webpack и sveltekit@vite - angular@application и angular@webpack - astro@vite - solid@vite, solid@webpack и solid-start@vite - preact@vite и preact@webpack - qwik@vite - lit@vite и lit@webpack - alpine@vite и alpine@webpack Для каждого mode реализованы изолированный adapter, нативный framework-компонент, declarations, manifest, CSS и внешний asset URL. Добавлены production integration-стенды с генерацией, typecheck, сборкой и Playwright-проверкой рендера спрайта и Viewer. Обновлены Viewer, RU/EN-гайды, README, technical reference и AI skills. Итоговая матрица включает 29 exact modes. Проверки: - 48 unit-тестов - 29 integration E2E-тестов
This commit is contained in:
162
test/angular-astro-mode-contracts.test.mjs
Normal file
162
test/angular-astro-mode-contracts.test.mjs
Normal file
@@ -0,0 +1,162 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { createJiti } from 'jiti'
|
||||
|
||||
const jiti = createJiti(import.meta.url)
|
||||
|
||||
const [
|
||||
{ angularApplicationAdapter },
|
||||
{ angularWebpackAdapter },
|
||||
{ astroViteAdapter },
|
||||
] = await Promise.all([
|
||||
jiti.import('../src/modes/angular-application/adapter.ts'),
|
||||
jiti.import('../src/modes/angular-webpack/adapter.ts'),
|
||||
jiti.import('../src/modes/astro-vite/adapter.ts'),
|
||||
])
|
||||
|
||||
function createContext(mode) {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-framework-family-'))
|
||||
const iconsDir = path.join(rootDir, 'icons')
|
||||
const iconPath = path.join(iconsDir, 'check.svg')
|
||||
fs.mkdirSync(iconsDir)
|
||||
fs.writeFileSync(
|
||||
iconPath,
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
return {
|
||||
rootDir,
|
||||
config: {
|
||||
mode,
|
||||
name: 'app',
|
||||
description: 'Application icons',
|
||||
input: [iconsDir],
|
||||
transform: {
|
||||
removeSize: true,
|
||||
replaceColors: true,
|
||||
addTransition: true,
|
||||
},
|
||||
generatedNotice: false,
|
||||
},
|
||||
prepared: {
|
||||
folder: {
|
||||
name: 'app',
|
||||
format: 'stack',
|
||||
path: iconsDir,
|
||||
files: [iconPath],
|
||||
},
|
||||
iconNames: ['check'],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function filesByPath(plan) {
|
||||
return new Map(plan.files.map((file) => [file.path, String(file.content)]))
|
||||
}
|
||||
|
||||
test('Angular application adapter emits a standalone component for the application builder', async () => {
|
||||
const plan = await angularApplicationAdapter.generate(createContext('angular@application'))
|
||||
const files = filesByPath(plan)
|
||||
|
||||
assert.equal(angularApplicationAdapter.mode, 'angular@application')
|
||||
assert.equal(plan.result.target, 'application')
|
||||
assert.equal(plan.paths.entry, '.svg-sprite/index.ts')
|
||||
assert.deepEqual([...files.keys()].sort(), [
|
||||
'.svg-sprite/angular/angular-component.css',
|
||||
'.svg-sprite/angular/angular-component.d.ts',
|
||||
'.svg-sprite/angular/angular-component.ts',
|
||||
'.svg-sprite/assets.d.ts',
|
||||
'.svg-sprite/icon-data.d.ts',
|
||||
'.svg-sprite/icon-data.js',
|
||||
'.svg-sprite/index.d.ts',
|
||||
'.svg-sprite/index.ts',
|
||||
'.svg-sprite/sprite.svg',
|
||||
'.svg-sprite/svg-sprite.manifest.d.ts',
|
||||
'.svg-sprite/svg-sprite.manifest.js',
|
||||
])
|
||||
|
||||
const component = files.get('.svg-sprite/angular/angular-component.ts')
|
||||
const declarations = files.get('.svg-sprite/angular/angular-component.d.ts')
|
||||
const manifest = files.get('.svg-sprite/svg-sprite.manifest.js')
|
||||
assert.match(component, /@Component\(\{/)
|
||||
assert.match(component, /standalone: true/)
|
||||
assert.match(component, /export class AppIcon/)
|
||||
assert.match(component, /input\.required<AppIconName>\(\)/)
|
||||
assert.match(component, /reference path="\.\.\/assets\.d\.ts"/)
|
||||
assert.match(component, /import spriteUrl from '\.\.\/sprite\.svg'/)
|
||||
assert.match(declarations, /InputSignal<AppIconName>/)
|
||||
assert.match(declarations, /ɵɵComponentDeclaration/)
|
||||
assert.match(manifest, /import spriteUrl from '\.\/sprite\.svg'/)
|
||||
assert.match(manifest, /"mode": "angular@application"/)
|
||||
assert.match(manifest, /"framework": "angular"/)
|
||||
assert.doesNotMatch(component, /customElements|HTMLElement|attachShadow/)
|
||||
})
|
||||
|
||||
test('Angular Webpack adapter owns its Webpack asset contract', async () => {
|
||||
const plan = await angularWebpackAdapter.generate(createContext('angular@webpack'))
|
||||
const files = filesByPath(plan)
|
||||
const component = files.get('.svg-sprite/angular/angular-component.ts')
|
||||
const manifest = files.get('.svg-sprite/svg-sprite.manifest.js')
|
||||
|
||||
assert.equal(angularWebpackAdapter.mode, 'angular@webpack')
|
||||
assert.equal(plan.result.target, 'webpack')
|
||||
assert.equal(files.has('.svg-sprite/assets.d.ts'), false)
|
||||
assert.match(component, /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/)
|
||||
assert.match(manifest, /new URL\('\.\/sprite\.svg', import\.meta\.url\)\.href/)
|
||||
assert.match(manifest, /"mode": "angular@webpack"/)
|
||||
assert.doesNotMatch(component, /customElements|HTMLElement|attachShadow/)
|
||||
})
|
||||
|
||||
test('Astro Vite adapter emits an Astro component with JavaScript frontmatter', async () => {
|
||||
const plan = await astroViteAdapter.generate(createContext('astro@vite'))
|
||||
const files = filesByPath(plan)
|
||||
|
||||
assert.equal(astroViteAdapter.mode, 'astro@vite')
|
||||
assert.equal(plan.result.target, 'vite')
|
||||
assert.equal(plan.paths.entry, '.svg-sprite/index.js')
|
||||
assert.deepEqual([...files.keys()].sort(), [
|
||||
'.svg-sprite/astro/astro-component.astro',
|
||||
'.svg-sprite/astro/astro-component.astro.d.ts',
|
||||
'.svg-sprite/astro/astro-component.css',
|
||||
'.svg-sprite/icon-data.d.ts',
|
||||
'.svg-sprite/icon-data.js',
|
||||
'.svg-sprite/index.d.ts',
|
||||
'.svg-sprite/index.js',
|
||||
'.svg-sprite/sprite.svg',
|
||||
'.svg-sprite/svg-sprite.manifest.d.ts',
|
||||
'.svg-sprite/svg-sprite.manifest.js',
|
||||
])
|
||||
|
||||
const component = files.get('.svg-sprite/astro/astro-component.astro')
|
||||
const declarations = files.get('.svg-sprite/astro/astro-component.astro.d.ts')
|
||||
const manifest = files.get('.svg-sprite/svg-sprite.manifest.js')
|
||||
assert.match(component, /^---\n/)
|
||||
assert.match(component, /sprite\.svg\?no-inline/)
|
||||
assert.match(component, /Astro\.props/)
|
||||
assert.doesNotMatch(component, /lang=["']ts|interface Props|customElements|HTMLElement|attachShadow/)
|
||||
assert.match(declarations, /HTMLAttributes<'svg'>/)
|
||||
assert.match(declarations, /export type AppIconProps/)
|
||||
assert.match(manifest, /sprite\.svg\?no-inline/)
|
||||
assert.match(manifest, /"framework": "astro"/)
|
||||
assert.match(manifest, /"componentName": "AppIcon"/)
|
||||
assert.match(manifest, /"mode": "astro@vite"/)
|
||||
})
|
||||
|
||||
test('framework-family adapters have no horizontal mode imports', () => {
|
||||
for (const modeDirectory of ['angular-application', 'angular-webpack', 'astro-vite']) {
|
||||
for (const fileName of ['adapter.ts', 'output.ts']) {
|
||||
const source = fs.readFileSync(
|
||||
path.resolve(import.meta.dirname, '..', 'src', 'modes', modeDirectory, fileName),
|
||||
'utf8',
|
||||
)
|
||||
const imports = [...source.matchAll(/^import .* from ['"]([^'"]+)['"]$/gm)]
|
||||
for (const [, specifier] of imports) {
|
||||
if (specifier.startsWith('../')) assert.match(specifier, /^\.\.\/\.\.\//)
|
||||
assert.doesNotMatch(specifier, /(?:^|\/)modes\//)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -11,6 +11,26 @@ const guideModes = new Map([
|
||||
['standalone-webpack.md', 'standalone@webpack'],
|
||||
['react-vite.md', 'react@vite'],
|
||||
['react-webpack.md', 'react@webpack'],
|
||||
['vue-vite.md', 'vue@vite'],
|
||||
['vue-webpack.md', 'vue@webpack'],
|
||||
['nuxt-vite.md', 'nuxt@vite'],
|
||||
['nuxt-webpack.md', 'nuxt@webpack'],
|
||||
['svelte-vite.md', 'svelte@vite'],
|
||||
['svelte-webpack.md', 'svelte@webpack'],
|
||||
['sveltekit-vite.md', 'sveltekit@vite'],
|
||||
['angular-application.md', 'angular@application'],
|
||||
['angular-webpack.md', 'angular@webpack'],
|
||||
['astro-vite.md', 'astro@vite'],
|
||||
['solid-vite.md', 'solid@vite'],
|
||||
['solid-webpack.md', 'solid@webpack'],
|
||||
['solid-start-vite.md', 'solid-start@vite'],
|
||||
['preact-vite.md', 'preact@vite'],
|
||||
['preact-webpack.md', 'preact@webpack'],
|
||||
['qwik-vite.md', 'qwik@vite'],
|
||||
['lit-vite.md', 'lit@vite'],
|
||||
['lit-webpack.md', 'lit@webpack'],
|
||||
['alpine-vite.md', 'alpine@vite'],
|
||||
['alpine-webpack.md', 'alpine@webpack'],
|
||||
['next-app-turbopack.md', 'next@app/turbopack'],
|
||||
['next-app-webpack.md', 'next@app/webpack'],
|
||||
['next-pages-turbopack.md', 'next@pages/turbopack'],
|
||||
@@ -35,6 +55,11 @@ const commandPatterns = {
|
||||
ru: /npx --yes @gromlab\/svg-sprites(?:\s|$)/,
|
||||
}
|
||||
|
||||
const spriteDirectoryPatterns = {
|
||||
en: /Choose a directory for the future SVG sprite/,
|
||||
ru: /Выберите каталог для будущего SVG-спрайта/,
|
||||
}
|
||||
|
||||
function markdownFiles(directory) {
|
||||
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const entryPath = path.join(directory, entry.name)
|
||||
@@ -67,10 +92,12 @@ test('exact-mode guides are reusable by docs and skills', () => {
|
||||
]
|
||||
assert.deepEqual(headings, expectedHeadings)
|
||||
assert.match(source, commandPatterns[language])
|
||||
assert.match(source, spriteDirectoryPatterns[language])
|
||||
if (mode !== 'standalone') {
|
||||
assert.match(source, /npm install --save-dev @gromlab\/svg-sprites/)
|
||||
}
|
||||
assert.doesNotMatch(source, /npx --yes[^\n]*@gromlab\/svg-sprites@/)
|
||||
assert.doesNotMatch(source, /check\.svg|icon="check"|#check|iconName: 'check'/)
|
||||
const modePattern = new RegExp(`"mode": "${mode.replaceAll('/', '\\/')}"`)
|
||||
assert.match(source, modePattern)
|
||||
assert.doesNotMatch(source, /\]\([^)]+\)/, `${language}/${file} must not depend on its location`)
|
||||
|
||||
90
test/lit-alpine-mode-contracts.test.mjs
Normal file
90
test/lit-alpine-mode-contracts.test.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const CASES = [
|
||||
{ mode: 'lit@vite', framework: 'lit', target: 'vite', folder: 'lit', runtime: 'lit-component.js' },
|
||||
{ mode: 'lit@webpack', framework: 'lit', target: 'webpack', folder: 'lit', runtime: 'lit-component.js' },
|
||||
{ mode: 'alpine@vite', framework: 'alpine', target: 'vite', folder: 'alpine', runtime: 'alpine-integration.js' },
|
||||
{ mode: 'alpine@webpack', framework: 'alpine', target: 'webpack', folder: 'alpine', runtime: 'alpine-integration.js' },
|
||||
]
|
||||
|
||||
test('keeps Lit and Alpine exact modes isolated', () => {
|
||||
for (const { mode } of CASES) {
|
||||
const modeDir = path.join(import.meta.dirname, '..', 'src', 'modes', mode.replace('@', '-'))
|
||||
const source = ['adapter.ts', 'output.ts']
|
||||
.map((file) => fs.readFileSync(path.join(modeDir, file), 'utf8'))
|
||||
.join('\n')
|
||||
|
||||
assert.doesNotMatch(source, /from ['"][^'"]*\.\.\/(?:lit|alpine)-(?:vite|webpack)/)
|
||||
assert.match(source, new RegExp(`const MODE = '${mode}'|mode: '${mode}'`))
|
||||
}
|
||||
})
|
||||
|
||||
test('generates isolated Lit and Alpine exact-mode contracts', async (t) => {
|
||||
for (const fixture of CASES) {
|
||||
await t.test(fixture.mode, async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), `svg-sprites-${fixture.framework}-`))
|
||||
const iconsDir = path.join(rootDir, 'icons')
|
||||
fs.mkdirSync(iconsDir)
|
||||
fs.writeFileSync(
|
||||
path.join(iconsDir, 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
assert.equal(isSpriteMode(fixture.mode), true)
|
||||
const result = await generateSprite(rootDir, {
|
||||
mode: fixture.mode,
|
||||
name: 'app',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const generatedDir = path.join(rootDir, '.svg-sprite')
|
||||
const runtimePath = path.join(generatedDir, fixture.folder, fixture.runtime)
|
||||
const runtime = fs.readFileSync(runtimePath, 'utf8')
|
||||
const runtimeTypes = fs.readFileSync(runtimePath.replace(/\.js$/, '.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
const manifestTypes = fs.readFileSync(path.join(generatedDir, 'svg-sprite.manifest.d.ts'), 'utf8')
|
||||
|
||||
assert.equal(result.mode, fixture.mode)
|
||||
assert.equal(result.target, fixture.target)
|
||||
assert.equal(result.iconCount, 1)
|
||||
assert.deepEqual(fs.readdirSync(path.join(generatedDir, fixture.folder)).sort(), [
|
||||
`${fixture.runtime.replace(/\.js$/, '')}.css`,
|
||||
`${fixture.runtime.replace(/\.js$/, '')}.d.ts`,
|
||||
fixture.runtime,
|
||||
])
|
||||
assert.match(manifest, new RegExp(`"framework": "${fixture.framework}"`))
|
||||
assert.match(manifest, new RegExp(`"mode": "${fixture.mode}"`))
|
||||
assert.match(manifestTypes, new RegExp(`mode: '${fixture.mode}'`))
|
||||
|
||||
if (fixture.framework === 'lit') {
|
||||
assert.match(runtime, /import \{ LitElement, html, nothing, unsafeCSS \} from 'lit'/)
|
||||
assert.match(runtime, /export class AppIcon extends LitElement/)
|
||||
assert.match(runtime, /export function defineAppIcon\(\)/)
|
||||
assert.match(runtimeTypes, /export declare class AppIcon extends LitElement/)
|
||||
assert.match(runtimeTypes, /"app-icon": AppIcon/)
|
||||
assert.match(manifest, /"componentName": "AppIcon"/)
|
||||
} else {
|
||||
assert.match(runtime, /export const appAlpinePlugin = \(Alpine\) =>/)
|
||||
assert.match(runtime, /Alpine\.directive\(appIconDirective/)
|
||||
assert.match(runtime, /Alpine\.magic\(appIconMagic/)
|
||||
assert.doesNotMatch(runtime, /customElements|extends HTMLElement|LitElement/)
|
||||
assert.match(runtimeTypes, /export type AppAlpineApi/)
|
||||
assert.match(runtimeTypes, /export declare const appAlpinePlugin: AppAlpinePluginType/)
|
||||
assert.doesNotMatch(runtimeTypes, /from ['"]alpinejs['"]|import\(['"]alpinejs['"]\)/)
|
||||
}
|
||||
|
||||
if (fixture.target === 'vite') {
|
||||
assert.match(runtime, /sprite\.svg\?no-inline/)
|
||||
assert.match(manifest, /sprite\.svg\?no-inline/)
|
||||
} else {
|
||||
assert.match(runtime, /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/)
|
||||
assert.match(manifest, /new URL\('\.\/sprite\.svg', import\.meta\.url\)\.href/)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
52
test/preact-modes.test.mjs
Normal file
52
test/preact-modes.test.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const MODES = [
|
||||
{ mode: 'preact@vite', target: 'vite', asset: /sprite\.svg\?no-inline/ },
|
||||
{ mode: 'preact@webpack', target: 'webpack', asset: /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/ },
|
||||
]
|
||||
|
||||
function fixture() {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-preact-'))
|
||||
fs.mkdirSync(path.join(rootDir, 'icons'))
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, 'icons', 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
return rootDir
|
||||
}
|
||||
|
||||
test('generates every isolated Preact family contract', async () => {
|
||||
for (const contract of MODES) {
|
||||
assert.equal(isSpriteMode(contract.mode), true)
|
||||
const result = await generateSprite(fixture(), {
|
||||
mode: contract.mode,
|
||||
name: 'app-icons',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const component = fs.readFileSync(path.join(result.generatedDir, 'preact', 'preact-component.js'), 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(result.generatedDir, 'preact', 'preact-component.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
|
||||
assert.equal(result.mode, contract.mode)
|
||||
assert.equal(result.target, contract.target)
|
||||
assert.deepEqual(fs.readdirSync(path.join(result.generatedDir, 'preact')).sort(), [
|
||||
'preact-component.d.ts',
|
||||
'preact-component.js',
|
||||
'preact-component.module.css',
|
||||
])
|
||||
assert.match(component, /import \{ h \} from 'preact'/)
|
||||
assert.match(component, /export const AppIconsIcon/)
|
||||
assert.match(component, contract.asset)
|
||||
assert.match(componentTypes, /FunctionalComponent<AppIconsIconProps>/)
|
||||
assert.match(manifest, /"framework": "preact"/)
|
||||
assert.match(manifest, new RegExp(`"mode": ${JSON.stringify(contract.mode)}`))
|
||||
assert.doesNotMatch(component, /customElements|HTMLElement|gromlab-sprite-viewer/)
|
||||
assert.equal(fs.existsSync(path.join(result.generatedDir, 'preact', 'preact-component.tsx')), false)
|
||||
}
|
||||
})
|
||||
43
test/qwik-vite-mode.test.mjs
Normal file
43
test/qwik-vite-mode.test.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
test('generates an isolated SSR-safe Qwik Vite contract', async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-qwik-'))
|
||||
fs.mkdirSync(path.join(rootDir, 'icons'))
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, 'icons', 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
assert.equal(isSpriteMode('qwik@vite'), true)
|
||||
const result = await generateSprite(rootDir, {
|
||||
mode: 'qwik@vite',
|
||||
name: 'app-icons',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const component = fs.readFileSync(path.join(result.generatedDir, 'qwik', 'qwik-component.jsx'), 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(result.generatedDir, 'qwik', 'qwik-component.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
|
||||
assert.equal(result.mode, 'qwik@vite')
|
||||
assert.equal(result.target, 'vite')
|
||||
assert.deepEqual(fs.readdirSync(path.join(result.generatedDir, 'qwik')).sort(), [
|
||||
'qwik-component.d.ts',
|
||||
'qwik-component.jsx',
|
||||
'qwik-component.module.css',
|
||||
])
|
||||
assert.match(component, /import \{ component\$ \} from '@builder\.io\/qwik'/)
|
||||
assert.match(component, /sprite\.svg\?no-inline/)
|
||||
assert.match(component, /export const AppIconsIcon = component\$/)
|
||||
assert.match(componentTypes, /Component<AppIconsIconProps>/)
|
||||
assert.match(manifest, /"framework": "qwik"/)
|
||||
assert.match(manifest, /"ssr": true/)
|
||||
assert.match(manifest, /"mode": "qwik@vite"/)
|
||||
assert.doesNotMatch(component, /\b(?:window|document|navigator|customElements|HTMLElement)\b/)
|
||||
assert.equal(fs.existsSync(path.join(result.generatedDir, 'qwik', 'qwik-component.tsx')), false)
|
||||
})
|
||||
59
test/solid-modes.test.mjs
Normal file
59
test/solid-modes.test.mjs
Normal file
@@ -0,0 +1,59 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const MODES = [
|
||||
{ mode: 'solid@vite', target: 'vite', asset: /sprite\.svg\?no-inline/ },
|
||||
{ mode: 'solid@webpack', target: 'webpack', asset: /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/ },
|
||||
{ mode: 'solid-start@vite', target: 'vite', asset: /sprite\.svg\?no-inline/, ssr: true },
|
||||
]
|
||||
|
||||
function fixture() {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-solid-'))
|
||||
fs.mkdirSync(path.join(rootDir, 'icons'))
|
||||
fs.writeFileSync(
|
||||
path.join(rootDir, 'icons', 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
return rootDir
|
||||
}
|
||||
|
||||
test('generates every isolated Solid family contract', async () => {
|
||||
for (const contract of MODES) {
|
||||
assert.equal(isSpriteMode(contract.mode), true)
|
||||
const result = await generateSprite(fixture(), {
|
||||
mode: contract.mode,
|
||||
name: 'app-icons',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const componentPath = path.join(result.generatedDir, 'solid', 'solid-component.jsx')
|
||||
const component = fs.readFileSync(componentPath, 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(result.generatedDir, 'solid', 'solid-component.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
|
||||
assert.equal(result.mode, contract.mode)
|
||||
assert.equal(result.target, contract.target)
|
||||
assert.deepEqual(fs.readdirSync(path.join(result.generatedDir, 'solid')).sort(), [
|
||||
'solid-component.d.ts',
|
||||
'solid-component.jsx',
|
||||
'solid-component.module.css',
|
||||
])
|
||||
assert.match(component, /import \{ Show, splitProps \} from 'solid-js'/)
|
||||
assert.match(component, /export const AppIconsIcon/)
|
||||
assert.match(component, contract.asset)
|
||||
assert.match(componentTypes, /Component<AppIconsIconProps>/)
|
||||
assert.match(manifest, /"framework": "solid(?:-start)?"/)
|
||||
assert.match(manifest, new RegExp(`"mode": ${JSON.stringify(contract.mode)}`))
|
||||
assert.doesNotMatch(component, /customElements|HTMLElement|gromlab-sprite-viewer/)
|
||||
assert.equal(fs.existsSync(path.join(result.generatedDir, 'solid', 'solid-component.tsx')), false)
|
||||
|
||||
if (contract.ssr) {
|
||||
assert.match(manifest, /"ssr": true/)
|
||||
assert.doesNotMatch(component, /\b(?:window|document|navigator|customElements)\b/)
|
||||
}
|
||||
}
|
||||
})
|
||||
69
test/svelte-modes.test.mjs
Normal file
69
test/svelte-modes.test.mjs
Normal file
@@ -0,0 +1,69 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const GENERATED_FILES = [
|
||||
'icon-data.d.ts',
|
||||
'icon-data.js',
|
||||
'index.d.ts',
|
||||
'index.js',
|
||||
'sprite.svg',
|
||||
'svelte',
|
||||
'svg-sprite.manifest.d.ts',
|
||||
'svg-sprite.manifest.js',
|
||||
]
|
||||
|
||||
test('generates isolated native Svelte contracts for every Svelte exact mode', async () => {
|
||||
const modes = [
|
||||
{ mode: 'svelte@vite', target: 'vite', url: /sprite\.svg\?no-inline/ },
|
||||
{ mode: 'svelte@webpack', target: 'webpack', url: /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/ },
|
||||
{ mode: 'sveltekit@vite', target: 'vite', url: /sprite\.svg\?no-inline/ },
|
||||
]
|
||||
|
||||
for (const expected of modes) {
|
||||
assert.equal(isSpriteMode(expected.mode), true)
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-svelte-'))
|
||||
const iconsDir = path.join(rootDir, 'icons')
|
||||
fs.mkdirSync(iconsDir)
|
||||
fs.writeFileSync(
|
||||
path.join(iconsDir, 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
const result = await generateSprite(rootDir, {
|
||||
mode: expected.mode,
|
||||
name: 'app',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const generatedDir = path.join(rootDir, '.svg-sprite')
|
||||
const component = fs.readFileSync(path.join(generatedDir, 'svelte', 'svelte-component.svelte'), 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(generatedDir, 'svelte', 'svelte-component.svelte.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
const manifestTypes = fs.readFileSync(path.join(generatedDir, 'svg-sprite.manifest.d.ts'), 'utf8')
|
||||
|
||||
assert.equal(result.mode, expected.mode)
|
||||
assert.equal(result.target, expected.target)
|
||||
assert.equal(result.iconCount, 1)
|
||||
assert.deepEqual(fs.readdirSync(generatedDir).sort(), GENERATED_FILES)
|
||||
assert.deepEqual(fs.readdirSync(path.join(generatedDir, 'svelte')).sort(), [
|
||||
'svelte-component.svelte',
|
||||
'svelte-component.svelte.d.ts',
|
||||
])
|
||||
assert.match(component, /<script>/)
|
||||
assert.match(component, /\$props\(\)/)
|
||||
assert.match(component, expected.url)
|
||||
assert.doesNotMatch(component, /lang=["']ts["']/)
|
||||
assert.doesNotMatch(component, /window|document|customElements/)
|
||||
assert.match(componentTypes, /Component<AppIconProps>/)
|
||||
assert.match(componentTypes, /SVGAttributes<SVGSVGElement>/)
|
||||
assert.match(manifest, /"framework": "svelte"/)
|
||||
assert.match(manifest, /"componentName": "AppIcon"/)
|
||||
assert.match(manifest, new RegExp(`"mode": ${JSON.stringify(expected.mode)}`))
|
||||
assert.match(manifestTypes, new RegExp(`mode: ${JSON.stringify(expected.mode).replaceAll('"', "'")}`))
|
||||
assert.doesNotMatch(component, /from ['"].*modes\//)
|
||||
}
|
||||
})
|
||||
85
test/vue-nuxt-mode-family.test.mjs
Normal file
85
test/vue-nuxt-mode-family.test.mjs
Normal file
@@ -0,0 +1,85 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const GENERATED_FILES = [
|
||||
'icon-data.d.ts',
|
||||
'icon-data.js',
|
||||
'index.d.ts',
|
||||
'index.js',
|
||||
'sprite.svg',
|
||||
'svg-sprite.manifest.d.ts',
|
||||
'svg-sprite.manifest.js',
|
||||
'vue',
|
||||
]
|
||||
|
||||
const MODES = [
|
||||
{
|
||||
mode: 'vue@webpack',
|
||||
target: 'webpack',
|
||||
componentAsset: /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/,
|
||||
manifestAsset: /new URL\('\.\/sprite\.svg', import\.meta\.url\)\.href/,
|
||||
},
|
||||
{
|
||||
mode: 'nuxt@vite',
|
||||
target: 'vite',
|
||||
componentAsset: /sprite\.svg\?no-inline/,
|
||||
manifestAsset: /sprite\.svg\?no-inline/,
|
||||
},
|
||||
{
|
||||
mode: 'nuxt@webpack',
|
||||
target: 'webpack',
|
||||
componentAsset: /import spriteUrl from '\.\.\/sprite\.svg'/,
|
||||
manifestAsset: /import spriteUrl from '\.\/sprite\.svg'/,
|
||||
},
|
||||
]
|
||||
|
||||
test('generates isolated Vue-family contracts for Vue Webpack and Nuxt', async () => {
|
||||
for (const contract of MODES) {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), `svg-sprites-${contract.mode.replace('@', '-')}-`))
|
||||
const iconsDir = path.join(rootDir, 'icons')
|
||||
fs.mkdirSync(iconsDir)
|
||||
fs.writeFileSync(
|
||||
path.join(iconsDir, 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
assert.equal(isSpriteMode(contract.mode), true)
|
||||
const result = await generateSprite(rootDir, {
|
||||
mode: contract.mode,
|
||||
name: 'app',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const generatedDir = path.join(rootDir, '.svg-sprite')
|
||||
const component = fs.readFileSync(path.join(generatedDir, 'vue', 'vue-component.js'), 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(generatedDir, 'vue', 'vue-component.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
const manifestTypes = fs.readFileSync(path.join(generatedDir, 'svg-sprite.manifest.d.ts'), 'utf8')
|
||||
|
||||
assert.equal(result.mode, contract.mode)
|
||||
assert.equal(result.target, contract.target)
|
||||
assert.deepEqual(fs.readdirSync(generatedDir).sort(), GENERATED_FILES)
|
||||
assert.deepEqual(fs.readdirSync(path.join(generatedDir, 'vue')).sort(), [
|
||||
'vue-component.d.ts',
|
||||
'vue-component.js',
|
||||
'vue-component.module.css',
|
||||
])
|
||||
assert.match(component, /import \{ defineComponent, h \} from 'vue'/)
|
||||
assert.match(component, /export const AppIcon = defineComponent/)
|
||||
assert.match(component, contract.componentAsset)
|
||||
assert.match(componentTypes, /DefineComponent<AppIconProps>/)
|
||||
assert.match(componentTypes, /--icon-color-/)
|
||||
assert.match(manifest, /"framework": "vue"/)
|
||||
assert.match(manifest, /"componentName": "AppIcon"/)
|
||||
assert.match(manifest, new RegExp(`"mode": ${JSON.stringify(contract.mode)}`))
|
||||
assert.match(manifest, contract.manifestAsset)
|
||||
assert.match(manifestTypes, new RegExp(`mode: ${JSON.stringify(contract.mode).replaceAll('"', "'")}`))
|
||||
assert.doesNotMatch(component, /\b(?:window|document|customElements|HTMLElement)\b/)
|
||||
assert.doesNotMatch(manifest, /\b(?:window|document|customElements|HTMLElement)\b/)
|
||||
assert.doesNotMatch(component, /from '@gromlab\/svg-sprites/)
|
||||
}
|
||||
})
|
||||
62
test/vue-vite-mode.test.mjs
Normal file
62
test/vue-vite-mode.test.mjs
Normal file
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
|
||||
import { generateSprite, isSpriteMode } from '../dist/index.js'
|
||||
|
||||
const GENERATED_FILES = [
|
||||
'icon-data.d.ts',
|
||||
'icon-data.js',
|
||||
'index.d.ts',
|
||||
'index.js',
|
||||
'sprite.svg',
|
||||
'svg-sprite.manifest.d.ts',
|
||||
'svg-sprite.manifest.js',
|
||||
'vue',
|
||||
]
|
||||
|
||||
test('generates the isolated Vue Vite contract', async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-vue-vite-'))
|
||||
const iconsDir = path.join(rootDir, 'icons')
|
||||
fs.mkdirSync(iconsDir)
|
||||
fs.writeFileSync(
|
||||
path.join(iconsDir, 'check.svg'),
|
||||
'<svg viewBox="0 0 24 24"><path fill="#16a34a" d="M2 12l6 6L22 4" /></svg>',
|
||||
)
|
||||
|
||||
assert.equal(isSpriteMode('vue@vite'), true)
|
||||
const result = await generateSprite(rootDir, {
|
||||
mode: 'vue@vite',
|
||||
name: 'app',
|
||||
generatedNotice: false,
|
||||
})
|
||||
const generatedDir = path.join(rootDir, '.svg-sprite')
|
||||
|
||||
assert.equal(result.mode, 'vue@vite')
|
||||
assert.equal(result.target, 'vite')
|
||||
assert.equal(result.iconCount, 1)
|
||||
assert.deepEqual(fs.readdirSync(generatedDir).sort(), GENERATED_FILES)
|
||||
assert.deepEqual(fs.readdirSync(path.join(generatedDir, 'vue')).sort(), [
|
||||
'vue-component.d.ts',
|
||||
'vue-component.js',
|
||||
'vue-component.module.css',
|
||||
])
|
||||
|
||||
const component = fs.readFileSync(path.join(generatedDir, 'vue', 'vue-component.js'), 'utf8')
|
||||
const componentTypes = fs.readFileSync(path.join(generatedDir, 'vue', 'vue-component.d.ts'), 'utf8')
|
||||
const manifest = fs.readFileSync(result.manifestPath, 'utf8')
|
||||
const manifestTypes = fs.readFileSync(path.join(generatedDir, 'svg-sprite.manifest.d.ts'), 'utf8')
|
||||
|
||||
assert.match(component, /import \{ defineComponent, h \} from 'vue'/)
|
||||
assert.match(component, /export const AppIcon = defineComponent/)
|
||||
assert.match(component, /sprite\.svg\?no-inline/)
|
||||
assert.match(componentTypes, /DefineComponent<AppIconProps>/)
|
||||
assert.match(componentTypes, /--icon-color-/)
|
||||
assert.match(manifest, /"framework": "vue"/)
|
||||
assert.match(manifest, /"componentName": "AppIcon"/)
|
||||
assert.match(manifest, /"mode": "vue@vite"/)
|
||||
assert.match(manifestTypes, /mode: 'vue@vite'/)
|
||||
assert.doesNotMatch(component, /from '@gromlab\/svg-sprites/)
|
||||
})
|
||||
Reference in New Issue
Block a user