Skip to content

Commit

Permalink
fix(runtime-utils): bind this in options api methods (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
inouetakuya authored Oct 19, 2024
1 parent 9ace343 commit af57c3e
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 39 deletions.
14 changes: 14 additions & 0 deletions examples/app-vitest-full/components/TestButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<button
data-testid="test-button"
@click="$emit('test-button-click')"
>
Button in TestButton component
</button>
</template>

<script setup lang="ts">
defineEmits<{
(e: 'test-button-click'): void
}>()
</script>
44 changes: 35 additions & 9 deletions examples/app-vitest-full/pages/other/options-api.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
<template>
<ul>
<li data-testid="greetingInSetup">
<li data-testid="greeting-in-setup">
{{ greetingInSetup }}
</li>
<li data-testid="greetingInData1">
<li data-testid="greeting-in-data1">
{{ greetingInData1 }}
</li>
<li data-testid="greetingInData2">
<li data-testid="greeting-in-data2">
{{ greetingInData2 }}
</li>
<li data-testid="greetingInComputed">
<li data-testid="greeting-in-computed">
{{ greetingInComputed }}
</li>
<li data-testid="computedData1">
<li data-testid="computed-data1">
{{ computedData1 }}
</li>
<li data-testid="computedGreetingInMethods">
<li data-testid="computed-greeting-in-methods">
{{ computedGreetingInMethods }}
</li>
<li data-testid="greetingInMethods">
<li data-testid="greeting-in-methods">
{{ greetingInMethods() }}
</li>
<li data-testid="returnData1">
<li data-testid="return-data1">
{{ returnData1() }}
</li>
<li data-testid="returnComputedData1">
<li data-testid="return-computed-data1">
{{ returnComputedData1() }}
</li>
<li>
<button
data-testid="button-in-page"
@click="onClickButtonInPage"
>
Button in page
</button>
</li>
<li>
<TestButton @test-button-click="onClickButtonInComponent" />
</li>
</ul>
</template>

<script lang="ts">
import TestButton from '~/components/TestButton.vue'
export default defineNuxtComponent({
name: 'OptionsApiPage',
components: {
TestButton,
},
setup() {
return {
greetingInSetup: 'Hello, setup',
Expand Down Expand Up @@ -70,6 +86,16 @@ export default defineNuxtComponent({
returnComputedData1() {
return this.computedData1
},
onClickButtonInPage() {
if (this === undefined) {
console.error('this in onClickButtonInPage is undefined')
}
},
onClickButtonInComponent() {
if (this === undefined) {
console.error('this in onClickButtonInComponent is undefined')
}
},
},
})
</script>
52 changes: 38 additions & 14 deletions examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'

import { mountSuspended } from '@nuxt/test-utils/runtime'

Expand Down Expand Up @@ -82,19 +82,6 @@ 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 Expand Up @@ -137,6 +124,43 @@ describe('mountSuspended', () => {
const component = await mountSuspended(DirectiveComponent)
expect(component.html()).toMatchInlineSnapshot(`"<div data-directive="true"></div>"`)
})

describe('Options API', () => {
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation((message) => {
console.log('[spy] console.error has been called', message)
})
})

afterEach(() => {
vi.restoreAllMocks()
})

it('should render asyncData and other options api properties within nuxt suspense', async () => {
const component = await mountSuspended(OptionsApiPage)
expect(component.find('[data-testid="greeting-in-setup"]').text()).toBe('Hello, setup')
expect(component.find('[data-testid="greeting-in-data1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="greeting-in-data2"]').text()).toBe('Hello, overwritten by asyncData')
expect(component.find('[data-testid="greeting-in-computed"]').text()).toBe('Hello, computed property')
expect(component.find('[data-testid="computed-data1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="computed-greeting-in-methods"]').text()).toBe('Hello, method')
expect(component.find('[data-testid="greeting-in-methods"]').text()).toBe('Hello, method')
expect(component.find('[data-testid="return-data1"]').text()).toBe('Hello, data1')
expect(component.find('[data-testid="return-computed-data1"]').text()).toBe('Hello, data1')
})

it('should not output error when button in page is clicked', async () => {
const component = await mountSuspended(OptionsApiPage)
await component.find('[data-testid="button-in-page"]').trigger('click')
expect(console.error).not.toHaveBeenCalled()
})

it('should not output error when button in component is clicked', async () => {
const component = await mountSuspended(OptionsApiPage)
await component.find('[data-testid="test-button"]').trigger('click')
expect(console.error).not.toHaveBeenCalled()
})
})
})

describe.each(Object.entries(formats))(`%s`, (name, component) => {
Expand Down
52 changes: 38 additions & 14 deletions examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it } from 'vitest'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'

import { renderSuspended } from '@nuxt/test-utils/runtime'
import { cleanup, fireEvent, screen, render } from '@testing-library/vue'
Expand Down Expand Up @@ -98,19 +98,6 @@ 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 Expand Up @@ -138,6 +125,43 @@ describe('renderSuspended', () => {
}
`)
})

describe('Options API', () => {
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation((message) => {
console.log('[spy] console.error has been called', message)
})
})

afterEach(() => {
vi.restoreAllMocks()
})

it('should render asyncData and other options api properties within nuxt suspense', async () => {
const { getByTestId } = await renderSuspended(OptionsApiPage)
expect(getByTestId('greeting-in-setup').textContent).toBe('Hello, setup')
expect(getByTestId('greeting-in-data1').textContent).toBe('Hello, data1')
expect(getByTestId('greeting-in-data2').textContent).toBe('Hello, overwritten by asyncData')
expect(getByTestId('greeting-in-computed').textContent).toBe('Hello, computed property')
expect(getByTestId('computed-data1').textContent).toBe('Hello, data1')
expect(getByTestId('computed-greeting-in-methods').textContent).toBe('Hello, method')
expect(getByTestId('greeting-in-methods').textContent).toBe('Hello, method')
expect(getByTestId('return-data1').textContent).toBe('Hello, data1')
expect(getByTestId('return-computed-data1').textContent).toBe('Hello, data1')
})

it('should not output error when button in page is clicked', async () => {
const { getByTestId } = await renderSuspended(OptionsApiPage)
await fireEvent.click(getByTestId('button-in-page'))
expect(console.error).not.toHaveBeenCalled()
})

it('should not output error when button in component is clicked', async () => {
const { getByTestId } = await renderSuspended(OptionsApiPage)
await fireEvent.click(getByTestId('test-button'))
expect(console.error).not.toHaveBeenCalled()
})
})
})

describe.each(Object.entries(formats))(`%s`, (name, component) => {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime-utils/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export async function mountSuspended<T>(
}
if (methods && typeof methods === 'object') {
for (const key in methods) {
renderContext[key] = methods[key]
renderContext[key] = methods[key].bind(renderContext)
}
}
if (computed && typeof computed === 'object') {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime-utils/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export async function renderSuspended<T>(
}
if (methods && typeof methods === 'object') {
for (const key in methods) {
renderContext[key] = methods[key]
renderContext[key] = methods[key].bind(renderContext)
}
}
if (computed && typeof computed === 'object') {
Expand Down

0 comments on commit af57c3e

Please sign in to comment.