more component tests

This commit is contained in:
Gauthier Roebroeck 2025-06-30 15:00:46 +08:00
parent 82388ccbc7
commit 4a5450ac25
10 changed files with 214 additions and 5 deletions

View file

@ -3,8 +3,8 @@
<router-view />
<FragmentSnackQueue />
<FragmentDialogInstanceConfirmEdit />
<FragmentDialogInstanceConfirm />
<FragmentDialogConfirmEdit />
<FragmentDialogConfirm />
</v-app>
</template>

View file

@ -19,6 +19,7 @@ declare module 'vue' {
FragmentLocaleSelector: typeof import('./fragments/fragment/LocaleSelector.vue')['default']
FragmentSnackQueue: typeof import('./fragments/fragment/SnackQueue.vue')['default']
FragmentThemeSelector: typeof import('./fragments/fragment/ThemeSelector.vue')['default']
FragmentUserFormCreateEdit: typeof import('./fragments/fragment/user/form/CreateEdit.vue')['default']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
LayoutAppBar: typeof import('./fragments/layout/app/Bar.vue')['default']
LayoutAppDrawer: typeof import('./fragments/layout/app/drawer/Drawer.vue')['default']

View file

@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import ChangePassword from './ChangePassword.vue'
import { expect, waitFor } from 'storybook/test'
const meta = {
component: ChangePassword,
render: (args: object) => ({
components: { ChangePassword },
setup() {
return { args }
},
template: '<ChangePassword />',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
},
args: {},
} satisfies Meta<typeof ChangePassword>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}
export const MatchingPasswords: Story = {
args: {},
play: async ({ canvas, userEvent }) => {
const password1 = canvas.getByLabelText(/new password/i, {
selector: 'input',
})
await userEvent.type(password1, 'abc')
const password2 = canvas.getByLabelText(/confirm password/i, {
selector: 'input',
})
await userEvent.type(password2, 'abc')
await waitFor(() => expect(canvas.getByText(/must be identical/i)).not.toBeVisible())
},
}
export const DifferentPasswords: Story = {
args: {},
play: async ({ canvas, userEvent }) => {
const password1 = canvas.getByLabelText(/new password/i, {
selector: 'input',
})
await userEvent.type(password1, 'abc')
const password2 = canvas.getByLabelText(/confirm password/i, {
selector: 'input',
})
await userEvent.type(password2, 'def')
await waitFor(() => expect(canvas.getByText(/must be identical/i)).toBeVisible())
},
}

View file

@ -44,7 +44,7 @@
</template>
<script setup lang="ts">
const newPassword = defineModel<string>({ required: true })
const newPassword = defineModel<string>()
const confirmPassword = ref<string>()
const showPassword = ref<boolean>(false)

View file

@ -0,0 +1,61 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import CreateEdit from './CreateEdit.vue'
import { UserRoles } from '@/types/UserRoles'
const meta = {
component: CreateEdit,
render: (args: object) => ({
components: { CreateEdit },
setup() {
return { args }
},
template: '<CreateEdit :model-value="args.modelValue" v-bind="args"/>',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
},
args: {},
} satisfies Meta<typeof CreateEdit>
export default meta
type Story = StoryObj<typeof meta>
export const CreateUser: Story = {
args: {
modelValue: {
email: '',
password: '',
roles: [UserRoles.PAGE_STREAMING, UserRoles.FILE_DOWNLOAD],
sharedLibraries: {
all: true,
libraryIds: ['1', '2'],
},
ageRestriction: {
age: 0,
restriction: 'NONE',
},
},
},
}
export const UpdateUser: Story = {
args: {
modelValue: {
id: '123',
email: 'user@example.org',
password: 'masked',
roles: [UserRoles.KOBO_SYNC, UserRoles.FILE_DOWNLOAD],
sharedLibraries: {
all: false,
libraryIds: ['1'],
},
ageRestriction: {
age: 12,
restriction: 'ALLOW_ONLY',
},
labelsAllow: ['kids'],
labelsExclude: ['teens'],
},
},
}

View file

@ -2,8 +2,16 @@ import { actuatorHandlers } from '@/mocks/api/handlers/actuator'
import { announcementHandlers } from '@/mocks/api/handlers/announcements'
import { releasesHandlers } from '@/mocks/api/handlers/releases'
import { HttpResponse } from 'msw'
import { librariesHandlers } from '@/mocks/api/handlers/libraries'
import { referentialHandlers } from '@/mocks/api/handlers/referential'
export const handlers = [...actuatorHandlers, ...announcementHandlers, ...releasesHandlers]
export const handlers = [
...librariesHandlers,
...referentialHandlers,
...actuatorHandlers,
...announcementHandlers,
...releasesHandlers,
]
export const response401Unauthorized = () =>
HttpResponse.json({ error: 'Unauthorized' }, { status: 401 })

View file

@ -0,0 +1,72 @@
import { httpTyped } from '@/mocks/api/httpTyped'
export const libraries = [
{
id: '1',
name: 'eBooks',
root: '/ebooks',
importComicInfoBook: true,
importComicInfoSeries: true,
importComicInfoCollection: true,
importComicInfoReadList: true,
importComicInfoSeriesAppendVolume: true,
importEpubBook: true,
importEpubSeries: true,
importMylarSeries: true,
importLocalArtwork: true,
importBarcodeIsbn: false,
scanForceModifiedTime: false,
scanInterval: 'DISABLED',
scanOnStartup: false,
scanCbx: true,
scanPdf: true,
scanEpub: true,
scanDirectoryExclusions: ['#recycle', '@Recycle', '@eaDir'],
repairExtensions: false,
convertToCbz: false,
emptyTrashAfterScan: true,
seriesCover: 'FIRST',
hashFiles: true,
hashPages: false,
hashKoreader: false,
analyzeDimensions: false,
oneshotsDirectory: '_oneshots',
unavailable: false,
},
{
id: '2',
name: 'Comics',
root: '/books',
importComicInfoBook: true,
importComicInfoSeries: true,
importComicInfoCollection: true,
importComicInfoReadList: true,
importComicInfoSeriesAppendVolume: true,
importEpubBook: true,
importEpubSeries: true,
importMylarSeries: true,
importLocalArtwork: true,
importBarcodeIsbn: true,
scanForceModifiedTime: false,
scanInterval: 'DISABLED',
scanOnStartup: false,
scanCbx: true,
scanPdf: true,
scanEpub: true,
scanDirectoryExclusions: ['#recycle', '@Recycle', '@eaDir'],
repairExtensions: false,
convertToCbz: false,
emptyTrashAfterScan: true,
seriesCover: 'FIRST_UNREAD_OR_FIRST',
hashFiles: true,
hashPages: false,
hashKoreader: false,
analyzeDimensions: true,
oneshotsDirectory: null,
unavailable: false,
},
]
export const librariesHandlers = [
httpTyped.get('/api/v1/libraries', ({ response }) => response(200).json(libraries)),
]

View file

@ -0,0 +1,7 @@
import { httpTyped } from '@/mocks/api/httpTyped'
export const sharingLabels = ['kids', 'teens']
export const referentialHandlers = [
httpTyped.get('/api/v1/sharing-labels', ({ response }) => response(200).json(sharingLabels)),
]

View file

@ -99,7 +99,7 @@ import { useMessagesStore } from '@/stores/messages'
import { useIntl } from 'vue-intl'
import { useDisplay } from 'vuetify'
import UserDeletionWarning from '@/components/user/DeletionWarning.vue'
import UserFormCreateEdit from '@/components/user/form/CreateEdit.vue'
import UserFormCreateEdit from '@/fragments/fragment/user/form/CreateEdit.vue'
import UserFormChangePassword from '@/components/user/form/ChangePassword.vue'
const intl = useIntl()