nuxt-spec 0.2.3 → 0.2.4-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config/index.mjs CHANGED
@@ -1,79 +1,85 @@
1
- // this is the default Vitest config object
2
- // based on https://nuxt.com/docs/4.x/getting-started/testing#setup
3
- // `projects=false` can be used to suspend the default usage of "projects" in Vitest config
4
-
5
- import { onConsoleLog } from './utils/warnings.mjs' // filter out unnecessary logs
6
- import { mergeConfig } from './utils/merge.mjs' // defu-based merge function
7
- import { defineConfig } from 'vitest/config'
8
- import { defineVitestProject } from '@nuxt/test-utils/config'
9
- import { playwright } from '@vitest/browser-playwright'
10
- import vue from '@vitejs/plugin-vue'
11
-
12
- export async function loadVitestConfig(userVitestConfig, projects = true) {
13
- const baseConfig = {
14
- test: {
15
- // filter-out unnecessary console logs coming from Vitest
16
- // when the import is resolved, unnecessary stderr logs are also filtered-out
17
- // as a side-effect
18
- onConsoleLog,
19
- },
20
- }
21
-
22
- if (projects === true) {
23
- baseConfig.test.projects = [
24
- // default fallback to catch tests directly in /test folder
25
- {
26
- test: {
27
- name: 'default',
28
- include: ['{test,tests}/**/*.{test,spec}.ts', '!test/{browser,e2e,nuxt,unit}/**'],
29
- environment: 'node',
30
- },
31
- },
32
- // proposed setup for Unit tests
33
- {
34
- test: {
35
- name: 'node',
36
- include: ['test/unit/**/*.{test,spec}.ts'],
37
- environment: 'node',
38
- },
39
- },
40
- // proposed setup for Nuxt component tests
41
- await defineVitestProject({
42
- test: {
43
- name: 'nuxt',
44
- include: ['test/nuxt/**/*.{test,spec}.ts'],
45
- environment: 'nuxt',
46
- },
47
- }),
48
- // proposed setup for classic E2E tests (node-based, using @nuxt/test-utils)
49
- {
50
- test: {
51
- name: 'e2e',
52
- include: ['test/e2e/**/*.{test,spec}.ts'],
53
- environment: 'node',
54
- },
55
- },
56
- // proposed setup for browser component tests (with Playwright runner)
57
- {
58
- // vue plugin is required for proper imports resolution
59
- plugins: [vue()],
60
- test: {
61
- name: 'browser',
62
- include: ['test/browser/**/*.{test,spec}.ts'],
63
- environment: 'node',
64
- browser: {
65
- provider: playwright(),
66
- enabled: true,
67
- headless: true,
68
- instances: [{
69
- browser: 'chromium',
70
- viewport: { width: 1280, height: 720 },
71
- }],
72
- },
73
- },
74
- },
75
- ]
76
- }
77
-
78
- return mergeConfig(userVitestConfig, defineConfig(baseConfig))
79
- }
1
+ // this is the default Vitest config object
2
+ // based on https://nuxt.com/docs/4.x/getting-started/testing#setup
3
+ // `projects=false` can be used to suspend the default usage of "projects" in Vitest config
4
+
5
+ import { fileURLToPath } from 'node:url'
6
+ import { onConsoleLog } from './utils/warnings.mjs' // filter out unnecessary logs
7
+ import { mergeConfig } from './utils/merge.mjs' // defu-based merge function
8
+ import { defineConfig } from 'vitest/config'
9
+ import { defineVitestProject } from '@nuxt/test-utils/config'
10
+ import { playwright } from '@vitest/browser-playwright'
11
+ import vue from '@vitejs/plugin-vue'
12
+
13
+ // absolute path so it works from nuxt-ignis package
14
+ const screenshotReportSetup = fileURLToPath(new URL('../utils/screenshot-report.ts', import.meta.url))
15
+
16
+ export async function loadVitestConfig(userVitestConfig, projects = true) {
17
+ const baseConfig = {
18
+ test: {
19
+ // filter-out unnecessary console logs coming from Vitest
20
+ // when the import is resolved, unnecessary stderr logs are also filtered-out
21
+ // as a side-effect
22
+ onConsoleLog,
23
+ },
24
+ }
25
+
26
+ if (projects === true) {
27
+ baseConfig.test.projects = [
28
+ // default fallback to catch tests directly in /test folder
29
+ {
30
+ test: {
31
+ name: 'default',
32
+ include: ['{test,tests}/**/*.{test,spec}.ts', '!test/{browser,e2e,nuxt,unit}/**'],
33
+ environment: 'node',
34
+ },
35
+ },
36
+ // proposed setup for Unit tests
37
+ {
38
+ test: {
39
+ name: 'node',
40
+ include: ['test/unit/**/*.{test,spec}.ts'],
41
+ environment: 'node',
42
+ },
43
+ },
44
+ // proposed setup for Nuxt component tests
45
+ await defineVitestProject({
46
+ test: {
47
+ name: 'nuxt',
48
+ include: ['test/nuxt/**/*.{test,spec}.ts'],
49
+ environment: 'nuxt',
50
+ },
51
+ }),
52
+ // proposed setup for classic E2E tests (node-based, using @nuxt/test-utils)
53
+ {
54
+ test: {
55
+ name: 'e2e',
56
+ include: ['test/e2e/**/*.{test,spec}.ts'],
57
+ environment: 'node',
58
+ // create report file for visual regression testing
59
+ globalSetup: [screenshotReportSetup],
60
+ },
61
+ },
62
+ // proposed setup for browser component tests (with Playwright runner)
63
+ {
64
+ // vue plugin is required for proper imports resolution
65
+ plugins: [vue()],
66
+ test: {
67
+ name: 'browser',
68
+ include: ['test/browser/**/*.{test,spec}.ts'],
69
+ environment: 'node',
70
+ browser: {
71
+ provider: playwright(),
72
+ enabled: true,
73
+ headless: true,
74
+ instances: [{
75
+ browser: 'chromium',
76
+ viewport: { width: 1280, height: 720 },
77
+ }],
78
+ },
79
+ },
80
+ },
81
+ ]
82
+ }
83
+
84
+ return mergeConfig(userVitestConfig, defineConfig(baseConfig))
85
+ }
@@ -1,4 +1,4 @@
1
- # settings for Nuxt & pnpm
2
-
3
- # https://pnpm.io/settings#shamefullyhoist
4
- shamefullyHoist: true
1
+ # settings for Nuxt & pnpm
2
+
3
+ # https://pnpm.io/settings#shamefullyhoist
4
+ shamefullyHoist: true
@@ -1,5 +1,5 @@
1
- import { loadVitestConfig } from 'nuxt-spec/config'
2
-
3
- export default loadVitestConfig({
4
- // custom config here
5
- })
1
+ import { loadVitestConfig } from 'nuxt-spec/config'
2
+
3
+ export default loadVitestConfig({
4
+ // custom config here
5
+ })
@@ -1,43 +1,43 @@
1
- // custom merger function based on defu
2
- // allows working with the "projects" array properly
3
- // user-defined config overrides are merged by "name"
4
-
5
- // for consistent and predictable results, passing "include" or "browser.instances"
6
- // will result in OVERRIDE instead of merging with nuxt-spec defaults
7
-
8
- import { createDefu } from 'defu'
9
-
10
- // in real Vitest config, "name" is nested inside another "test" object
11
- const getProjectName = project => project?.name ?? project?.test?.name
12
-
13
- export const mergeConfig = createDefu((obj, key, value) => {
14
- if (key === 'projects' && Array.isArray(obj[key]) && Array.isArray(value)) {
15
- const defaults = obj[key]
16
- const overrides = value
17
-
18
- // override default values if user-defined config specifies them
19
- obj[key] = defaults.map((defaultProject) => {
20
- const override = overrides.find(o => getProjectName(o) === getProjectName(defaultProject))
21
- return override ? mergeProject(override, defaultProject) : defaultProject
22
- })
23
-
24
- // add any user projects that don't exist in defaults
25
- for (const override of overrides) {
26
- if (!defaults.some(d => getProjectName(d) === getProjectName(override))) {
27
- obj[key].push(override)
28
- }
29
- }
30
-
31
- return true
32
- }
33
- })
34
-
35
- // Keys where user value should fully replace the default, not merge
36
- const overrideKeys = new Set(['include', 'instances'])
37
-
38
- const mergeProject = createDefu((obj, key, value) => {
39
- if (overrideKeys.has(key)) {
40
- obj[key] = value
41
- return true
42
- }
43
- })
1
+ // custom merger function based on defu
2
+ // allows working with the "projects" array properly
3
+ // user-defined config overrides are merged by "name"
4
+
5
+ // for consistent and predictable results, passing "include" or "browser.instances"
6
+ // will result in OVERRIDE instead of merging with nuxt-spec defaults
7
+
8
+ import { createDefu } from 'defu'
9
+
10
+ // in real Vitest config, "name" is nested inside another "test" object
11
+ const getProjectName = project => project?.name ?? project?.test?.name
12
+
13
+ export const mergeConfig = createDefu((obj, key, value) => {
14
+ if (key === 'projects' && Array.isArray(obj[key]) && Array.isArray(value)) {
15
+ const defaults = obj[key]
16
+ const overrides = value
17
+
18
+ // override default values if user-defined config specifies them
19
+ obj[key] = defaults.map((defaultProject) => {
20
+ const override = overrides.find(o => getProjectName(o) === getProjectName(defaultProject))
21
+ return override ? mergeProject(override, defaultProject) : defaultProject
22
+ })
23
+
24
+ // add any user projects that don't exist in defaults
25
+ for (const override of overrides) {
26
+ if (!defaults.some(d => getProjectName(d) === getProjectName(override))) {
27
+ obj[key].push(override)
28
+ }
29
+ }
30
+
31
+ return true
32
+ }
33
+ })
34
+
35
+ // Keys where user value should fully replace the default, not merge
36
+ const overrideKeys = new Set(['include', 'instances'])
37
+
38
+ const mergeProject = createDefu((obj, key, value) => {
39
+ if (overrideKeys.has(key)) {
40
+ obj[key] = value
41
+ return true
42
+ }
43
+ })
@@ -1,33 +1,33 @@
1
- const messageFilters = [
2
- // Node+Windows false positive
3
- // https://github.com/nuxt/icon/issues/140
4
- 'Use of deprecated trailing slash pattern mapping',
5
- // remove once Vue stops considering <Suspense> experimental
6
- '<Suspense> is an experimental feature',
7
- // merge defaults with user-defined filters from NUXT_SPEC_MESSAGE_FILTERS env variable
8
- ...(process.env.NUXT_SPEC_MESSAGE_FILTERS
9
- ? process.env.NUXT_SPEC_MESSAGE_FILTERS.split(',')
10
- : []),
11
- ]
12
-
13
- // 1) filter-out unnecessary stderr/stdout logs coming from Vitest
14
- // (applied as side-effect on import)
15
-
16
- const _stderrWrite = process.stderr.write.bind(process.stderr)
17
- process.stderr.write = (chunk, ...args) => {
18
- if (typeof chunk === 'string' && messageFilters.some(f => chunk.includes(f))) return true
19
- return _stderrWrite(chunk, ...args)
20
- }
21
-
22
- const _stdoutWrite = process.stdout.write.bind(process.stdout)
23
- process.stdout.write = (chunk, ...args) => {
24
- if (typeof chunk === 'string' && messageFilters.some(f => chunk.includes(f))) return true
25
- return _stdoutWrite(chunk, ...args)
26
- }
27
-
28
- // 2) filter-out unnecessary console logs coming from Vitest
29
- // (used as Vitest onConsoleLog hook)
30
-
31
- export function onConsoleLog(log) {
32
- if (messageFilters.some(f => log.includes(f))) return false
33
- }
1
+ const messageFilters = [
2
+ // Node+Windows false positive
3
+ // https://github.com/nuxt/icon/issues/140
4
+ 'Use of deprecated trailing slash pattern mapping',
5
+ // remove once Vue stops considering <Suspense> experimental
6
+ '<Suspense> is an experimental feature',
7
+ // merge defaults with user-defined filters from NUXT_SPEC_MESSAGE_FILTERS env variable
8
+ ...(process.env.NUXT_SPEC_MESSAGE_FILTERS
9
+ ? process.env.NUXT_SPEC_MESSAGE_FILTERS.split(',')
10
+ : []),
11
+ ]
12
+
13
+ // 1) filter-out unnecessary stderr/stdout logs coming from Vitest
14
+ // (applied as side-effect on import)
15
+
16
+ const _stderrWrite = process.stderr.write.bind(process.stderr)
17
+ process.stderr.write = (chunk, ...args) => {
18
+ if (typeof chunk === 'string' && messageFilters.some(f => chunk.includes(f))) return true
19
+ return _stderrWrite(chunk, ...args)
20
+ }
21
+
22
+ const _stdoutWrite = process.stdout.write.bind(process.stdout)
23
+ process.stdout.write = (chunk, ...args) => {
24
+ if (typeof chunk === 'string' && messageFilters.some(f => chunk.includes(f))) return true
25
+ return _stdoutWrite(chunk, ...args)
26
+ }
27
+
28
+ // 2) filter-out unnecessary console logs coming from Vitest
29
+ // (used as Vitest onConsoleLog hook)
30
+
31
+ export function onConsoleLog(log) {
32
+ if (messageFilters.some(f => log.includes(f))) return false
33
+ }
package/nuxt.config.ts CHANGED
@@ -1,19 +1,19 @@
1
- export default defineNuxtConfig({
2
- modules: [
3
- '@nuxt/eslint',
4
- '@nuxt/test-utils/module',
5
- ],
6
-
7
- // exclude file used for explicit exports (nuxt-spec/components) from Nuxt resolution
8
- components: {
9
- dirs: [{ path: '~/components', ignore: ['index.ts'] }],
10
- },
11
-
12
- compatibilityDate: '2026-06-01',
13
-
14
- eslint: {
15
- config: {
16
- stylistic: true,
17
- },
18
- },
19
- })
1
+ export default defineNuxtConfig({
2
+ modules: [
3
+ '@nuxt/eslint',
4
+ '@nuxt/test-utils/module',
5
+ ],
6
+
7
+ // exclude file used for explicit exports (nuxt-spec/components) from Nuxt resolution
8
+ components: {
9
+ dirs: [{ path: '~/components', ignore: ['index.ts'] }],
10
+ },
11
+
12
+ compatibilityDate: '2026-06-01',
13
+
14
+ eslint: {
15
+ config: {
16
+ stylistic: true,
17
+ },
18
+ },
19
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-spec",
3
- "version": "0.2.3",
3
+ "version": "0.2.4-alpha",
4
4
  "description": "Test-pack layer for Nuxt Applications",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,6 +9,19 @@
9
9
  "license": "MIT",
10
10
  "type": "module",
11
11
  "main": "./nuxt.config.ts",
12
+ "scripts": {
13
+ "analyze": "nuxt analyze",
14
+ "eslint": "eslint .",
15
+ "build": "pnpm test && nuxt build",
16
+ "dev": "nuxt dev",
17
+ "generate": "nuxt generate",
18
+ "prepublishOnly": "node bin/prepublish-check.js",
19
+ "preview": "nuxt preview",
20
+ "start": "nuxt start",
21
+ "test": "vitest run",
22
+ "test-u": "vitest run -u",
23
+ "test-i": "vitest"
24
+ },
12
25
  "bin": {
13
26
  "nuxt-spec": "bin/cli.js"
14
27
  },
@@ -38,35 +51,24 @@
38
51
  "nuxt.config.ts"
39
52
  ],
40
53
  "dependencies": {
41
- "@nuxt/eslint": "1.15.2",
54
+ "@nuxt/eslint": "~1.16.0",
42
55
  "@nuxt/test-utils": "4.0.3",
43
- "@vitejs/plugin-vue": "6.0.7",
44
- "@vitest/browser": "4.1.8",
45
- "@vitest/browser-playwright": "4.1.8",
46
- "@vitest/ui": "4.1.8",
47
- "@vue/test-utils": "2.4.10",
56
+ "@vitejs/plugin-vue": "~6.0.7",
57
+ "@vitest/browser": "4.1.9",
58
+ "@vitest/browser-playwright": "4.1.9",
59
+ "@vitest/ui": "4.1.9",
60
+ "@vue/test-utils": "2.4.11",
48
61
  "elrh-cosca": "0.3.6",
49
- "fast-png": "8.0.0",
50
- "happy-dom": "20.9.0",
51
- "nuxt": "4.4.7",
52
- "pixelmatch": "7.2.0",
53
- "playwright-core": "1.60.0",
54
- "typescript": "6.0.3",
55
- "vitest": "4.1.8",
62
+ "fast-png": "~8.0.0",
63
+ "happy-dom": "~20.10.6",
64
+ "nuxt": "4.4.8",
65
+ "pixelmatch": "~7.2.0",
66
+ "playwright-core": "~1.61.1",
67
+ "typescript": "~6.0.3",
68
+ "vitest": "4.1.9",
56
69
  "vitest-browser-vue": "2.1.0",
57
- "vue": "3.5.35",
58
- "vue-router": "5.1.0"
70
+ "vue": "~3.5.39",
71
+ "vue-router": "~5.1.0"
59
72
  },
60
- "scripts": {
61
- "analyze": "nuxt analyze",
62
- "eslint": "eslint .",
63
- "build": "pnpm test && nuxt build",
64
- "dev": "nuxt dev",
65
- "generate": "nuxt generate",
66
- "preview": "nuxt preview",
67
- "start": "nuxt start",
68
- "test": "vitest run",
69
- "test-u": "vitest run -u",
70
- "test-i": "vitest"
71
- }
72
- }
73
+ "packageManager": "pnpm@11.9.0"
74
+ }
package/utils/e2e.ts CHANGED
@@ -1,30 +1,30 @@
1
- import { createPage, url } from '@nuxt/test-utils/e2e'
2
- import type { NuxtPage } from '@nuxt/test-utils'
3
-
4
- // visit a specified URL and return the page instance for further interaction
5
- export async function gotoPage(pageName: string): Promise<NuxtPage> {
6
- const page = await createPage()
7
- const urlPath = pageName.startsWith('/') ? url(pageName) : url(`/${pageName}`)
8
- await page.goto(urlPath, { waitUntil: 'hydration' })
9
- return page
10
- }
11
-
12
- // extract HTML content from specified element on a given page
13
- export async function getDataHtml(page: NuxtPage | string, element: string): Promise<string> {
14
- const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
15
- const dataDiv = pageInstance.locator(element)
16
- return await dataDiv.innerHTML()
17
- }
18
-
19
- // execute an API call and extract HTML content from the result
20
- // (assumes clickable element that triggers the request
21
- // and separate element for displaying the response)
22
- export async function getAPIResultHtml(page: NuxtPage | string, triggerElement: string, targetUrl: string, responseElement: string) {
23
- const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
24
- await pageInstance.click(triggerElement)
25
- await pageInstance.waitForResponse(response =>
26
- response.url().includes(targetUrl) && response.ok(),
27
- )
28
- const resultDiv = pageInstance.locator(responseElement)
29
- return await resultDiv.innerHTML()
30
- }
1
+ import { createPage, url } from '@nuxt/test-utils/e2e'
2
+ import type { NuxtPage } from '@nuxt/test-utils'
3
+
4
+ // visit a specified URL and return the page instance for further interaction
5
+ export async function gotoPage(pageName: string): Promise<NuxtPage> {
6
+ const page = await createPage()
7
+ const urlPath = pageName.startsWith('/') ? url(pageName) : url(`/${pageName}`)
8
+ await page.goto(urlPath, { waitUntil: 'hydration' })
9
+ return page
10
+ }
11
+
12
+ // extract HTML content from specified element on a given page
13
+ export async function getDataHtml(page: NuxtPage | string, element: string): Promise<string> {
14
+ const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
15
+ const dataDiv = pageInstance.locator(element)
16
+ return await dataDiv.innerHTML()
17
+ }
18
+
19
+ // execute an API call and extract HTML content from the result
20
+ // (assumes clickable element that triggers the request
21
+ // and separate element for displaying the response)
22
+ export async function getAPIResultHtml(page: NuxtPage | string, triggerElement: string, targetUrl: string, responseElement: string) {
23
+ const pageInstance = typeof page === 'string' ? await gotoPage(page) : page
24
+ await pageInstance.click(triggerElement)
25
+ await pageInstance.waitForResponse(response =>
26
+ response.url().includes(targetUrl) && response.ok(),
27
+ )
28
+ const resultDiv = pageInstance.locator(responseElement)
29
+ return await resultDiv.innerHTML()
30
+ }
@@ -0,0 +1,8 @@
1
+ <div class="failure">
2
+ <h2>{{FILE_NAME}}</h2>
3
+ <p class="message">{{MESSAGE}}</p>
4
+ <div class="pair">
5
+ <figure><figcaption>Baseline image</figcaption><img src="{{BASELINE_URI}}" alt="Baseline image for reference"></figure>
6
+ <figure><figcaption>Actual screenshot</figcaption><img src="{{ACTUAL_URI}}" alt="Actual screenshot taken"></figure>
7
+ </div>
8
+ </div>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Visual Regression Report</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; margin: 2rem; background: #f5f5f5; color: #222; }
8
+ h1 { font-size: 1.4rem; }
9
+ .failure { background: #fff; border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 1rem 0; box-shadow: 0 1px 3px rgba(0, 0, 0, .1); }
10
+ .failure h2 { font-size: 1rem; margin: 0 0 .5rem; }
11
+ .failure .message { color: #b00; font-size: .85rem; margin: 0 0 .75rem; }
12
+ .pair { display: flex; gap: 1rem; flex-wrap: wrap; }
13
+ .pair figure { margin: 0; flex: 1 1 0; min-width: 240px; }
14
+ .pair figcaption { font-size: .8rem; margin-bottom: .25rem; font-weight: bold; }
15
+ .pair figure:first-child figcaption { color: #0b0; }
16
+ .pair figure:last-child figcaption { color: #b00; }
17
+ .pair img { max-width: 100%; background: #fff; }
18
+ .pair figure:first-child img { border: 2px solid #0b0; }
19
+ .pair figure:last-child img { border: 2px solid #b00; }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <h1>Visual Regression Report - {{TIMESTAMP}}</h1>
24
+ <!-- BEGIN failed screenshot entries -->
@@ -0,0 +1,4 @@
1
+ <!-- END failed screenshot entries -->
2
+ <div>Generated by <a href="https://github.com/nuxt/nuxt-spec">Nuxt Spec</a> @ {{TIMESTAMP}}</div>
3
+ </body>
4
+ </html>