mirror of
https://github.com/nuxt/nuxt.git
synced 2024-12-05 03:47:12 +00:00
462 lines
17 KiB
TypeScript
462 lines
17 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { Plugin } from 'vite'
|
|
import type { Component } from '@nuxt/schema'
|
|
import type { UnpluginOptions } from 'unplugin'
|
|
import { IslandsTransformPlugin } from '../src/components/plugins/islands-transform'
|
|
import { normalizeLineEndings } from './utils'
|
|
|
|
const getComponents = () => [{
|
|
filePath: '/root/hello.server.vue',
|
|
mode: 'server',
|
|
pascalName: 'HelloWorld',
|
|
island: true,
|
|
kebabName: 'hello-world',
|
|
chunkName: 'components/hello-world',
|
|
export: 'default',
|
|
shortPath: '',
|
|
prefetch: false,
|
|
preload: false,
|
|
}] as Component[]
|
|
|
|
const pluginWebpack = IslandsTransformPlugin({
|
|
getComponents,
|
|
selectiveClient: true,
|
|
}).raw({}, { framework: 'webpack', webpack: { compiler: {} as any } })
|
|
|
|
const viteTransform = async (source: string, id: string, selectiveClient = false) => {
|
|
const vitePlugin = IslandsTransformPlugin({
|
|
getComponents,
|
|
selectiveClient,
|
|
}).raw({}, { framework: 'vite' }) as Plugin
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
const result = await (vitePlugin.transform! as Function)(source, id)
|
|
return typeof result === 'string' ? result : result?.code
|
|
}
|
|
|
|
const webpackTransform = async (source: string, id: string) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
const result = await ((pluginWebpack as UnpluginOptions).transform! as Function)(source, id)
|
|
return typeof result === 'string' ? result : result?.code
|
|
}
|
|
|
|
describe('islandTransform - server and island components', () => {
|
|
describe('slots', () => {
|
|
it('expect slot transform to match inline snapshot', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<slot />
|
|
|
|
<slot name="named" :some-data="someData" />
|
|
<slot
|
|
name="other"
|
|
:some-data="someData"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
const someData = 'some data'
|
|
|
|
</script>`
|
|
, 'hello.server.vue')
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
|
|
|
<NuxtTeleportSsrSlot name="named" :props="[{ [\`some-data\`]: someData }]"><slot name="named" :some-data="someData" /></NuxtTeleportSsrSlot>
|
|
<NuxtTeleportSsrSlot name="other" :props="[{ [\`some-data\`]: someData }]"><slot
|
|
name="other"
|
|
:some-data="someData"
|
|
/></NuxtTeleportSsrSlot>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
const someData = 'some data'
|
|
|
|
</script>"
|
|
`)
|
|
})
|
|
|
|
it('generates bindings when props are needed to be merged', async () => {
|
|
const result = await viteTransform(`<script setup lang="ts">
|
|
withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
|
|
things: () => [],
|
|
somethingElse: "yay",
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<template v-for="thing in things">
|
|
<slot name="thing" v-bind="thing" />
|
|
</template>
|
|
</template>
|
|
`, 'hello.server.vue')
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
|
|
things: () => [],
|
|
somethingElse: "yay",
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<template v-for="thing in things">
|
|
<NuxtTeleportSsrSlot name="thing" :props="[__mergeProps(thing, { })]"><slot name="thing" v-bind="thing" /></NuxtTeleportSsrSlot>
|
|
</template>
|
|
</template>
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('expect slot fallback transform to match inline snapshot', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<slot :some-data="someData">
|
|
<div>fallback</div>
|
|
</slot>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
const someData = 'some data'
|
|
|
|
</script>`
|
|
, 'hello.server.vue')
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<NuxtTeleportSsrSlot name="default" :props="[{ [\`some-data\`]: someData }]"><slot :some-data="someData"/><template #fallback>
|
|
<div>fallback</div>
|
|
</template></NuxtTeleportSsrSlot>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
const someData = 'some data'
|
|
|
|
</script>"
|
|
`)
|
|
})
|
|
|
|
it('expect slot transform with fallback and no name to match inline snapshot #23209', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<UCard>
|
|
<template #header>
|
|
<h3>Partial Hydration Example - Server - {{ count }}</h3>
|
|
</template>
|
|
<template #default>
|
|
<p>message: {{ message }}</p>
|
|
<p>Below is the slot I want to be hydrated on the client</p>
|
|
<div>
|
|
<slot>
|
|
This is the default content of the slot, I should not see this after
|
|
the client loading has completed.
|
|
</slot>
|
|
</div>
|
|
<p>Above is the slot I want to be hydrated on the client</p>
|
|
</template>
|
|
</UCard>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
export interface Props {
|
|
count?: number;
|
|
}
|
|
const props = withDefaults(defineProps<Props>(), { count: 0 });
|
|
|
|
const message = "Hello World";
|
|
</script>
|
|
`
|
|
, 'hello.server.vue')
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<UCard>
|
|
<template #header>
|
|
<h3>Partial Hydration Example - Server - {{ count }}</h3>
|
|
</template>
|
|
<template #default>
|
|
<p>message: {{ message }}</p>
|
|
<p>Below is the slot I want to be hydrated on the client</p>
|
|
<div>
|
|
<NuxtTeleportSsrSlot name="default" :props="undefined"><slot/><template #fallback>
|
|
This is the default content of the slot, I should not see this after
|
|
the client loading has completed.
|
|
</template></NuxtTeleportSsrSlot>
|
|
</div>
|
|
<p>Above is the slot I want to be hydrated on the client</p>
|
|
</template>
|
|
</UCard>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
export interface Props {
|
|
count?: number;
|
|
}
|
|
const props = withDefaults(defineProps<Props>(), { count: 0 });
|
|
|
|
const message = "Hello World";
|
|
</script>
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('expect v-if/v-else/v-else-if to be set in teleport component wrapper', async () => {
|
|
const result = await viteTransform(`<script setup lang="ts">
|
|
const foo = true;
|
|
</script>
|
|
<template>
|
|
<slot v-if="foo" />
|
|
<slot v-else-if="test" />
|
|
<slot v-else />
|
|
</template>
|
|
`, 'WithVif.vue', true)
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
const foo = true;
|
|
</script>
|
|
<template>
|
|
<NuxtTeleportSsrSlot v-if="foo" name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
|
<NuxtTeleportSsrSlot v-else-if="test" name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
|
<NuxtTeleportSsrSlot v-else name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>
|
|
</template>
|
|
"
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('nuxt-client', () => {
|
|
describe('vite', () => {
|
|
it('test transform with vite', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<HelloWorld nuxt-client />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import HelloWorld from './HelloWorld.vue'
|
|
</script>
|
|
`, 'hello.server.vue', true)
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<NuxtTeleportIslandComponent to="HelloWorld-CyH3UXLuYA" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
import HelloWorld from './HelloWorld.vue'
|
|
</script>
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('test dynamic nuxt-client', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<HelloWorld :nuxt-client="nuxtClient" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const nuxtClient = false
|
|
</script>
|
|
`, 'hello.server.vue', true)
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<NuxtTeleportIslandComponent to="HelloWorld-eo0XycWCUV" :nuxt-client="nuxtClient"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const nuxtClient = false
|
|
</script>
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('should not transform if disabled', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<HelloWorld :nuxt-client="nuxtClient" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const nuxtClient = false
|
|
</script>
|
|
`, 'hello.server.vue', false)
|
|
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<HelloWorld :nuxt-client="nuxtClient" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const nuxtClient = false
|
|
</script>
|
|
"
|
|
`)
|
|
})
|
|
|
|
it('should add import if there is no scripts in the SFC', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<HelloWorld />
|
|
<HelloWorld nuxt-client />
|
|
</div>
|
|
</template>
|
|
|
|
`, 'hello.server.vue', true)
|
|
|
|
expect(result).toMatchInlineSnapshot(`
|
|
"<script setup>
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
|
|
<div>
|
|
<HelloWorld />
|
|
<NuxtTeleportIslandComponent to="HelloWorld-CyH3UXLuYA" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
</div>
|
|
</template>
|
|
|
|
"
|
|
`)
|
|
expect(result).toContain('import NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'')
|
|
})
|
|
|
|
it('should move v-if to the wrapper component', async () => {
|
|
const result = await viteTransform(`<template>
|
|
<div>
|
|
<HelloWorld v-if="false" nuxt-client />
|
|
<HelloWorld v-else-if="true" nuxt-client />
|
|
<HelloWorld v-else nuxt-client />
|
|
</div>
|
|
</template>
|
|
`, 'hello.server.vue', true)
|
|
|
|
expect(result).toMatchInlineSnapshot(`
|
|
"<script setup>
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
|
|
<div>
|
|
<NuxtTeleportIslandComponent v-if="false" to="HelloWorld-D9uaHyzL7X" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
<NuxtTeleportIslandComponent v-else-if="true" to="HelloWorld-o4RZMtArnE" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
<NuxtTeleportIslandComponent v-else to="HelloWorld-m1IbXHdd8O" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
|
|
</div>
|
|
</template>
|
|
"
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('webpack', () => {
|
|
it('test transform with webpack', async () => {
|
|
const spyOnWarn = vi.spyOn(console, 'warn')
|
|
const result = await webpackTransform(`<template>
|
|
<div>
|
|
<!-- should not be wrapped by NuxtTeleportIslandComponent -->
|
|
<HelloWorld />
|
|
|
|
<!-- should be not wrapped by NuxtTeleportIslandComponent for now -->
|
|
<HelloWorld nuxt-client />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const someData = 'some data'
|
|
</script>
|
|
`, 'hello.server.vue')
|
|
expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
|
|
"<template>
|
|
<div>
|
|
<!-- should not be wrapped by NuxtTeleportIslandComponent -->
|
|
<HelloWorld />
|
|
|
|
<!-- should be not wrapped by NuxtTeleportIslandComponent for now -->
|
|
<HelloWorld nuxt-client />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mergeProps as __mergeProps } from 'vue'
|
|
import { vforToArray as __vforToArray } from '#app/components/utils'
|
|
import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
|
|
import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
|
|
import HelloWorld from './HelloWorld.vue'
|
|
|
|
const someData = 'some data'
|
|
</script>
|
|
"
|
|
`)
|
|
|
|
expect(spyOnWarn).toHaveBeenCalledWith('The `nuxt-client` attribute and client components within islands are only supported with Vite. file: hello.server.vue')
|
|
})
|
|
})
|
|
})
|
|
})
|