Skip to content

Commit fa59e33

Browse files
committed
test(settings): adapt dialog specs to script-setup migration
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
1 parent 5de9123 commit fa59e33

8 files changed

Lines changed: 89 additions & 62 deletions

apps/settings/src/components/UserList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
externalActions,
4848
onEditUser: openEditDialog,
4949
}"
50-
@scrollEnd="handleScrollEnd">
50+
@scroll-end="handleScrollEnd">
5151
<template #before>
5252
<caption class="hidden-visually">
5353
{{ t('settings', 'List of accounts. This list is not fully rendered for performance reasons. The accounts will be rendered as you navigate through the list.') }}

apps/settings/src/components/Users/EditUserDialog.spec.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,25 @@
66
import { mount } from '@vue/test-utils'
77
import { beforeEach, describe, expect, it, vi } from 'vitest'
88

9-
const confirmPassword = vi.hoisted(() => vi.fn())
9+
// `<script setup>` children and `useStore()` are invisible to VTU stubs/mocks,
10+
// so swap them via `vi.mock` (see dialogTestHelpers).
11+
const { confirmPassword, dispatch } = vi.hoisted(() => ({ confirmPassword: vi.fn(), dispatch: vi.fn() }))
12+
1013
vi.mock('@nextcloud/password-confirmation', () => ({ confirmPassword }))
1114
vi.mock('@nextcloud/dialogs', () => ({ showError: vi.fn(), showSuccess: vi.fn() }))
15+
vi.mock('../../store/index.js', () => ({
16+
useStore: () => ({
17+
dispatch,
18+
getters: {
19+
getGroups: [],
20+
getServerData: { languages: [], canChangePassword: true },
21+
getPasswordPolicyMinLength: 8,
22+
},
23+
}),
24+
}))
25+
vi.mock('@nextcloud/vue/components/NcDialog', async () => ({ default: (await import('./dialogTestHelpers.ts')).NcDialogStub }))
26+
vi.mock('@nextcloud/vue/components/NcButton', async () => ({ default: (await import('./dialogTestHelpers.ts')).NcButtonStub }))
27+
vi.mock('./UserFormFields.vue', async () => ({ default: (await import('./dialogTestHelpers.ts')).UserFormFieldsStub }))
1228

1329
// Decouple the dialog test from form-data diffing internals: always report a
1430
// non-empty change set so save() proceeds past its early return. Other exports
@@ -31,30 +47,14 @@ vi.mock('./userFormUtils.ts', async (importActual) => ({
3147

3248
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
3349
import EditUserDialog from './EditUserDialog.vue'
34-
import { flushPromises, NcButtonStub, NcDialogStub, UserFormFieldsStub } from './dialogTestHelpers.ts'
50+
import { flushPromises, NcDialogStub } from './dialogTestHelpers.ts'
3551

36-
function mountDialog({ dispatch = vi.fn() } = {}) {
52+
function mountDialog() {
3753
return mount(EditUserDialog, {
3854
propsData: {
3955
user: { id: 'bob', backendCapabilities: { setPassword: true } },
4056
quotaOptions: [],
4157
},
42-
mocks: {
43-
t: (_app: string, text: string) => text,
44-
$store: {
45-
dispatch,
46-
getters: {
47-
getGroups: [],
48-
getServerData: { languages: [], canChangePassword: true },
49-
getPasswordPolicyMinLength: 8,
50-
},
51-
},
52-
},
53-
stubs: {
54-
NcDialog: NcDialogStub,
55-
NcButton: NcButtonStub,
56-
UserFormFields: UserFormFieldsStub,
57-
},
5858
})
5959
}
6060

@@ -65,8 +65,8 @@ describe('EditUserDialog loading feedback', () => {
6565
})
6666

6767
it('does not dispatch a second save request while one is in flight', async () => {
68-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
69-
const wrapper = mountDialog({ dispatch })
68+
dispatch.mockReturnValue(new Promise(() => {}))
69+
const wrapper = mountDialog()
7070

7171
await wrapper.find('form').trigger('submit')
7272
await flushPromises()

apps/settings/src/components/Users/NewUserDialog.spec.ts

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,27 @@
55

66
import { mount } from '@vue/test-utils'
77
import { beforeEach, describe, expect, it, vi } from 'vitest'
8+
9+
// `<script setup>` children and `useStore()` are invisible to VTU stubs/mocks,
10+
// so swap them via `vi.mock` (see dialogTestHelpers).
11+
const { dispatch } = vi.hoisted(() => ({ dispatch: vi.fn() }))
12+
13+
vi.mock('../../store/index.js', () => ({
14+
useStore: () => ({
15+
dispatch,
16+
getters: {
17+
getServerData: { newUserGenerateUserID: false, newUserRequireEmail: false },
18+
getPasswordPolicyMinLength: 8,
19+
},
20+
}),
21+
}))
22+
vi.mock('@nextcloud/vue/components/NcDialog', async () => ({ default: (await import('./dialogTestHelpers.ts')).NcDialogStub }))
23+
vi.mock('@nextcloud/vue/components/NcButton', async () => ({ default: (await import('./dialogTestHelpers.ts')).NcButtonStub }))
24+
vi.mock('./UserFormFields.vue', async () => ({ default: (await import('./dialogTestHelpers.ts')).UserFormFieldsStub }))
25+
826
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
927
import NewUserDialog from './NewUserDialog.vue'
10-
import { flushPromises, NcButtonStub, NcDialogStub, UserFormFieldsStub } from './dialogTestHelpers.ts'
28+
import { flushPromises, NcDialogStub } from './dialogTestHelpers.ts'
1129

1230
function makeNewUser(overrides = {}) {
1331
return {
@@ -24,28 +42,13 @@ function makeNewUser(overrides = {}) {
2442
}
2543
}
2644

27-
function mountDialog({ dispatch = vi.fn(), loading = { all: false } } = {}) {
45+
function mountDialog({ loading = { all: false } } = {}) {
2846
return mount(NewUserDialog, {
2947
propsData: {
3048
loading,
3149
newUser: makeNewUser(),
3250
quotaOptions: [],
3351
},
34-
mocks: {
35-
t: (_app: string, text: string) => text,
36-
$store: {
37-
dispatch,
38-
getters: {
39-
getServerData: { newUserGenerateUserID: false, newUserRequireEmail: false },
40-
getPasswordPolicyMinLength: 8,
41-
},
42-
},
43-
},
44-
stubs: {
45-
NcDialog: NcDialogStub,
46-
NcButton: NcButtonStub,
47-
UserFormFields: UserFormFieldsStub,
48-
},
4952
})
5053
}
5154

@@ -55,8 +58,8 @@ describe('NewUserDialog loading feedback', () => {
5558
})
5659

5760
it('does not dispatch a second create request while one is in flight', async () => {
58-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
59-
const wrapper = mountDialog({ dispatch })
61+
dispatch.mockReturnValue(new Promise(() => {}))
62+
const wrapper = mountDialog()
6063

6164
await wrapper.find('form').trigger('submit')
6265
await wrapper.find('form').trigger('submit')
@@ -66,8 +69,8 @@ describe('NewUserDialog loading feedback', () => {
6669
})
6770

6871
it('marks the form as busy and inert while creating', async () => {
69-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
70-
const wrapper = mountDialog({ dispatch })
72+
dispatch.mockReturnValue(new Promise(() => {}))
73+
const wrapper = mountDialog()
7174

7275
await wrapper.find('form').trigger('submit')
7376

@@ -77,8 +80,8 @@ describe('NewUserDialog loading feedback', () => {
7780
})
7881

7982
it('shows a spinner and busy label on the submit button while creating', async () => {
80-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
81-
const wrapper = mountDialog({ dispatch })
83+
dispatch.mockReturnValue(new Promise(() => {}))
84+
const wrapper = mountDialog()
8285

8386
await wrapper.find('form').trigger('submit')
8487

@@ -87,8 +90,8 @@ describe('NewUserDialog loading feedback', () => {
8790
})
8891

8992
it('sets aria-disabled (not disabled) on the submit button while creating', async () => {
90-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
91-
const wrapper = mountDialog({ dispatch })
93+
dispatch.mockReturnValue(new Promise(() => {}))
94+
const wrapper = mountDialog()
9295
const submit = wrapper.find('[data-test="submit"]')
9396

9497
expect(submit.attributes('aria-disabled')).toBe('false')
@@ -101,8 +104,8 @@ describe('NewUserDialog loading feedback', () => {
101104
})
102105

103106
it('prevents closing the dialog while creating', async () => {
104-
const dispatch = vi.fn().mockReturnValue(new Promise(() => {}))
105-
const wrapper = mountDialog({ dispatch })
107+
dispatch.mockReturnValue(new Promise(() => {}))
108+
const wrapper = mountDialog()
106109
const dialog = wrapper.findComponent(NcDialogStub)
107110

108111
expect(dialog.props('noClose')).toBe(false)
@@ -114,9 +117,9 @@ describe('NewUserDialog loading feedback', () => {
114117

115118
it('re-enables the form when the request fails', async () => {
116119
const error = { response: { data: { ocs: { meta: { statuscode: 0 } } } } }
117-
const dispatch = vi.fn().mockRejectedValue(error)
120+
dispatch.mockRejectedValue(error)
118121
const loading = { all: false }
119-
const wrapper = mountDialog({ dispatch, loading })
122+
const wrapper = mountDialog({ loading })
120123

121124
await wrapper.find('form').trigger('submit')
122125
await flushPromises()

apps/settings/src/components/Users/dialogTestHelpers.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,23 @@
66
// Shared scaffolding for the NewUserDialog / EditUserDialog specs. The mount
77
// factories stay per-spec (they differ in props, store getters, and mocks);
88
// only the version-agnostic stubs and helpers live here.
9+
//
10+
// The dialogs use `<script setup>`, so child components resolve from local
11+
// imports and the store from `useStore()`. VTU's `stubs` and `mocks.$store`
12+
// reach neither, so each spec swaps them with `vi.mock`. Stubs are render
13+
// functions, not `template`: the runtime-only test build has no compiler.
914

10-
// The dialogs call the bare global `t`/`n` (injected at runtime by
11-
// core/src/globals.js) inside <script>. Provide identity stand-ins on import.
15+
// Minimal local typings so the stubs don't depend on a particular Vue version's
16+
// type exports (the IDE resolves bare `vue` to the Vue 3 types).
17+
type StubChild = unknown
18+
type CreateElement = (tag: string, data?: Record<string, unknown> | StubChild[], children?: StubChild[]) => StubChild
19+
interface StubVm {
20+
$attrs: Record<string, string>
21+
$scopedSlots: Record<string, ((props: object) => StubChild) | undefined>
22+
}
23+
24+
// The dialogs translate via the imported `translate`, but the store and some
25+
// helpers still call the bare globals. Provide identity stand-ins on import.
1226
globalThis.t = (_app: string, text: string) => text
1327
globalThis.n = (_app: string, singular: string, plural: string, count: number) => (count === 1 ? singular : plural)
1428

@@ -24,19 +38,29 @@ export const flushPromises = () => new Promise((resolve) => setTimeout(resolve))
2438
export const NcDialogStub = {
2539
name: 'NcDialog',
2640
props: ['name', 'size', 'noClose', 'closeOnClickOutside', 'outTransition'],
27-
template: '<div><slot /><div data-test="actions"><slot name="actions" /></div></div>',
41+
render(this: StubVm, h: CreateElement) {
42+
return h('div', [
43+
this.$scopedSlots.default?.({}),
44+
h('div', { attrs: { 'data-test': 'actions' } }, [this.$scopedSlots.actions?.({})]),
45+
])
46+
},
2847
}
2948

3049
/** Stub that forwards attrs (e.g. aria-disabled) and renders icon + default slots. */
3150
export const NcButtonStub = {
3251
name: 'NcButton',
3352
inheritAttrs: false,
34-
template: '<button v-bind="$attrs"><slot name="icon" /><slot /></button>',
53+
render(this: StubVm, h: CreateElement) {
54+
return h('button', { attrs: this.$attrs }, [
55+
this.$scopedSlots.icon?.({}),
56+
this.$scopedSlots.default?.({}),
57+
])
58+
},
3559
}
3660

3761
/** Stub exposing focusField, which the dialogs call on mount and on errors. */
3862
export const UserFormFieldsStub = {
3963
name: 'UserFormFields',
4064
methods: { focusField() {} },
41-
template: '<div />',
65+
render: (h: CreateElement) => h('div'),
4266
}

dist/core-common.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/core-common.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-users-management.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-users-management.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)