mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-02-15 01:02:48 +00:00
refactor(package/ui): eslint config it4c update (#9233)
This commit is contained in:
parent
72714f58a6
commit
36e9ad6f80
@ -1,23 +1,10 @@
|
||||
// TODO: Update eslint-config-it4c to support ESLint 10 (currently incompatible)
|
||||
import css from '@eslint/css'
|
||||
import config, { vue3, vitest } from 'eslint-config-it4c'
|
||||
import config, { css, vue3, vitest } from 'eslint-config-it4c'
|
||||
import jsdocPlugin from 'eslint-plugin-jsdoc'
|
||||
import playwrightPlugin from 'eslint-plugin-playwright'
|
||||
import storybookPlugin from 'eslint-plugin-storybook'
|
||||
import vuejsAccessibilityPlugin from 'eslint-plugin-vuejs-accessibility'
|
||||
import { tailwind4 } from 'tailwind-csstree'
|
||||
|
||||
import type { Linter } from 'eslint'
|
||||
|
||||
/** Exclude CSS files from JS-focused config blocks (JS rules crash on CSS language) */
|
||||
function excludeCSS(configs: Linter.Config[]): Linter.Config[] {
|
||||
return configs.map((c) => {
|
||||
// Don't touch global-ignores-only blocks
|
||||
if (Object.keys(c).length === 1 && 'ignores' in c) return c
|
||||
return { ...c, ignores: [...(c.ignores ?? []), '**/*.css'] }
|
||||
})
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
@ -30,41 +17,13 @@ export default [
|
||||
'playwright-report/',
|
||||
],
|
||||
},
|
||||
...excludeCSS(config),
|
||||
...excludeCSS(vue3),
|
||||
...excludeCSS(vitest),
|
||||
{
|
||||
ignores: ['**/*.css'],
|
||||
rules: {
|
||||
// TODO: replace with alias
|
||||
'import-x/no-relative-parent-imports': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// CLI scripts - allow sync methods and console
|
||||
files: ['scripts/**/*.ts'],
|
||||
rules: {
|
||||
'n/shebang': 'off',
|
||||
'n/no-sync': 'off',
|
||||
'no-console': 'off',
|
||||
'security/detect-non-literal-fs-filename': 'off',
|
||||
},
|
||||
},
|
||||
...config,
|
||||
...vue3,
|
||||
...vitest,
|
||||
{
|
||||
// Playwright visual tests
|
||||
files: ['**/*.visual.spec.ts'],
|
||||
...playwrightPlugin.configs['flat/recommended'],
|
||||
rules: {
|
||||
'n/no-process-env': 'off',
|
||||
'vitest/require-hook': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// Playwright config
|
||||
files: ['playwright.config.ts'],
|
||||
rules: {
|
||||
'n/no-process-env': 'off',
|
||||
},
|
||||
},
|
||||
// Storybook files
|
||||
// eslint-disable-next-line import-x/no-named-as-default-member -- flat config access pattern
|
||||
@ -96,18 +55,12 @@ export default [
|
||||
...vuejsAccessibilityPlugin.configs.recommended.rules,
|
||||
},
|
||||
},
|
||||
...css,
|
||||
{
|
||||
// CSS files with Tailwind v4 syntax support
|
||||
// Extend CSS config with Tailwind v4 syntax support
|
||||
files: ['**/*.css'],
|
||||
plugins: { css },
|
||||
language: 'css/css',
|
||||
languageOptions: {
|
||||
customSyntax: tailwind4,
|
||||
},
|
||||
rules: {
|
||||
'css/no-empty-blocks': 'error',
|
||||
'css/no-duplicate-imports': 'error',
|
||||
'css/no-invalid-at-rules': 'error',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
411
packages/ui/package-lock.json
generated
411
packages/ui/package-lock.json
generated
@ -17,7 +17,6 @@
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.18.2",
|
||||
"@axe-core/playwright": "^4.11.1",
|
||||
"@eslint/css": "^0.14.1",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@size-limit/file": "^12.0.0",
|
||||
@ -29,7 +28,7 @@
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-it4c": "^0.9.0",
|
||||
"eslint-config-it4c": "^0.11.2",
|
||||
"eslint-plugin-jsdoc": "^62.5.3",
|
||||
"eslint-plugin-playwright": "^2.5.1",
|
||||
"eslint-plugin-storybook": "^10.2.8",
|
||||
@ -1317,21 +1316,6 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/css": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/css/-/css-0.14.1.tgz",
|
||||
"integrity": "sha512-NXiteSacmpaXqgyIW3+GcNzexXyfC0kd+gig6WTjD4A74kBGJeNx1tV0Hxa0v7x0+mnIyKfGPhGNs1uhRFdh+w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/css-tree": "^3.6.6",
|
||||
"@eslint/plugin-kit": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/css-tree": {
|
||||
"version": "3.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-3.6.8.tgz",
|
||||
@ -4256,17 +4240,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
||||
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
|
||||
"integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/type-utils": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/type-utils": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@ -4279,7 +4263,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.55.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@ -4295,16 +4279,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
|
||||
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -4320,14 +4304,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
|
||||
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
|
||||
"integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.54.0",
|
||||
"@typescript-eslint/types": "^8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.55.0",
|
||||
"@typescript-eslint/types": "^8.55.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -4342,14 +4326,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
|
||||
"integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -4360,9 +4344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -4377,15 +4361,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
|
||||
"integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@ -4402,9 +4386,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
|
||||
"integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -4416,16 +4400,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
|
||||
"integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"@typescript-eslint/project-service": "8.55.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/visitor-keys": "8.55.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@ -4470,16 +4454,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
|
||||
"integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
"@typescript-eslint/scope-manager": "8.55.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -4494,13 +4478,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
|
||||
"integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/types": "8.55.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -4828,6 +4812,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/eslint-plugin": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.6.7.tgz",
|
||||
"integrity": "sha512-sd2QJirEscSQk3Pywtelbs7z8RQp1gyF5BfeZVtTHE8y3suyzbAA71NuT9z01uTRMHoCf5p6M2t2WYNJ7m5FlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "^8.55.0",
|
||||
"@typescript-eslint/utils": "^8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.57.0",
|
||||
"typescript": ">=5.0.0",
|
||||
"vitest": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
},
|
||||
"vitest": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
@ -7072,14 +7083,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-it4c": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-it4c/-/eslint-config-it4c-0.9.0.tgz",
|
||||
"integrity": "sha512-iPcSbv3/dmrMRidPSugJguJejeiPL41uW79i/faRX55p4s2t5XZO+WvDcJu+vil7wTtpz8LJGjwz23VvK+FycQ==",
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-it4c/-/eslint-config-it4c-0.11.2.tgz",
|
||||
"integrity": "sha512-2ZiGvcjQAQPUbvLns83WGvnFlVupsVFJF9erCFYbR2ktKDvkTxqBxQEh6b4d4CDOhIBqAabkphtBkmdOF6SYCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.6.0",
|
||||
"@eslint/css": "^0.7.0",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@graphql-eslint/eslint-plugin": "^4.4.0",
|
||||
"@vitest/eslint-plugin": "^1.6.7",
|
||||
"@vue/eslint-config-typescript": "^14.6.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
@ -7089,16 +7102,57 @@
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"eslint-plugin-vue": "^10.7.0",
|
||||
"eslint-plugin-yml": "^3.0.0",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"eslint-plugin-yml": "^3.1.2",
|
||||
"neostandard": "^0.12.2",
|
||||
"typescript-eslint": "^8.54.0"
|
||||
"typescript-eslint": "^8.55.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">= 9"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-it4c/node_modules/@eslint/core": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
|
||||
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-it4c/node_modules/@eslint/css": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/css/-/css-0.7.0.tgz",
|
||||
"integrity": "sha512-d6mo8etv4igrTGxgvWSgA5+TsppfObM/Xhlu8JWbkqNBiaJXztUNH45R1B4i1GL2PNIFMLREI3Kh9lTBi19l7g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.13.0",
|
||||
"@eslint/css-tree": "^3.3.3",
|
||||
"@eslint/plugin-kit": "^0.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-it4c/node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
|
||||
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.13.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
||||
@ -7607,189 +7661,10 @@
|
||||
"storybook": "^10.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz",
|
||||
"integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^7.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >= 20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"vitest": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"optional": true
|
||||
},
|
||||
"vitest": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
|
||||
"integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
"@typescript-eslint/visitor-keys": "7.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
|
||||
"integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
|
||||
"integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
"@typescript-eslint/visitor-keys": "7.18.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
|
||||
"integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "7.18.0",
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
"@typescript-eslint/typescript-estree": "7.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.56.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "7.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
|
||||
"integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/eslint-visitor-keys": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vitest/node_modules/ts-api-utils": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
||||
"integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "10.7.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.7.0.tgz",
|
||||
"integrity": "sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==",
|
||||
"version": "10.8.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.8.0.tgz",
|
||||
"integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -7806,7 +7681,7 @@
|
||||
"peerDependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"vue-eslint-parser": "^10.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@ -13323,16 +13198,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==",
|
||||
"version": "8.55.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
|
||||
"integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.55.0",
|
||||
"@typescript-eslint/parser": "8.55.0",
|
||||
"@typescript-eslint/typescript-estree": "8.55.0",
|
||||
"@typescript-eslint/utils": "8.55.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
"description": "Vue component library for ocelot.social - works with Vue 2.7+ and Vue 3",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"imports": {
|
||||
"#src/*": "./src/*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
@ -72,7 +75,6 @@
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.18.2",
|
||||
"@axe-core/playwright": "^4.11.1",
|
||||
"@eslint/css": "^0.14.1",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@size-limit/file": "^12.0.0",
|
||||
@ -84,7 +86,7 @@
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-it4c": "^0.9.0",
|
||||
"eslint-config-it4c": "^0.11.2",
|
||||
"eslint-plugin-jsdoc": "^62.5.3",
|
||||
"eslint-plugin-playwright": "^2.5.1",
|
||||
"eslint-plugin-storybook": "^10.2.8",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable n/no-process-env */
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Completeness checker for @ocelot-social/ui components
|
||||
*
|
||||
@ -13,7 +13,7 @@
|
||||
* Note: JSDoc comments on props are checked via ESLint (jsdoc/require-jsdoc)
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
|
||||
import { glob } from 'glob'
|
||||
@ -25,6 +25,17 @@ function toKebabCase(str: string): string {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
||||
}
|
||||
|
||||
/** Read file contents, returning null if the file does not exist */
|
||||
async function tryReadFile(path: string): Promise<string | null> {
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename -- path from glob, not user input
|
||||
return await readFile(path, 'utf-8')
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
interface CheckResult {
|
||||
component: string
|
||||
errors: string[]
|
||||
@ -35,7 +46,7 @@ const results: CheckResult[] = []
|
||||
let hasErrors = false
|
||||
|
||||
// Find all Vue components (excluding index files)
|
||||
const components = glob.sync('src/components/**/Os*.vue')
|
||||
const components = await glob('src/components/**/Os*.vue')
|
||||
|
||||
for (const componentPath of components) {
|
||||
const componentName = basename(componentPath, '.vue')
|
||||
@ -54,28 +65,32 @@ for (const componentPath of components) {
|
||||
warnings: [],
|
||||
}
|
||||
|
||||
// Read all files once (null = file does not exist)
|
||||
const [storyContent, visualTestContent, unitTestContent, variantsContent] = await Promise.all([
|
||||
tryReadFile(storyPath),
|
||||
tryReadFile(visualTestPath),
|
||||
tryReadFile(unitTestPath),
|
||||
tryReadFile(variantsPath),
|
||||
])
|
||||
|
||||
// Check 1: Story file exists
|
||||
if (!existsSync(storyPath)) {
|
||||
if (storyContent === null) {
|
||||
result.errors.push(`Missing story file: ${storyPath}`)
|
||||
}
|
||||
|
||||
// Check 2: Visual regression test file exists
|
||||
if (!existsSync(visualTestPath)) {
|
||||
if (visualTestContent === null) {
|
||||
result.errors.push(`Missing visual test file: ${visualTestPath}`)
|
||||
}
|
||||
|
||||
// Check 3: Visual tests include accessibility checks
|
||||
if (existsSync(visualTestPath)) {
|
||||
const visualTestContent = readFileSync(visualTestPath, 'utf-8')
|
||||
if (!visualTestContent.includes('checkA11y(')) {
|
||||
result.errors.push(`Missing checkA11y() calls in visual tests: ${visualTestPath}`)
|
||||
}
|
||||
if (visualTestContent !== null && !visualTestContent.includes('checkA11y(')) {
|
||||
result.errors.push(`Missing checkA11y() calls in visual tests: ${visualTestPath}`)
|
||||
}
|
||||
|
||||
// Check 4: Keyboard accessibility tests exist
|
||||
if (existsSync(unitTestPath)) {
|
||||
const unitTestContent = readFileSync(unitTestPath, 'utf-8')
|
||||
if (!unitTestContent.includes("describe('keyboard accessibility'")) {
|
||||
if (unitTestContent !== null) {
|
||||
if (!/describe\(\s*['"]keyboard accessibility['"]/.test(unitTestContent)) {
|
||||
result.errors.push(`Missing keyboard accessibility tests in: ${unitTestPath}`)
|
||||
}
|
||||
} else {
|
||||
@ -83,18 +98,14 @@ for (const componentPath of components) {
|
||||
}
|
||||
|
||||
// Check 5 & 6: Story and visual test coverage
|
||||
if (existsSync(storyPath) && existsSync(visualTestPath)) {
|
||||
const storyContent = readFileSync(storyPath, 'utf-8')
|
||||
const visualTestContent = readFileSync(visualTestPath, 'utf-8')
|
||||
|
||||
// Extract exported story names (e.g., "export const Primary: Story")
|
||||
if (storyContent !== null && visualTestContent !== null) {
|
||||
const storyExports = storyContent.matchAll(/export\s+const\s+(\w+):\s*Story/g)
|
||||
|
||||
for (const match of storyExports) {
|
||||
const storyName = match[1]
|
||||
if (storyName === 'Playground') continue
|
||||
const kebabName = toKebabCase(storyName)
|
||||
|
||||
// Check if this story is tested in visual tests (URL pattern: --story-name)
|
||||
if (!visualTestContent.includes(`--${kebabName}`)) {
|
||||
result.warnings.push(`Story "${storyName}" missing visual test (--${kebabName})`)
|
||||
}
|
||||
@ -102,29 +113,20 @@ for (const componentPath of components) {
|
||||
}
|
||||
|
||||
// Check 5: Variant values are demonstrated in stories
|
||||
if (existsSync(storyPath) && existsSync(variantsPath)) {
|
||||
const variantsContent = readFileSync(variantsPath, 'utf-8')
|
||||
const storyContent = readFileSync(storyPath, 'utf-8')
|
||||
|
||||
// Extract variants block
|
||||
if (storyContent !== null && variantsContent !== null) {
|
||||
const variantsBlockMatch = /variants:\s*\{([\s\S]*?)\n\s{4}\},/m.exec(variantsContent)
|
||||
|
||||
if (variantsBlockMatch) {
|
||||
const variantsBlock = variantsBlockMatch[1]
|
||||
|
||||
// Extract each variant type (variant, size, etc.)
|
||||
const variantTypeMatches = variantsBlock.matchAll(/^\s{6}(\w+):\s*\{([\s\S]*?)\n\s{6}\}/gm)
|
||||
|
||||
for (const match of variantTypeMatches) {
|
||||
const variantName = match[1]
|
||||
const variantValues = match[2]
|
||||
|
||||
// Extract individual values
|
||||
const valueMatches = variantValues.matchAll(/^\s+(\w+):\s*\[/gm)
|
||||
|
||||
for (const valueMatch of valueMatches) {
|
||||
const value = valueMatch[1]
|
||||
// Check if this value appears in stories (multiple patterns)
|
||||
const patterns = [
|
||||
`${variantName}="${value}"`,
|
||||
`${variantName}='${value}'`,
|
||||
@ -132,8 +134,7 @@ for (const componentPath of components) {
|
||||
`${variantName}: "${value}"`,
|
||||
]
|
||||
|
||||
const found = patterns.some((p) => storyContent.includes(p))
|
||||
if (!found) {
|
||||
if (!patterns.some((p) => storyContent.includes(p))) {
|
||||
result.warnings.push(`Variant "${variantName}=${value}" not demonstrated in story`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,12 +8,14 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
slots: { default: 'Click me' },
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toBe('Click me')
|
||||
})
|
||||
|
||||
describe('variant prop', () => {
|
||||
it('applies default variant classes by default', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
// Default variant with filled appearance
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-default)]')
|
||||
})
|
||||
@ -22,6 +24,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { variant: 'primary' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-primary)]')
|
||||
})
|
||||
|
||||
@ -29,6 +32,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { variant: 'danger' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-danger)]')
|
||||
})
|
||||
})
|
||||
@ -36,6 +40,7 @@ describe('osButton', () => {
|
||||
describe('appearance prop', () => {
|
||||
it('applies filled appearance by default', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
expect(wrapper.classes()).toContain('shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]')
|
||||
})
|
||||
|
||||
@ -43,6 +48,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { appearance: 'outline', variant: 'primary' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-transparent')
|
||||
expect(wrapper.classes()).toContain('border-[var(--color-primary)]')
|
||||
expect(wrapper.classes()).toContain('text-[var(--color-primary)]')
|
||||
@ -52,6 +58,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { appearance: 'ghost', variant: 'primary' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-transparent')
|
||||
expect(wrapper.classes()).toContain('text-[var(--color-primary)]')
|
||||
expect(wrapper.classes()).not.toContain('border-[var(--color-primary)]')
|
||||
@ -61,6 +68,7 @@ describe('osButton', () => {
|
||||
describe('size prop', () => {
|
||||
it('applies md size by default', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
expect(wrapper.classes()).toContain('h-[36px]')
|
||||
})
|
||||
|
||||
@ -68,6 +76,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { size: 'sm' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('h-[26px]')
|
||||
expect(wrapper.classes()).toContain('text-[12px]')
|
||||
})
|
||||
@ -76,6 +85,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { size: 'lg' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('h-12')
|
||||
})
|
||||
|
||||
@ -88,6 +98,7 @@ describe('osButton', () => {
|
||||
} as const
|
||||
for (const [size, expected] of Object.entries(sizes)) {
|
||||
const wrapper = mount(OsButton, { props: { size: size as keyof typeof sizes } })
|
||||
|
||||
expect(wrapper.classes()).toContain(expected)
|
||||
}
|
||||
})
|
||||
@ -97,6 +108,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { fullWidth: true },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('w-full')
|
||||
})
|
||||
|
||||
@ -104,6 +116,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
attrs: { class: 'my-custom-class' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('my-custom-class')
|
||||
})
|
||||
|
||||
@ -111,11 +124,13 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { disabled: true },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('defaults to type="button"', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
expect(wrapper.attributes('type')).toBe('button')
|
||||
})
|
||||
|
||||
@ -123,6 +138,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { type: 'submit' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('type')).toBe('submit')
|
||||
})
|
||||
|
||||
@ -130,6 +146,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { variant: 'danger' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('data-variant')).toBe('danger')
|
||||
})
|
||||
|
||||
@ -137,18 +154,21 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { appearance: 'outline' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('data-appearance')).toBe('outline')
|
||||
})
|
||||
|
||||
it('emits click event', async () => {
|
||||
const wrapper = mount(OsButton)
|
||||
await wrapper.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('click')).toHaveLength(1)
|
||||
})
|
||||
|
||||
describe('focus styles', () => {
|
||||
it('default variant has dashed outline focus style using currentColor', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
expect(wrapper.classes()).toContain('focus:outline-dashed')
|
||||
expect(wrapper.classes()).toContain('focus:outline-current')
|
||||
expect(wrapper.classes()).toContain('focus:outline-1')
|
||||
@ -158,6 +178,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { variant: 'primary' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('focus:outline-dashed')
|
||||
expect(wrapper.classes()).toContain('focus:outline-1')
|
||||
})
|
||||
@ -169,8 +190,9 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg data-testid="icon"></svg>' },
|
||||
})
|
||||
const iconWrapper = wrapper.find('.os-button__icon')
|
||||
expect(iconWrapper.exists()).toBeTruthy()
|
||||
expect(iconWrapper.find('[data-testid="icon"]').exists()).toBeTruthy()
|
||||
|
||||
expect(iconWrapper.exists()).toBe(true)
|
||||
expect(iconWrapper.find('[data-testid="icon"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders both icon and text', () => {
|
||||
@ -180,7 +202,8 @@ describe('osButton', () => {
|
||||
default: 'Save',
|
||||
},
|
||||
})
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBeTruthy()
|
||||
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('Save')
|
||||
})
|
||||
|
||||
@ -192,6 +215,7 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).toContain('gap-2')
|
||||
})
|
||||
|
||||
@ -204,6 +228,7 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).toContain('gap-1')
|
||||
expect(contentSpan.classes()).not.toContain('gap-2')
|
||||
})
|
||||
@ -213,6 +238,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).not.toContain('gap-2')
|
||||
})
|
||||
|
||||
@ -224,6 +250,7 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).not.toContain('gap-2')
|
||||
expect(wrapper.find('.os-button__icon').classes()).toContain('-mr-1')
|
||||
})
|
||||
@ -233,6 +260,7 @@ describe('osButton', () => {
|
||||
slots: { default: 'Click me' },
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).not.toContain('gap-2')
|
||||
})
|
||||
|
||||
@ -240,7 +268,8 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
slots: { default: 'Click me' },
|
||||
})
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBeFalsy()
|
||||
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBe(false)
|
||||
expect(wrapper.text()).toBe('Click me')
|
||||
})
|
||||
|
||||
@ -248,7 +277,8 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
})
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBeTruthy()
|
||||
|
||||
expect(wrapper.find('.os-button__icon').exists()).toBe(true)
|
||||
expect(wrapper.text()).toBe('')
|
||||
})
|
||||
})
|
||||
@ -260,6 +290,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-full')
|
||||
expect(wrapper.classes()).toContain('p-0')
|
||||
})
|
||||
@ -270,6 +301,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('w-[36px]')
|
||||
})
|
||||
|
||||
@ -279,6 +311,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('w-[26px]')
|
||||
})
|
||||
|
||||
@ -288,6 +321,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('w-12')
|
||||
})
|
||||
|
||||
@ -297,6 +331,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('w-14')
|
||||
})
|
||||
|
||||
@ -306,6 +341,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-full')
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-primary)]')
|
||||
})
|
||||
@ -316,6 +352,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-full')
|
||||
expect(wrapper.classes()).toContain('bg-transparent')
|
||||
})
|
||||
@ -327,6 +364,7 @@ describe('osButton', () => {
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
const iconWrapper = wrapper.find('.os-button__icon')
|
||||
|
||||
expect(iconWrapper.classes()).not.toContain('-ml-1')
|
||||
expect(iconWrapper.classes()).not.toContain('-mr-1')
|
||||
})
|
||||
@ -340,6 +378,7 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const contentSpan = wrapper.find('button > span')
|
||||
|
||||
expect(contentSpan.classes()).toContain('gap-1')
|
||||
expect(contentSpan.classes()).not.toContain('gap-2')
|
||||
})
|
||||
@ -349,6 +388,7 @@ describe('osButton', () => {
|
||||
props: { circle: false },
|
||||
slots: { default: 'Click me' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).not.toContain('rounded-full')
|
||||
expect(wrapper.classes()).not.toContain('p-0')
|
||||
})
|
||||
@ -360,8 +400,9 @@ describe('osButton', () => {
|
||||
props: { loading: true },
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBeTruthy()
|
||||
expect(wrapper.find('svg').exists()).toBeTruthy()
|
||||
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBe(true)
|
||||
expect(wrapper.find('svg').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('disables button when loading=true', () => {
|
||||
@ -369,6 +410,7 @@ describe('osButton', () => {
|
||||
props: { loading: true },
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
@ -377,6 +419,7 @@ describe('osButton', () => {
|
||||
props: { loading: true },
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('aria-busy')).toBe('true')
|
||||
})
|
||||
|
||||
@ -386,6 +429,7 @@ describe('osButton', () => {
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
const contentSpan = wrapper.find('span')
|
||||
|
||||
expect(contentSpan.classes()).not.toContain('opacity-0')
|
||||
expect(wrapper.text()).toContain('Save')
|
||||
})
|
||||
@ -394,13 +438,15 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBeFalsy()
|
||||
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not set aria-busy when not loading', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('aria-busy')).toBeUndefined()
|
||||
})
|
||||
|
||||
@ -409,6 +455,7 @@ describe('osButton', () => {
|
||||
props: { loading: true, disabled: true },
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
@ -418,6 +465,7 @@ describe('osButton', () => {
|
||||
slots: { default: 'Save' },
|
||||
})
|
||||
await wrapper.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('click')).toBeUndefined()
|
||||
})
|
||||
|
||||
@ -430,8 +478,9 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const iconWrapper = wrapper.find('.os-button__icon')
|
||||
expect(iconWrapper.exists()).toBeTruthy()
|
||||
expect(iconWrapper.find('.os-button__spinner').exists()).toBeTruthy()
|
||||
|
||||
expect(iconWrapper.exists()).toBe(true)
|
||||
expect(iconWrapper.find('.os-button__spinner').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps icon visible when loading with icon', () => {
|
||||
@ -443,6 +492,7 @@ describe('osButton', () => {
|
||||
},
|
||||
})
|
||||
const iconWrapper = wrapper.find('.os-button__icon')
|
||||
|
||||
expect(iconWrapper.classes()).not.toContain('[&>*]:invisible')
|
||||
})
|
||||
|
||||
@ -453,7 +503,8 @@ describe('osButton', () => {
|
||||
})
|
||||
// Spinner is a direct child of button, not inside content wrapper
|
||||
const spinner = wrapper.find('button > .os-button__spinner')
|
||||
expect(spinner.exists()).toBeTruthy()
|
||||
|
||||
expect(spinner.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('does not render button-level spinner when icon is present', () => {
|
||||
@ -466,7 +517,8 @@ describe('osButton', () => {
|
||||
})
|
||||
// No spinner as direct child of button — it's inside the icon wrapper
|
||||
const buttonSpinner = wrapper.find('button > .os-button__spinner')
|
||||
expect(buttonSpinner.exists()).toBeFalsy()
|
||||
|
||||
expect(buttonSpinner.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps icon visible and shows spinner for icon-only loading', () => {
|
||||
@ -475,9 +527,10 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg data-testid="icon"></svg>' },
|
||||
})
|
||||
const iconWrapper = wrapper.find('.os-button__icon')
|
||||
expect(iconWrapper.exists()).toBeTruthy()
|
||||
expect(iconWrapper.find('[data-testid="icon"]').exists()).toBeTruthy()
|
||||
expect(iconWrapper.find('.os-button__spinner').exists()).toBeTruthy()
|
||||
|
||||
expect(iconWrapper.exists()).toBe(true)
|
||||
expect(iconWrapper.find('[data-testid="icon"]').exists()).toBe(true)
|
||||
expect(iconWrapper.find('.os-button__spinner').exists()).toBe(true)
|
||||
expect(iconWrapper.classes()).not.toContain('[&>*]:invisible')
|
||||
})
|
||||
|
||||
@ -487,8 +540,9 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Add' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-full')
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBeTruthy()
|
||||
expect(wrapper.find('.os-button__spinner').exists()).toBe(true)
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
expect(wrapper.attributes('aria-busy')).toBe('true')
|
||||
})
|
||||
@ -497,12 +551,14 @@ describe('osButton', () => {
|
||||
describe('keyboard accessibility', () => {
|
||||
it('renders as native button element for keyboard support', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
// Native button elements have built-in Enter/Space key support
|
||||
expect((wrapper.element as HTMLElement).tagName).toBe('BUTTON')
|
||||
})
|
||||
|
||||
it('is focusable by default', () => {
|
||||
const wrapper = mount(OsButton)
|
||||
|
||||
// No tabindex=-1 means button is in natural tab order
|
||||
expect(wrapper.attributes('tabindex')).toBeUndefined()
|
||||
})
|
||||
@ -511,6 +567,7 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, {
|
||||
props: { disabled: true },
|
||||
})
|
||||
|
||||
// Disabled buttons have disabled attribute which browsers handle correctly
|
||||
expect(wrapper.attributes('disabled')).toBeDefined()
|
||||
})
|
||||
@ -520,6 +577,7 @@ describe('osButton', () => {
|
||||
slots: { icon: '<svg></svg>' },
|
||||
attrs: { 'aria-label': 'Close' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('aria-label')).toBe('Close')
|
||||
expect(wrapper.attributes('tabindex')).toBeUndefined()
|
||||
})
|
||||
@ -528,7 +586,9 @@ describe('osButton', () => {
|
||||
const wrapper = mount(OsButton, { attachTo: document.body })
|
||||
const button = wrapper.element as HTMLButtonElement
|
||||
button.focus()
|
||||
|
||||
expect(document.activeElement).toBe(button)
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
@ -38,6 +38,7 @@ test.describe('OsButton keyboard accessibility', () => {
|
||||
|
||||
const buttons = root.locator('button:not([disabled])')
|
||||
const count = await buttons.count()
|
||||
|
||||
expect(count).toBeGreaterThan(0)
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
@ -45,6 +46,7 @@ test.describe('OsButton keyboard accessibility', () => {
|
||||
await button.focus()
|
||||
const outline = await button.evaluate((el) => getComputedStyle(el).outlineStyle)
|
||||
const label = (await button.textContent()) ?? ''
|
||||
|
||||
expect(outline, `Button "${label}" must have visible focus outline`).not.toBe('none')
|
||||
}
|
||||
})
|
||||
@ -56,7 +58,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('all-variants.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -65,7 +69,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('all-sizes.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -74,7 +80,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('appearance-filled.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -83,7 +91,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('appearance-outline.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -92,7 +102,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('appearance-ghost.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -101,7 +113,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('all-appearances.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -110,7 +124,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('disabled.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -119,7 +135,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('full-width.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -128,7 +146,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('icon.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -137,7 +157,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('icon-only.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -146,7 +168,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('icon-sizes.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -155,7 +179,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('icon-appearances.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -164,7 +190,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex')).toHaveScreenshot('circle.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -173,7 +201,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('circle-sizes.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -182,7 +212,9 @@ test.describe('OsButton visual regression', () => {
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('circle-appearances.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
@ -200,7 +232,9 @@ test.describe('OsButton visual regression', () => {
|
||||
;(el as HTMLElement).style.animationPlayState = 'paused'
|
||||
})
|
||||
})
|
||||
|
||||
await expect(root.locator('.flex-col').first()).toHaveScreenshot('loading.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, getCurrentInstance, h, isVue2 } from 'vue-demi'
|
||||
|
||||
import { cn } from '../../utils'
|
||||
import { cn } from '#src/utils'
|
||||
|
||||
import { buttonVariants } from './button.variants'
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ describe('ocelotUI Plugin', () => {
|
||||
|
||||
it('registers only Os-prefixed components', () => {
|
||||
const mockApp = {
|
||||
component: vi.fn(),
|
||||
component: vi.fn<(name: string, component: unknown) => void>(),
|
||||
}
|
||||
|
||||
OcelotUI.install?.(mockApp as never)
|
||||
@ -28,23 +28,24 @@ describe('ocelotUI Plugin', () => {
|
||||
|
||||
it('does not register non-component exports', () => {
|
||||
const mockApp = {
|
||||
component: vi.fn(),
|
||||
component: vi.fn<(name: string, component: unknown) => void>(),
|
||||
}
|
||||
|
||||
OcelotUI.install?.(mockApp as never)
|
||||
|
||||
// buttonVariants should NOT be registered
|
||||
const callArgs = mockApp.component.mock.calls.map((call: unknown[]) => call[0])
|
||||
const callArgs = mockApp.component.mock.calls.map((call) => call[0])
|
||||
|
||||
expect(callArgs).not.toContain('buttonVariants')
|
||||
})
|
||||
|
||||
it('works without throwing', () => {
|
||||
const mockApp = {
|
||||
component: vi.fn(),
|
||||
component: vi.fn<(name: string, component: unknown) => void>(),
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
OcelotUI.install?.(mockApp as never)
|
||||
}).not.toThrow()
|
||||
}).not.toThrowError()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { describe, it, expect, vi, afterEach, expectTypeOf } from 'vitest'
|
||||
|
||||
import { ocelotPreset, requiredCssVariables, validateCssVariables } from './tailwind.preset'
|
||||
|
||||
@ -13,26 +13,27 @@ describe('tailwind.preset', () => {
|
||||
|
||||
describe('requiredCssVariables', () => {
|
||||
it('exports an array', () => {
|
||||
expect(Array.isArray(requiredCssVariables)).toBeTruthy()
|
||||
expect(Array.isArray(requiredCssVariables)).toBe(true)
|
||||
})
|
||||
|
||||
it('contains only strings', () => {
|
||||
for (const variable of requiredCssVariables) {
|
||||
expect(typeof variable).toBe('string')
|
||||
expectTypeOf(variable).toBeString()
|
||||
}
|
||||
})
|
||||
|
||||
it('all variables start with --', () => {
|
||||
// This test validates the format constraint.
|
||||
for (const variable of requiredCssVariables) {
|
||||
expect(variable.startsWith('--')).toBeTruthy()
|
||||
expect(variable.startsWith('--')).toBe(true)
|
||||
}
|
||||
|
||||
// Ensure test runs even with empty array
|
||||
expect(requiredCssVariables.every((v) => v.startsWith('--'))).toBeTruthy()
|
||||
expect(requiredCssVariables.every((v) => v.startsWith('--'))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('validateCssVariables', () => {
|
||||
describe(validateCssVariables, () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
vi.restoreAllMocks()
|
||||
@ -43,12 +44,12 @@ describe('tailwind.preset', () => {
|
||||
|
||||
expect(() => {
|
||||
validateCssVariables()
|
||||
}).not.toThrow()
|
||||
}).not.toThrowError()
|
||||
})
|
||||
|
||||
it('does not warn when all variables are defined', () => {
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const mockGetPropertyValue = vi.fn().mockReturnValue('some-value')
|
||||
const mockGetPropertyValue = vi.fn<(prop: string) => string>().mockReturnValue('some-value')
|
||||
|
||||
vi.stubGlobal('window', {})
|
||||
vi.stubGlobal('document', {
|
||||
@ -65,7 +66,7 @@ describe('tailwind.preset', () => {
|
||||
|
||||
it('warns when variables are missing', () => {
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const mockGetPropertyValue = vi.fn().mockReturnValue('')
|
||||
const mockGetPropertyValue = vi.fn<(prop: string) => string>().mockReturnValue('')
|
||||
|
||||
vi.stubGlobal('window', {})
|
||||
vi.stubGlobal('document', {
|
||||
|
||||
@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { cn } from './cn'
|
||||
|
||||
describe('cn', () => {
|
||||
describe(cn, () => {
|
||||
it('merges class names', () => {
|
||||
expect(cn('foo', 'bar')).toBe('foo bar')
|
||||
})
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
/* Paths */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"#src/*": ["src/*"]
|
||||
},
|
||||
|
||||
/* Types */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user