Skip to content

Commit

Permalink
fix(runtime-utils): support options api data, computed + methods (#963)
Browse files Browse the repository at this point in the history
  • Loading branch information
inouetakuya authored Oct 7, 2024
1 parent cf1b48f commit 9ec23da
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 2 deletions.
75 changes: 75 additions & 0 deletions examples/app-vitest-full/pages/other/options-api.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<ul>
<li data-testid="greetingInSetup">
{{ greetingInSetup }}
</li>
<li data-testid="greetingInData1">
{{ greetingInData1 }}
</li>
<li data-testid="greetingInData2">
{{ greetingInData2 }}
</li>
<li data-testid="greetingInComputed">
{{ greetingInComputed }}
</li>
<li data-testid="computedData1">
{{ computedData1 }}
</li>
<li data-testid="computedGreetingInMethods">
{{ computedGreetingInMethods }}
</li>
<li data-testid="greetingInMethods">
{{ greetingInMethods() }}
</li>
<li data-testid="returnData1">
{{ returnData1() }}
</li>
<li data-testid="returnComputedData1">
{{ returnComputedData1() }}
</li>
</ul>
</template>

<script lang="ts">
export default defineNuxtComponent({
name: 'OptionsApiPage',
setup() {
return {
greetingInSetup: 'Hello, setup',
}
},
async asyncData() {
return {
greetingInData2: 'Hello, overwritten by asyncData',
}
},
data() {
return {
greetingInData1: 'Hello, data1',
greetingInData2: 'Hello, data2',
}
},
computed: {
greetingInComputed() {
return 'Hello, computed property'
},
computedData1() {
return this.greetingInData1
},
computedGreetingInMethods() {
return this.greetingInMethods()
},
},
methods: {
greetingInMethods() {
return 'Hello, method'
},
returnData1() {
return this.greetingInData1
},
returnComputedData1() {
return this.computedData1
},
},
})
</script>
14 changes: 14 additions & 0 deletions examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ExportDefaultComponent from '~/components/ExportDefaultComponent.vue'
import ExportDefineComponent from '~/components/ExportDefineComponent.vue'
import ExportDefaultWithRenderComponent from '~/components/ExportDefaultWithRenderComponent.vue'
import ExportDefaultReturnsRenderComponent from '~/components/ExportDefaultReturnsRenderComponent.vue'
import OptionsApiPage from '~/pages/other/options-api.vue'

import { BoundAttrs } from '#components'
import DirectiveComponent from '~/components/DirectiveComponent.vue'
Expand Down Expand Up @@ -81,6 +82,19 @@ describe('mountSuspended', () => {
)
})

it('should render asyncData and other options api properties within nuxt suspense', async () => {
const component = await mountSuspended(OptionsApiPage)
expect(component.find('[data-testid="greetingInSetup"]').text()).toBe('Hello, setup')
expect(component.find('[data-testid="greetingInData1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="greetingInData2"]').text()).toBe('Hello, overwritten by asyncData')
expect(component.find('[data-testid="greetingInComputed"]').text()).toBe('Hello, computed property')
expect(component.find('[data-testid="computedData1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="computedGreetingInMethods"]').text()).toBe('Hello, method')
expect(component.find('[data-testid="greetingInMethods"]').text()).toBe('Hello, method')
expect(component.find('[data-testid="returnData1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="returnComputedData1"]').text()).toBe('Hello, data1')
})

it('can receive emitted events from components mounted within nuxt suspense', async () => {
const component = await mountSuspended(WrapperTests)
component.find('button#emitCustomEvent').trigger('click')
Expand Down
14 changes: 14 additions & 0 deletions examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ExportDefaultComponent from '~/components/ExportDefaultComponent.vue'
import ExportDefineComponent from '~/components/ExportDefineComponent.vue'
import ExportDefaultWithRenderComponent from '~/components/ExportDefaultWithRenderComponent.vue'
import ExportDefaultReturnsRenderComponent from '~/components/ExportDefaultReturnsRenderComponent.vue'
import OptionsApiPage from '~/pages/other/options-api.vue'

import { BoundAttrs } from '#components'

Expand Down Expand Up @@ -97,6 +98,19 @@ describe('renderSuspended', () => {
expect(screen.getByText(text)).toBeDefined()
})

it('should render asyncData and other options api properties within nuxt suspense', async () => {
const { getByTestId } = await renderSuspended(OptionsApiPage)
expect(getByTestId('greetingInSetup').textContent).toBe('Hello, setup')
expect(getByTestId('greetingInData1').textContent).toBe('Hello, data1')
expect(getByTestId('greetingInData2').textContent).toBe('Hello, overwritten by asyncData')
expect(getByTestId('greetingInComputed').textContent).toBe('Hello, computed property')
expect(getByTestId('computedData1').textContent).toBe('Hello, data1')
expect(getByTestId('computedGreetingInMethods').textContent).toBe('Hello, method')
expect(getByTestId('greetingInMethods').textContent).toBe('Hello, method')
expect(getByTestId('returnData1').textContent).toBe('Hello, data1')
expect(getByTestId('returnComputedData1').textContent).toBe('Hello, data1')
})

it('can receive emitted events from components rendered within nuxt suspense', async () => {
const { emitted } = await renderSuspended(WrapperTests)
const button = screen.getByRole('button', { name: 'Click me!' })
Expand Down
21 changes: 20 additions & 1 deletion src/runtime-utils/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function mountSuspended<T>(
const vueApp = tryUseNuxtApp()?.vueApp
// @ts-expect-error untyped global __unctx__
|| globalThis.__unctx__.get('nuxt-app').tryUse().vueApp
const { render, setup } = component as DefineComponent<Record<string, unknown>, Record<string, unknown>>
const { render, setup, data, computed, methods } = component as DefineComponent<Record<string, unknown>, Record<string, unknown>>

let setupContext: SetupContext
let setupState: Record<string, unknown>
Expand Down Expand Up @@ -116,6 +116,14 @@ export async function mountSuspended<T>(
...component,
render: render
? function (this: unknown, _ctx: Record<string, unknown>, ...args: unknown[]) {
// Set before setupState set to allow asyncData to overwrite data
if (data && typeof data === 'function') {
// @ts-expect-error error TS2554: Expected 1 arguments, but got 0
const dataObject: Record<string, unknown> = data()
for (const key in dataObject) {
renderContext[key] = dataObject[key]
}
}
for (const key in setupState || {}) {
renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key]
}
Expand All @@ -125,6 +133,17 @@ export async function mountSuspended<T>(
for (const key in passedProps || {}) {
renderContext[key] = passedProps[key]
}
if (methods && typeof methods === 'object') {
for (const key in methods) {
renderContext[key] = methods[key]
}
}
if (computed && typeof computed === 'object') {
for (const key in computed) {
// @ts-expect-error error TS2339: Property 'call' does not exist on type 'ComputedGetter<any> | WritableComputedOptions<any, any>'
renderContext[key] = computed[key].call(renderContext)
}
}
return render.call(this, renderContext, ...args)
}
: undefined,
Expand Down
21 changes: 20 additions & 1 deletion src/runtime-utils/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export async function renderSuspended<T>(
const vueApp = tryUseNuxtApp()?.vueApp
// @ts-expect-error untyped global __unctx__
|| globalThis.__unctx__.get('nuxt-app').tryUse().vueApp
const { render, setup } = component as DefineComponent<Record<string, unknown>, Record<string, unknown>>
const { render, setup, data, computed, methods } = component as DefineComponent<Record<string, unknown>, Record<string, unknown>>

let setupContext: SetupContext
let setupState: SetupState
Expand Down Expand Up @@ -138,6 +138,14 @@ export async function renderSuspended<T>(
...component,
render: render
? function (this: unknown, _ctx: Record<string, unknown>, ...args: unknown[]) {
// Set before setupState set to allow asyncData to overwrite data
if (data && typeof data === 'function') {
// @ts-expect-error error TS2554: Expected 1 arguments, but got 0
const dataObject: Record<string, unknown> = data()
for (const key in dataObject) {
renderContext[key] = dataObject[key]
}
}
for (const key in setupState || {}) {
renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key]
}
Expand All @@ -147,6 +155,17 @@ export async function renderSuspended<T>(
for (const key in passedProps || {}) {
renderContext[key] = passedProps[key]
}
if (methods && typeof methods === 'object') {
for (const key in methods) {
renderContext[key] = methods[key]
}
}
if (computed && typeof computed === 'object') {
for (const key in computed) {
// @ts-expect-error error TS2339: Property 'call' does not exist on type 'ComputedGetter<any> | WritableComputedOptions<any, any>'
renderContext[key] = computed[key].call(renderContext)
}
}
return render.call(this, renderContext, ...args)
}
: undefined,
Expand Down

0 comments on commit 9ec23da

Please sign in to comment.