2023-10-06 10:30:53 +00:00
|
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
|
|
|
import { randomUUID } from 'node:crypto'
|
|
|
|
import { afterAll, describe, expect, it } from 'vitest'
|
|
|
|
import { dirname, join, normalize, resolve } from 'pathe'
|
|
|
|
import { withoutTrailingSlash } from 'ufo'
|
|
|
|
import { createApp, resolveApp } from '../src/core/app'
|
|
|
|
import { loadNuxt } from '../src'
|
|
|
|
|
|
|
|
const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url))))
|
|
|
|
|
|
|
|
describe('resolveApp', () => {
|
|
|
|
afterAll(async () => {
|
|
|
|
await rm(resolve(repoRoot, '.fixture'), { recursive: true, force: true })
|
|
|
|
})
|
|
|
|
it('resolves app with default configuration', async () => {
|
|
|
|
const app = await getResolvedApp([])
|
|
|
|
expect(app).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"components": [],
|
|
|
|
"configs": [],
|
2024-07-16 14:12:31 +00:00
|
|
|
"dir": "<rootDir>",
|
2023-10-06 10:30:53 +00:00
|
|
|
"errorComponent": "<repoRoot>/packages/nuxt/src/app/components/nuxt-error-page.vue",
|
|
|
|
"extensions": [
|
|
|
|
".js",
|
|
|
|
".jsx",
|
|
|
|
".mjs",
|
|
|
|
".ts",
|
|
|
|
".tsx",
|
|
|
|
".vue",
|
|
|
|
],
|
|
|
|
"layouts": {},
|
2024-05-01 13:10:33 +00:00
|
|
|
"mainComponent": "<repoRoot>/packages/nuxt/src/app/components/welcome.vue",
|
2023-10-06 10:30:53 +00:00
|
|
|
"middleware": [
|
|
|
|
{
|
|
|
|
"global": true,
|
|
|
|
"name": "manifest-route-rule",
|
|
|
|
"path": "<repoRoot>/packages/nuxt/src/app/middleware/manifest-route-rule.ts",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"plugins": [
|
|
|
|
{
|
|
|
|
"mode": "client",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/payload.client.ts",
|
|
|
|
},
|
2024-06-20 22:18:57 +00:00
|
|
|
{
|
|
|
|
"mode": "client",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/navigation-repaint.client.ts",
|
|
|
|
},
|
2023-12-19 20:03:09 +00:00
|
|
|
{
|
|
|
|
"mode": "client",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/check-outdated-build.client.ts",
|
|
|
|
},
|
2023-10-06 10:30:53 +00:00
|
|
|
{
|
|
|
|
"mode": "server",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/revive-payload.server.ts",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"mode": "client",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/revive-payload.client.ts",
|
|
|
|
},
|
2024-07-01 09:06:48 +00:00
|
|
|
{
|
|
|
|
"mode": "client",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/chunk-reload.client.ts",
|
|
|
|
},
|
2023-10-06 10:30:53 +00:00
|
|
|
{
|
|
|
|
"filename": "components.plugin.mjs",
|
|
|
|
"getContents": [Function],
|
|
|
|
"mode": "all",
|
|
|
|
"src": "<rootDir>/.nuxt/components.plugin.mjs",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"mode": "all",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/head/runtime/plugins/unhead.ts",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"mode": "all",
|
|
|
|
"src": "<repoRoot>/packages/nuxt/src/app/plugins/router.ts",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
"rootComponent": "<repoRoot>/packages/nuxt/src/app/components/nuxt-root.vue",
|
|
|
|
"templates": [],
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2024-01-02 22:04:26 +00:00
|
|
|
it('resolves layouts and middleware correctly', async () => {
|
|
|
|
const app = await getResolvedApp([
|
|
|
|
'middleware/index.ts',
|
|
|
|
'middleware/auth/index.ts',
|
|
|
|
'middleware/other.ts',
|
|
|
|
'layouts/index.vue',
|
|
|
|
'layouts/default/index.vue',
|
2024-04-05 18:08:32 +00:00
|
|
|
'layouts/other.vue',
|
2024-01-02 22:04:26 +00:00
|
|
|
])
|
|
|
|
// Middleware are not resolved in a nested manner
|
|
|
|
expect(app.middleware.filter(m => m.path.startsWith('<rootDir>'))).toMatchInlineSnapshot(`
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"global": false,
|
|
|
|
"name": "other",
|
|
|
|
"path": "<rootDir>/middleware/other.ts",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
`)
|
|
|
|
expect(app.layouts).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"default": {
|
|
|
|
"file": "<rootDir>/layouts/default/index.vue",
|
|
|
|
"name": "default",
|
|
|
|
},
|
|
|
|
"other": {
|
|
|
|
"file": "<rootDir>/layouts/other.vue",
|
|
|
|
"name": "other",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2023-10-06 10:30:53 +00:00
|
|
|
it('resolves layer plugins in correct order', async () => {
|
|
|
|
const app = await getResolvedApp([
|
|
|
|
// layer 1
|
|
|
|
'layer1/plugins/02.plugin.ts',
|
|
|
|
'layer1/plugins/object-named.ts',
|
|
|
|
'layer1/plugins/override-test.ts',
|
|
|
|
'layer1/nuxt.config.ts',
|
|
|
|
// layer 2
|
|
|
|
'layer2/plugins/01.plugin.ts',
|
|
|
|
'layer2/plugins/object-named.ts',
|
|
|
|
'layer2/plugins/override-test.ts',
|
|
|
|
'layer2/nuxt.config.ts',
|
|
|
|
// final (user) layer
|
|
|
|
'plugins/00.plugin.ts',
|
|
|
|
'plugins/object-named.ts',
|
|
|
|
{
|
|
|
|
name: 'nuxt.config.ts',
|
2024-04-05 18:08:32 +00:00
|
|
|
contents: 'export default defineNuxtConfig({ extends: [\'./layer2\', \'./layer1\'] })',
|
|
|
|
},
|
2023-10-06 10:30:53 +00:00
|
|
|
])
|
|
|
|
const fixturePlugins = app.plugins.filter(p => !('getContents' in p) && p.src.includes('<rootDir>')).map(p => p.src)
|
|
|
|
// TODO: support overriding named plugins
|
|
|
|
expect(fixturePlugins).toMatchInlineSnapshot(`
|
|
|
|
[
|
|
|
|
"<rootDir>/layer1/plugins/02.plugin.ts",
|
|
|
|
"<rootDir>/layer1/plugins/object-named.ts",
|
|
|
|
"<rootDir>/layer1/plugins/override-test.ts",
|
|
|
|
"<rootDir>/layer2/plugins/01.plugin.ts",
|
|
|
|
"<rootDir>/layer2/plugins/object-named.ts",
|
|
|
|
"<rootDir>/layer2/plugins/override-test.ts",
|
|
|
|
"<rootDir>/plugins/00.plugin.ts",
|
|
|
|
"<rootDir>/plugins/object-named.ts",
|
|
|
|
]
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('resolves layer middleware in correct order', async () => {
|
|
|
|
const app = await getResolvedApp([
|
|
|
|
// layer 1
|
|
|
|
'layer1/middleware/global.global.ts',
|
|
|
|
'layer1/middleware/named-from-layer.ts',
|
|
|
|
'layer1/middleware/named-override.ts',
|
|
|
|
'layer1/nuxt.config.ts',
|
|
|
|
// layer 2
|
|
|
|
'layer2/middleware/global.global.ts',
|
|
|
|
'layer2/middleware/named-from-layer.ts',
|
|
|
|
'layer2/middleware/named-override.ts',
|
|
|
|
'layer2/plugins/override-test.ts',
|
|
|
|
'layer2/nuxt.config.ts',
|
|
|
|
// final (user) layer
|
|
|
|
'middleware/named-override.ts',
|
|
|
|
'middleware/named.ts',
|
|
|
|
{
|
|
|
|
name: 'nuxt.config.ts',
|
2024-04-05 18:08:32 +00:00
|
|
|
contents: 'export default defineNuxtConfig({ extends: [\'./layer2\', \'./layer1\'] })',
|
|
|
|
},
|
2023-10-06 10:30:53 +00:00
|
|
|
])
|
|
|
|
const fixtureMiddleware = app.middleware.filter(p => p.path.includes('<rootDir>')).map(p => p.path)
|
|
|
|
// TODO: fix this
|
|
|
|
expect(fixtureMiddleware).toMatchInlineSnapshot(`
|
|
|
|
[
|
2023-10-12 22:21:02 +00:00
|
|
|
"<rootDir>/layer2/middleware/global.global.ts",
|
|
|
|
"<rootDir>/layer2/middleware/named-from-layer.ts",
|
|
|
|
"<rootDir>/middleware/named-override.ts",
|
2023-10-06 10:30:53 +00:00
|
|
|
"<rootDir>/middleware/named.ts",
|
|
|
|
]
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('resolves layer layouts correctly', async () => {
|
|
|
|
const app = await getResolvedApp([
|
|
|
|
// layer 1
|
|
|
|
'layer1/layouts/default.vue',
|
|
|
|
'layer1/layouts/layer.vue',
|
|
|
|
'layer1/nuxt.config.ts',
|
|
|
|
// layer 2
|
|
|
|
'layer2/layouts/default.vue',
|
|
|
|
'layer2/layouts/layer.vue',
|
|
|
|
'layer2/nuxt.config.ts',
|
|
|
|
// final (user) layer
|
|
|
|
'layouts/default.vue',
|
|
|
|
{
|
|
|
|
name: 'nuxt.config.ts',
|
2024-04-05 18:08:32 +00:00
|
|
|
contents: 'export default defineNuxtConfig({ extends: [\'./layer2\', \'./layer1\'] })',
|
|
|
|
},
|
2023-10-06 10:30:53 +00:00
|
|
|
])
|
|
|
|
expect(app.layouts).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"default": {
|
|
|
|
"file": "<rootDir>/layouts/default.vue",
|
|
|
|
"name": "default",
|
|
|
|
},
|
|
|
|
"layer": {
|
|
|
|
"file": "<rootDir>/layer2/layouts/layer.vue",
|
|
|
|
"name": "layer",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2023-10-16 21:58:40 +00:00
|
|
|
|
|
|
|
it('resolves nested layouts correctly', async () => {
|
|
|
|
const app = await getResolvedApp([
|
|
|
|
'layouts/default.vue',
|
|
|
|
'layouts/some/layout.vue',
|
|
|
|
'layouts/SomeOther.vue',
|
|
|
|
'layouts/SomeOther/Thing/Index.vue',
|
|
|
|
'layouts/thing/thing/thing.vue',
|
|
|
|
'layouts/desktop-base/base.vue',
|
|
|
|
'layouts/some.vue',
|
2024-04-05 18:08:32 +00:00
|
|
|
'layouts/SomeOther/layout.ts',
|
2023-10-16 21:58:40 +00:00
|
|
|
])
|
|
|
|
expect(app.layouts).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"default": {
|
|
|
|
"file": "<rootDir>/layouts/default.vue",
|
|
|
|
"name": "default",
|
|
|
|
},
|
|
|
|
"desktop-base": {
|
|
|
|
"file": "<rootDir>/layouts/desktop-base/base.vue",
|
|
|
|
"name": "desktop-base",
|
|
|
|
},
|
|
|
|
"some": {
|
|
|
|
"file": "<rootDir>/layouts/some.vue",
|
|
|
|
"name": "some",
|
|
|
|
},
|
|
|
|
"some-layout": {
|
|
|
|
"file": "<rootDir>/layouts/some/layout.vue",
|
|
|
|
"name": "some-layout",
|
|
|
|
},
|
|
|
|
"some-other": {
|
|
|
|
"file": "<rootDir>/layouts/SomeOther.vue",
|
|
|
|
"name": "some-other",
|
|
|
|
},
|
|
|
|
"some-other-layout": {
|
|
|
|
"file": "<rootDir>/layouts/SomeOther/layout.ts",
|
|
|
|
"name": "some-other-layout",
|
|
|
|
},
|
|
|
|
"some-other-thing": {
|
|
|
|
"file": "<rootDir>/layouts/SomeOther/Thing/Index.vue",
|
|
|
|
"name": "some-other-thing",
|
|
|
|
},
|
|
|
|
"thing": {
|
|
|
|
"file": "<rootDir>/layouts/thing/thing/thing.vue",
|
|
|
|
"name": "thing",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2023-10-06 10:30:53 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
async function getResolvedApp (files: Array<string | { name: string, contents: string }>) {
|
|
|
|
const rootDir = resolve(repoRoot, 'node_modules/.fixture', randomUUID())
|
|
|
|
await mkdir(rootDir, { recursive: true })
|
|
|
|
for (const file of files) {
|
|
|
|
const filename = typeof file === 'string' ? join(rootDir, file) : join(rootDir, file.name)
|
|
|
|
await mkdir(dirname(filename), { recursive: true })
|
|
|
|
await writeFile(filename, typeof file === 'string' ? '' : file.contents || '')
|
|
|
|
}
|
|
|
|
|
|
|
|
const nuxt = await loadNuxt({ cwd: rootDir })
|
|
|
|
const app = createApp(nuxt)
|
|
|
|
await resolveApp(nuxt, app)
|
|
|
|
|
|
|
|
const normaliseToRepo = (id?: string | null) =>
|
|
|
|
id?.replace(rootDir, '<rootDir>').replace(repoRoot, '<repoRoot>').replace(/.*node_modules\//, '')
|
|
|
|
|
|
|
|
app.dir = normaliseToRepo(app.dir)!
|
|
|
|
|
|
|
|
const componentKeys = ['rootComponent', 'errorComponent', 'mainComponent'] as const
|
|
|
|
for (const _key of componentKeys) {
|
|
|
|
const key = _key as typeof componentKeys[number]
|
|
|
|
app[key] = normaliseToRepo(app[key])
|
|
|
|
}
|
|
|
|
for (const plugin of app.plugins) {
|
|
|
|
plugin.src = normaliseToRepo(plugin.src)!
|
|
|
|
}
|
|
|
|
for (const mw of app.middleware) {
|
|
|
|
mw.path = normaliseToRepo(mw.path)!
|
|
|
|
}
|
|
|
|
|
2024-09-18 19:41:53 +00:00
|
|
|
for (const layout of Object.values(app.layouts)) {
|
|
|
|
layout.file = normaliseToRepo(layout.file)!
|
2023-10-06 10:30:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
await nuxt.close()
|
|
|
|
|
|
|
|
return app
|
|
|
|
}
|