2024-06-13 16:59:24 +00:00
|
|
|
import { describe, expect, it } from 'vitest'
|
2024-11-28 16:34:02 +00:00
|
|
|
import { compileScript, parse } from '@vue/compiler-sfc'
|
|
|
|
import * as Parser from 'acorn'
|
|
|
|
|
|
|
|
import { PageMetaPlugin } from '../src/pages/plugins/page-meta'
|
2024-06-13 16:59:24 +00:00
|
|
|
import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
|
|
|
|
import type { NuxtPage } from '../schema'
|
|
|
|
|
|
|
|
const filePath = '/app/pages/index.vue'
|
|
|
|
|
|
|
|
describe('page metadata', () => {
|
|
|
|
it('should not extract metadata from empty files', async () => {
|
|
|
|
expect(await getRouteMeta('', filePath)).toEqual({})
|
|
|
|
expect(await getRouteMeta('<template><div>Hi</div></template>', filePath)).toEqual({})
|
|
|
|
})
|
|
|
|
|
2024-08-09 11:11:38 +00:00
|
|
|
it('should extract metadata from JS/JSX files', async () => {
|
|
|
|
const fileContents = `definePageMeta({ name: 'bar' })`
|
|
|
|
for (const ext of ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs']) {
|
|
|
|
const meta = await getRouteMeta(fileContents, `/app/pages/index.${ext}`)
|
|
|
|
expect(meta).toStrictEqual({
|
|
|
|
name: 'bar',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-11-28 16:34:02 +00:00
|
|
|
it('should parse JSX files', async () => {
|
|
|
|
const fileContents = `
|
|
|
|
export default {
|
|
|
|
setup () {
|
|
|
|
definePageMeta({ name: 'bar' })
|
|
|
|
return () => <div></div>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`
|
|
|
|
const meta = await getRouteMeta(fileContents, `/app/pages/index.jsx`)
|
|
|
|
expect(meta).toStrictEqual({
|
|
|
|
name: 'bar',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
// TODO: https://github.com/nuxt/nuxt/pull/30066
|
|
|
|
it.todo('should handle experimental decorators', async () => {
|
|
|
|
const fileContents = `
|
|
|
|
<script setup lang="ts">
|
|
|
|
function something (_method: () => unknown) {
|
|
|
|
return () => 'decorated'
|
|
|
|
}
|
|
|
|
class SomeClass {
|
|
|
|
@something
|
|
|
|
public someMethod () {
|
|
|
|
return 'initial'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
definePageMeta({ name: 'bar' })
|
|
|
|
</script>
|
|
|
|
`
|
|
|
|
const meta = await getRouteMeta(fileContents, `/app/pages/index.vue`)
|
|
|
|
expect(meta).toStrictEqual({
|
|
|
|
name: 'bar',
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-06-13 16:59:24 +00:00
|
|
|
it('should use and invalidate cache', async () => {
|
|
|
|
const fileContents = `<script setup>definePageMeta({ foo: 'bar' })</script>`
|
|
|
|
const meta = await getRouteMeta(fileContents, filePath)
|
|
|
|
expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy()
|
|
|
|
expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy()
|
|
|
|
expect(meta === await getRouteMeta('<template><div>Hi</div></template>' + fileContents, filePath)).toBeFalsy()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should extract serialisable metadata', async () => {
|
|
|
|
const meta = await getRouteMeta(`
|
|
|
|
<script setup>
|
|
|
|
definePageMeta({
|
|
|
|
name: 'some-custom-name',
|
|
|
|
path: '/some-custom-path',
|
|
|
|
validate: () => true,
|
|
|
|
middleware: [
|
|
|
|
function () {},
|
|
|
|
],
|
|
|
|
otherValue: {
|
|
|
|
foo: 'bar',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`, filePath)
|
|
|
|
|
|
|
|
expect(meta).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"meta": {
|
|
|
|
"__nuxt_dynamic_meta_key": Set {
|
|
|
|
"meta",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"name": "some-custom-name",
|
|
|
|
"path": "/some-custom-path",
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2024-08-21 11:38:18 +00:00
|
|
|
it('should extract serialisable metadata from files with multiple blocks', async () => {
|
|
|
|
const meta = await getRouteMeta(`
|
|
|
|
<script lang="ts">
|
|
|
|
export default {
|
|
|
|
name: 'thing'
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<script setup>
|
|
|
|
definePageMeta({
|
|
|
|
name: 'some-custom-name',
|
|
|
|
path: '/some-custom-path',
|
|
|
|
validate: () => true,
|
|
|
|
middleware: [
|
|
|
|
function () {},
|
|
|
|
],
|
|
|
|
otherValue: {
|
|
|
|
foo: 'bar',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`, filePath)
|
|
|
|
|
|
|
|
expect(meta).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"meta": {
|
|
|
|
"__nuxt_dynamic_meta_key": Set {
|
|
|
|
"meta",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"name": "some-custom-name",
|
|
|
|
"path": "/some-custom-path",
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
|
2024-06-13 16:59:24 +00:00
|
|
|
it('should extract serialisable metadata in options api', async () => {
|
|
|
|
const meta = await getRouteMeta(`
|
|
|
|
<script>
|
|
|
|
export default {
|
|
|
|
setup() {
|
|
|
|
definePageMeta({
|
|
|
|
name: 'some-custom-name',
|
|
|
|
path: '/some-custom-path',
|
|
|
|
middleware: (from, to) => console.warn('middleware'),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
`, filePath)
|
|
|
|
|
|
|
|
expect(meta).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"meta": {
|
|
|
|
"__nuxt_dynamic_meta_key": Set {
|
|
|
|
"meta",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"name": "some-custom-name",
|
|
|
|
"path": "/some-custom-path",
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2024-06-25 16:53:30 +00:00
|
|
|
|
|
|
|
it('should extract serialisable metadata all quoted', async () => {
|
|
|
|
const meta = await getRouteMeta(`
|
|
|
|
<script setup>
|
|
|
|
definePageMeta({
|
|
|
|
"otherValue": {
|
|
|
|
foo: 'bar',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`, filePath)
|
|
|
|
|
|
|
|
expect(meta).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"meta": {
|
|
|
|
"__nuxt_dynamic_meta_key": Set {
|
|
|
|
"meta",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2024-11-27 16:57:15 +00:00
|
|
|
|
|
|
|
it('should extract configured extra meta', async () => {
|
|
|
|
const meta = await getRouteMeta(`
|
|
|
|
<script setup>
|
|
|
|
definePageMeta({
|
|
|
|
foo: 'bar',
|
|
|
|
bar: true,
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`, filePath, ['bar', 'foo'])
|
|
|
|
|
|
|
|
expect(meta).toMatchInlineSnapshot(`
|
|
|
|
{
|
|
|
|
"bar": true,
|
|
|
|
"foo": "bar",
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
})
|
2024-06-13 16:59:24 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('normalizeRoutes', () => {
|
|
|
|
it('should produce valid route objects when used with extracted meta', async () => {
|
|
|
|
const page: NuxtPage = { path: '/', file: filePath }
|
|
|
|
Object.assign(page, await getRouteMeta(`
|
|
|
|
<script setup>
|
|
|
|
definePageMeta({
|
|
|
|
name: 'some-custom-name',
|
|
|
|
path: ref('/some-custom-path'), /* dynamic */
|
|
|
|
validate: () => true,
|
|
|
|
redirect: '/',
|
|
|
|
middleware: [
|
|
|
|
function () {},
|
|
|
|
],
|
|
|
|
otherValue: {
|
|
|
|
foo: 'bar',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`, filePath))
|
|
|
|
|
|
|
|
page.meta ||= {}
|
|
|
|
page.meta.layout = 'test'
|
|
|
|
page.meta.foo = 'bar'
|
|
|
|
|
2024-10-09 12:58:05 +00:00
|
|
|
const { routes, imports } = normalizeRoutes([page], new Set(), {
|
|
|
|
clientComponentRuntime: '<client-component-runtime>',
|
|
|
|
serverComponentRuntime: '<server-component-runtime>',
|
|
|
|
overrideMeta: true,
|
|
|
|
})
|
2024-06-13 16:59:24 +00:00
|
|
|
expect({ routes, imports }).toMatchInlineSnapshot(`
|
2024-09-10 13:56:08 +00:00
|
|
|
{
|
|
|
|
"imports": Set {
|
|
|
|
"import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
|
|
|
|
},
|
|
|
|
"routes": "[
|
2024-06-13 16:59:24 +00:00
|
|
|
{
|
2024-09-10 13:56:08 +00:00
|
|
|
name: "some-custom-name",
|
|
|
|
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
|
|
|
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
|
|
|
redirect: "/",
|
|
|
|
component: () => import("/app/pages/index.vue")
|
2024-06-13 16:59:24 +00:00
|
|
|
}
|
2024-09-10 13:56:08 +00:00
|
|
|
]",
|
|
|
|
}
|
|
|
|
`)
|
2024-06-13 16:59:24 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should produce valid route objects when used without extracted meta', () => {
|
|
|
|
const page: NuxtPage = { path: '/', file: filePath }
|
|
|
|
page.meta ||= {}
|
|
|
|
page.meta.layout = 'test'
|
|
|
|
page.meta.foo = 'bar'
|
|
|
|
|
2024-10-09 12:58:05 +00:00
|
|
|
const { routes, imports } = normalizeRoutes([page], new Set(), {
|
|
|
|
clientComponentRuntime: '<client-component-runtime>',
|
|
|
|
serverComponentRuntime: '<server-component-runtime>',
|
|
|
|
overrideMeta: false,
|
|
|
|
})
|
2024-06-13 16:59:24 +00:00
|
|
|
expect({ routes, imports }).toMatchInlineSnapshot(`
|
2024-09-10 13:56:08 +00:00
|
|
|
{
|
|
|
|
"imports": Set {
|
|
|
|
"import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
|
|
|
|
},
|
|
|
|
"routes": "[
|
2024-06-13 16:59:24 +00:00
|
|
|
{
|
2024-09-10 13:56:08 +00:00
|
|
|
name: indexN6pT4Un8hYMeta?.name ?? undefined,
|
|
|
|
path: indexN6pT4Un8hYMeta?.path ?? "/",
|
2024-10-22 12:57:16 +00:00
|
|
|
props: indexN6pT4Un8hYMeta?.props ?? false,
|
2024-09-10 13:56:08 +00:00
|
|
|
meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} },
|
|
|
|
alias: indexN6pT4Un8hYMeta?.alias || [],
|
|
|
|
redirect: indexN6pT4Un8hYMeta?.redirect,
|
|
|
|
component: () => import("/app/pages/index.vue")
|
2024-06-13 16:59:24 +00:00
|
|
|
}
|
2024-09-10 13:56:08 +00:00
|
|
|
]",
|
|
|
|
}
|
|
|
|
`)
|
2024-06-13 16:59:24 +00:00
|
|
|
})
|
|
|
|
})
|
2024-11-28 16:34:02 +00:00
|
|
|
|
|
|
|
describe('rewrite page meta', () => {
|
|
|
|
const transformPlugin = PageMetaPlugin().raw({}, {} as any) as { transform: (code: string, id: string) => { code: string } | null }
|
|
|
|
|
|
|
|
it('should extract metadata from vue components', () => {
|
|
|
|
const sfc = `
|
|
|
|
<script setup lang="ts">
|
|
|
|
definePageMeta({
|
|
|
|
name: 'hi',
|
|
|
|
other: 'value'
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
`
|
|
|
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
|
|
|
expect(transformPlugin.transform.call({
|
|
|
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
|
|
|
sourceType: 'module',
|
|
|
|
ecmaVersion: 'latest',
|
|
|
|
locations: true,
|
|
|
|
...opts,
|
|
|
|
}),
|
|
|
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
|
|
|
"const __nuxt_page_meta = {
|
|
|
|
name: 'hi',
|
|
|
|
other: 'value'
|
|
|
|
}
|
|
|
|
export default __nuxt_page_meta"
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
})
|