mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-21 13:15:12 +00:00
fix(ui-templates): validate templates with html-validate
(#28024)
This commit is contained in:
parent
3d55642aa5
commit
8271ea22df
@ -49,6 +49,7 @@ export const RenderPlugin = () => {
|
||||
// Apply critters to inline styles
|
||||
html = await critters.process(html)
|
||||
}
|
||||
html = html.replace(/<html[^>]*>/, '<html lang="en">')
|
||||
// We no longer need references to external CSS
|
||||
html = html.replace(/<link[^>]*>/g, '')
|
||||
|
||||
|
@ -12,7 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite",
|
||||
"lint": "eslint --ext .ts,.js .",
|
||||
"optimize-assets": "npx svgo public/assets/**/*.svg",
|
||||
"postinstall": "pnpm build",
|
||||
"prerender": "pnpm build && jiti ./lib/prerender",
|
||||
@ -25,6 +24,7 @@
|
||||
"execa": "9.3.0",
|
||||
"globby": "14.0.2",
|
||||
"html-minifier": "4.0.0",
|
||||
"html-validate": "^8.20.1",
|
||||
"jiti": "2.0.0-beta.3",
|
||||
"knitwork": "1.1.0",
|
||||
"pathe": "1.1.2",
|
||||
|
@ -9,7 +9,7 @@
|
||||
</head>
|
||||
<body class="antialiased bg-white dark:bg-[#020420] text-[#020420] dark:text-white min-h-screen place-content-center flex flex-col items-center justify-center text-sm sm:text-base">
|
||||
<div class="flex flex-col mt-6 sm:mt-0">
|
||||
<div class="flex flex-col gap-y-4 items-center justify-center">
|
||||
<h1 class="flex flex-col gap-y-4 items-center justify-center">
|
||||
<a href="https://nuxt.com?utm_source=nuxt-welcome" target="_blank" class="inline-flex items-end gap-4">
|
||||
<svg class="h-8 sm:h-12" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 800 200">
|
||||
<path fill="#00DC82" d="M168.303 200h111.522c3.543 0 7.022-.924 10.09-2.679A20.086 20.086 0 0 0 297.3 190a19.855 19.855 0 0 0 2.7-10.001 19.858 19.858 0 0 0-2.709-9.998L222.396 41.429a20.09 20.09 0 0 0-7.384-7.32 20.313 20.313 0 0 0-10.088-2.679c-3.541 0-7.02.925-10.087 2.68a20.082 20.082 0 0 0-7.384 7.32l-19.15 32.896L130.86 9.998a20.086 20.086 0 0 0-7.387-7.32A20.322 20.322 0 0 0 113.384 0c-3.542 0-7.022.924-10.09 2.679a20.091 20.091 0 0 0-7.387 7.319L2.709 170A19.853 19.853 0 0 0 0 179.999c-.002 3.511.93 6.96 2.7 10.001a20.091 20.091 0 0 0 7.385 7.321A20.322 20.322 0 0 0 20.175 200h70.004c27.737 0 48.192-12.075 62.266-35.633l34.171-58.652 18.303-31.389 54.93 94.285h-73.233L168.303 200Zm-79.265-31.421-48.854-.011 73.232-125.706 36.541 62.853-24.466 42.01c-9.347 15.285-19.965 20.854-36.453 20.854Z" />
|
||||
@ -17,7 +17,7 @@
|
||||
</svg>
|
||||
<span class="inline-block font-mono leading-none text-[#00DC82] group-hover:border-[#00DC42] text-[12px] sm:text-[14px] font-semibold border-[#00DC42]/50 bg-[#00DC42]/10 group-hover:bg-[#00DC42]/15 px-2 sm:px-2.5 py-1 sm:py-1.5 border rounded">{{ version }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
<div class="max-w-[980px] w-full grid grid-cols-1 sm:grid-cols-3 mt-6 sm:mt-10 gap-4 sm:gap-6 px-4">
|
||||
<div class="sm:col-span-2 flex flex-col gap-1 border p-6 rounded-lg border-[#00DC42]/50 dark:bg-white/5 bg-gray-50/10">
|
||||
<div class="w-[32px] h-[32px] bg-[#00DC82]/5 flex items-center justify-center border rounded border-[#00DC82] dark:border-[#00DC82]/80 dark:bg-[#020420] text-[#00DC82] dark:text-[#00DC82]">
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-404.vue 1`] = `
|
||||
exports[`template > produces correct output for error-404 template 1`] = `
|
||||
".grid {
|
||||
display: grid;
|
||||
}
|
||||
@ -135,7 +135,7 @@ exports[`template > correctly outputs style blocks for error-404.vue 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-404.vue 2`] = `
|
||||
exports[`template > produces correct output for error-404 template 2`] = `
|
||||
"*,
|
||||
:before,
|
||||
:after {
|
||||
@ -240,7 +240,7 @@ p {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-500.vue 1`] = `
|
||||
exports[`template > produces correct output for error-500 template 1`] = `
|
||||
".grid {
|
||||
display: grid;
|
||||
}
|
||||
@ -346,7 +346,7 @@ exports[`template > correctly outputs style blocks for error-500.vue 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-500.vue 2`] = `
|
||||
exports[`template > produces correct output for error-500 template 2`] = `
|
||||
"*,
|
||||
:before,
|
||||
:after {
|
||||
@ -447,7 +447,7 @@ p {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-dev.vue 1`] = `
|
||||
exports[`template > produces correct output for error-dev template 1`] = `
|
||||
".absolute {
|
||||
position: absolute;
|
||||
}
|
||||
@ -606,7 +606,7 @@ exports[`template > correctly outputs style blocks for error-dev.vue 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for error-dev.vue 2`] = `
|
||||
exports[`template > produces correct output for error-dev template 2`] = `
|
||||
"*,
|
||||
:before,
|
||||
:after {
|
||||
@ -724,7 +724,7 @@ pre {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for loading.vue 1`] = `
|
||||
exports[`template > produces correct output for loading template 1`] = `
|
||||
".nuxt-loader-bar {
|
||||
background: #00dc82;
|
||||
position: fixed;
|
||||
@ -887,7 +887,7 @@ exports[`template > correctly outputs style blocks for loading.vue 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for loading.vue 2`] = `
|
||||
exports[`template > produces correct output for loading template 2`] = `
|
||||
"@keyframes nuxt-loading-move {
|
||||
100% {
|
||||
stroke-dashoffset: -128;
|
||||
@ -998,7 +998,7 @@ svg {
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for welcome.vue 1`] = `
|
||||
exports[`template > produces correct output for welcome template 1`] = `
|
||||
".sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
@ -1358,7 +1358,7 @@ exports[`template > correctly outputs style blocks for welcome.vue 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`template > correctly outputs style blocks for welcome.vue 2`] = `
|
||||
exports[`template > produces correct output for welcome template 2`] = `
|
||||
"*,
|
||||
:before,
|
||||
:after {
|
||||
@ -1392,6 +1392,7 @@ body {
|
||||
margin: 0;
|
||||
line-height: inherit;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
@ -1400,6 +1401,7 @@ a {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
@ -1,36 +0,0 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { execaCommand } from 'execa'
|
||||
import { format } from 'prettier'
|
||||
|
||||
const distDir = fileURLToPath(new URL('../node_modules/.temp/dist/templates', import.meta.url))
|
||||
|
||||
describe('template', () => {
|
||||
beforeAll(async () => {
|
||||
await execaCommand('pnpm build', {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
env: {
|
||||
OUTPUT_DIR: './node_modules/.temp/dist',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => rm(distDir, { force: true, recursive: true }))
|
||||
|
||||
function formatCss (css: string) {
|
||||
return format(css, {
|
||||
parser: 'css',
|
||||
})
|
||||
}
|
||||
|
||||
it.each(['error-404.vue', 'error-500.vue', 'error-dev.vue', 'loading.vue', 'welcome.vue'])('correctly outputs style blocks for %s', async (file) => {
|
||||
const contents = readFileSync(`${distDir}/${file}`, 'utf-8')
|
||||
|
||||
const scopedStyle = contents.match(/<style scoped>([\s\S]*)<\/style>/)
|
||||
const globalStyle = contents.match(/style: \[[\s\S]*children: `([\s\S]*)`/)
|
||||
|
||||
expect(await formatCss(scopedStyle?.[1] || '')).toMatchSnapshot()
|
||||
expect(await formatCss(globalStyle?.[1] || '')).toMatchSnapshot()
|
||||
})
|
||||
})
|
69
packages/ui-templates/test/templates.spec.ts
Normal file
69
packages/ui-templates/test/templates.spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { execaCommand } from 'execa'
|
||||
import { format } from 'prettier'
|
||||
import { createJiti } from 'jiti'
|
||||
// @ts-expect-error types not valid for bundler resolution
|
||||
import { HtmlValidate } from 'html-validate'
|
||||
|
||||
const distDir = fileURLToPath(new URL('../node_modules/.temp/dist/templates', import.meta.url))
|
||||
|
||||
describe('template', () => {
|
||||
beforeAll(async () => {
|
||||
await execaCommand('pnpm build', {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
env: {
|
||||
OUTPUT_DIR: './node_modules/.temp/dist',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(() => rm(distDir, { force: true, recursive: true }))
|
||||
|
||||
function formatCss (css: string) {
|
||||
return format(css, {
|
||||
parser: 'css',
|
||||
})
|
||||
}
|
||||
|
||||
const jiti = createJiti(import.meta.url)
|
||||
|
||||
const validator = new HtmlValidate({
|
||||
extends: [
|
||||
'html-validate:document',
|
||||
'html-validate:recommended',
|
||||
'html-validate:standard',
|
||||
],
|
||||
rules: {
|
||||
//
|
||||
'svg-focusable': 'off',
|
||||
'no-unknown-elements': 'error',
|
||||
// Conflicts or not needed as we use prettier formatting
|
||||
'void-style': 'off',
|
||||
'no-trailing-whitespace': 'off',
|
||||
// Conflict with Nuxt defaults
|
||||
'require-sri': 'off',
|
||||
'attribute-boolean-style': 'off',
|
||||
'doctype-style': 'off',
|
||||
// Unreasonable rule
|
||||
'no-inline-style': 'off',
|
||||
},
|
||||
})
|
||||
|
||||
it.each(['error-404', 'error-500', 'error-dev', 'loading', 'welcome'])('produces correct output for %s template', async (file) => {
|
||||
const contents = readFileSync(`${distDir}/${file}.vue`, 'utf-8')
|
||||
|
||||
const scopedStyle = contents.match(/<style scoped>([\s\S]*)<\/style>/)
|
||||
const globalStyle = contents.match(/style: \[[\s\S]*children: `([\s\S]*)`/)
|
||||
|
||||
expect(await formatCss(scopedStyle?.[1] || '')).toMatchSnapshot()
|
||||
expect(await formatCss(globalStyle?.[1] || '')).toMatchSnapshot()
|
||||
|
||||
const { template } = await jiti.import(`file://${distDir}/${file}.ts`) as { template: () => string }
|
||||
const html = template()
|
||||
const { valid, results } = await (validator as any).validateString(html)
|
||||
expect.soft(valid).toBe(true)
|
||||
expect.soft(results).toEqual([])
|
||||
})
|
||||
})
|
@ -601,6 +601,9 @@ importers:
|
||||
html-minifier:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
html-validate:
|
||||
specifier: ^8.20.1
|
||||
version: 8.20.1(vitest@1.6.0(@types/node@20.14.9)(happy-dom@14.12.3)(sass@1.69.4)(terser@5.27.0))
|
||||
jiti:
|
||||
specifier: 2.0.0-beta.3
|
||||
version: 2.0.0-beta.3
|
||||
@ -1733,6 +1736,10 @@ packages:
|
||||
'@floating-ui/utils@0.2.1':
|
||||
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
|
||||
|
||||
'@html-validate/stylish@4.2.0':
|
||||
resolution: {integrity: sha512-Nl8HCv0hGRSLQ+n1OD4Hk3a+Urwk9HH0vQkAzzCarT4KlA7bRl+6xEiS5PZVwOmjtC7XiH/oNe3as9Fxcr2A1w==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1':
|
||||
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||
engines: {node: '>=12.22'}
|
||||
@ -2312,6 +2319,12 @@ packages:
|
||||
'@shikijs/vitepress-twoslash@1.10.1':
|
||||
resolution: {integrity: sha512-bBFHGKMGW0ACa8jFjDK2V9sWSh6wh1R1/U5VbVUr0HBm7kLR/H0bbr9RZeD91wKZb9JI3zJRwiNrTCueLuBw8A==}
|
||||
|
||||
'@sidvind/better-ajv-errors@2.1.3':
|
||||
resolution: {integrity: sha512-lWuod/rh7Xz5uXiEGSfm2Sd5PG7K/6yJfoAZVqzsEswjPJhUz15R7Gn/o8RczA041QS15hBd/BCSeu9vwPArkA==}
|
||||
engines: {node: '>= 16.14'}
|
||||
peerDependencies:
|
||||
ajv: 4.11.8 - 8
|
||||
|
||||
'@sinclair/typebox@0.27.8':
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
|
||||
@ -4494,6 +4507,25 @@ packages:
|
||||
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
html-validate@8.20.1:
|
||||
resolution: {integrity: sha512-EawDiHzvZtnbBIfxE90lvKOWqNsmZGqRXTy+utxlGo525Vqjowg+RK42q1AeJ6zm1AyVTFIDSah1eBe9tc6YHg==}
|
||||
engines: {node: '>= 16.14'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
jest: ^27.1 || ^28.1.3 || ^29.0.3
|
||||
jest-diff: ^27.1 || ^28.1.3 || ^29.0.3
|
||||
jest-snapshot: ^27.1 || ^28.1.3 || ^29.0.3
|
||||
vitest: ^0.34 || ^1
|
||||
peerDependenciesMeta:
|
||||
jest:
|
||||
optional: true
|
||||
jest-diff:
|
||||
optional: true
|
||||
jest-snapshot:
|
||||
optional: true
|
||||
vitest:
|
||||
optional: true
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
@ -4920,6 +4952,10 @@ packages:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
klona@2.0.6:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -7870,6 +7906,10 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.1': {}
|
||||
|
||||
'@html-validate/stylish@4.2.0':
|
||||
dependencies:
|
||||
kleur: 4.1.5
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1': {}
|
||||
|
||||
'@humanwhocodes/retry@0.3.0': {}
|
||||
@ -8692,6 +8732,12 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@sidvind/better-ajv-errors@2.1.3(ajv@8.12.0)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
ajv: 8.12.0
|
||||
chalk: 4.1.2
|
||||
|
||||
'@sinclair/typebox@0.27.8': {}
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
@ -11477,6 +11523,22 @@ snapshots:
|
||||
|
||||
html-tags@3.3.1: {}
|
||||
|
||||
html-validate@8.20.1(vitest@1.6.0(@types/node@20.14.9)(happy-dom@14.12.3)(sass@1.69.4)(terser@5.27.0)):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
'@html-validate/stylish': 4.2.0
|
||||
'@sidvind/better-ajv-errors': 2.1.3(ajv@8.12.0)
|
||||
ajv: 8.12.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 10.4.1
|
||||
ignore: 5.3.1
|
||||
kleur: 4.1.5
|
||||
minimist: 1.2.8
|
||||
prompts: 2.4.2
|
||||
semver: 7.6.2
|
||||
optionalDependencies:
|
||||
vitest: 1.6.0(@types/node@20.14.9)(happy-dom@14.12.3)(sass@1.69.4)(terser@5.27.0)
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
@ -11868,6 +11930,8 @@ snapshots:
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
kleur@4.1.5: {}
|
||||
|
||||
klona@2.0.6: {}
|
||||
|
||||
knitwork@1.1.0: {}
|
||||
|
Loading…
Reference in New Issue
Block a user