mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
chore: add missing tests
This commit is contained in:
parent
221e46c1b9
commit
bfc807cfb9
20 changed files with 215 additions and 29 deletions
|
@ -147,6 +147,8 @@ export default abstract class UnitTestCase {
|
|||
customer_email: 'Koel Plus',
|
||||
product_id: 'koel-plus',
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
protected disablePlusEdition () {
|
||||
|
@ -157,6 +159,8 @@ export default abstract class UnitTestCase {
|
|||
customer_email: '',
|
||||
product_id: '',
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
protected stub (testId = 'stub') {
|
||||
|
|
|
@ -30,6 +30,9 @@ import Btn from '@/components/ui/form/Btn.vue'
|
|||
import TextInput from '@/components/ui/form/TextInput.vue'
|
||||
import FormRow from '@/components/ui/form/FormRow.vue'
|
||||
|
||||
const { handleHttpError } = useErrorHandler()
|
||||
const { toastSuccess } = useMessageToaster()
|
||||
|
||||
const emit = defineEmits<{ (e: 'cancel'): void }>()
|
||||
const email = ref('')
|
||||
const loading = ref(false)
|
||||
|
@ -43,9 +46,9 @@ const requestResetPasswordLink = async () => {
|
|||
try {
|
||||
loading.value = true
|
||||
await authService.requestResetPasswordLink(email.value)
|
||||
useMessageToaster().toastSuccess('Password reset link sent. Please check your email.')
|
||||
toastSuccess('Password reset link sent. Please check your email.')
|
||||
} catch (error: unknown) {
|
||||
useErrorHandler().handleHttpError(error, { 404: 'No user with this email address found.' })
|
||||
handleHttpError(error, { 404: 'No user with this email address found.' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { screen, waitFor } from '@testing-library/vue'
|
|||
import { expect, it, Mock } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { authService } from '@/services'
|
||||
import { logger } from '@/utils'
|
||||
import LoginFrom from './LoginForm.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
|
@ -25,12 +26,14 @@ new class extends UnitTestCase {
|
|||
})
|
||||
|
||||
it('fails to log in', async () => {
|
||||
const mock = this.mock(authService, 'login').mockRejectedValue(new Error('Unauthenticated'))
|
||||
const mock = this.mock(authService, 'login').mockRejectedValue('Unauthenticated')
|
||||
const logMock = this.mock(logger, 'error')
|
||||
const { emitted } = await this.submitForm(mock)
|
||||
await this.tick()
|
||||
|
||||
expect(emitted().loggedin).toBeFalsy()
|
||||
expect(screen.getByTestId('login-form').classList.contains('error')).toBe(true)
|
||||
expect(logMock).toHaveBeenCalledWith('Unauthenticated')
|
||||
})
|
||||
|
||||
it('shows forgot password form', async () => {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { commonStore } from '@/stores'
|
||||
import AboutKoelButton from './AboutKoelButton.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('shows no notification when no new available available', () => {
|
||||
commonStore.state.current_version = '1.0.0'
|
||||
commonStore.state.latest_version = '1.0.0'
|
||||
|
||||
this.beAdmin().render(AboutKoelButton)
|
||||
expect(screen.queryByTitle('New version available!')).toBeNull()
|
||||
expect(screen.queryByTestId('new-version-indicator')).toBeNull()
|
||||
screen.getByTitle('About Koel')
|
||||
})
|
||||
|
||||
it('shows notification when new version available', () => {
|
||||
commonStore.state.current_version = '1.0.0'
|
||||
commonStore.state.latest_version = '1.0.1'
|
||||
|
||||
this.beAdmin().render(AboutKoelButton)
|
||||
screen.getByTitle('New version available!')
|
||||
screen.getByTestId('new-version-indicator')
|
||||
})
|
||||
|
||||
it('doesn\'t show notification to non-admin users', () => {
|
||||
commonStore.state.current_version = '1.0.0'
|
||||
commonStore.state.latest_version = '1.0.1'
|
||||
|
||||
this.be().render(AboutKoelButton)
|
||||
expect(screen.queryByTitle('New version available!')).toBeNull()
|
||||
expect(screen.queryByTestId('new-version-indicator')).toBeNull()
|
||||
screen.getByTitle('About Koel')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
<span
|
||||
v-if="shouldNotifyNewVersion"
|
||||
class="absolute w-[10px] aspect-square right-px top-px rounded-full bg-k-highlight"
|
||||
data-testid="new-version-indicator"
|
||||
/>
|
||||
</ExtraDrawerButton>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { eventBus } from '@/utils'
|
||||
import LogoutButton from './LogoutButton.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('emits the logout event', async () => {
|
||||
const emitMock = this.mock(eventBus, 'emit')
|
||||
this.render(LogoutButton)
|
||||
|
||||
await this.user.click(screen.getByRole('button'))
|
||||
|
||||
expect(emitMock).toHaveBeenCalledWith('LOG_OUT')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ const current = ref(false)
|
|||
const { onRouteChanged } = useRouter()
|
||||
|
||||
if (screen) {
|
||||
onRouteChanged(route => current.value = route.screen === props.screen)
|
||||
onRouteChanged(route => (current.value = route.screen === props.screen))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import SidebarManageSection from './SidebarManageSection.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('shows all menu items if current user is an admin', () => {
|
||||
this.beAdmin().render(SidebarManageSection)
|
||||
screen.getByText('Settings')
|
||||
screen.getByText('Users')
|
||||
screen.getByText('Upload')
|
||||
})
|
||||
|
||||
it('shows nothing if current user is not an admin', () => {
|
||||
this.be().render(SidebarManageSection)
|
||||
expect(screen.queryByText('Settings')).toBeNull()
|
||||
expect(screen.queryByText('Upload')).toBeNull()
|
||||
expect(screen.queryByText('Users')).toBeNull()
|
||||
})
|
||||
|
||||
it('shows only the upload menu item if current user is a Plus user', () => {
|
||||
this.be().enablePlusEdition().render(SidebarManageSection)
|
||||
screen.getByText('Upload')
|
||||
expect(screen.queryByText('Settings')).toBeNull()
|
||||
expect(screen.queryByText('Users')).toBeNull()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
Settings
|
||||
</SidebarItem>
|
||||
<SidebarItem screen="Upload" href="#/upload">
|
||||
<SidebarItem v-if="allowsUpload" screen="Upload" href="#/upload">
|
||||
<template #icon>
|
||||
<Icon :icon="faUpload" fixed-width />
|
||||
</template>
|
||||
|
@ -28,11 +28,12 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { faTools, faUpload, faUsers } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useAuthorization } from '@/composables'
|
||||
import { useAuthorization, useUpload } from '@/composables'
|
||||
|
||||
import SidebarSection from '@/components/layout/main-wrapper/sidebar/SidebarSection.vue'
|
||||
import SidebarSectionHeader from '@/components/layout/main-wrapper/sidebar/SidebarSectionHeader.vue'
|
||||
import SidebarItem from '@/components/layout/main-wrapper/sidebar/SidebarItem.vue'
|
||||
|
||||
const { isAdmin } = useAuthorization()
|
||||
const { allowsUpload } = useUpload()
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import SidebarToggleButton from './SidebarToggleButton.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('emits the toggle event', () => {
|
||||
const { emitted } = this.render(SidebarToggleButton)
|
||||
this.trigger(screen.getByRole('checkbox'), 'click')
|
||||
expect(emitted()['update:modelValue']).toBeTruthy()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { screen } from '@testing-library/vue'
|
||||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import SidebarYourMusicSection from './SidebarYourMusicSection.vue'
|
||||
import { eventBus } from '@/utils'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('shows YouTube item if a video is played', async () => {
|
||||
this.render(SidebarYourMusicSection)
|
||||
expect(screen.queryByTestId('youtube')).toBeNull()
|
||||
|
||||
eventBus.emit('PLAY_YOUTUBE_VIDEO', {
|
||||
id: 'video-id',
|
||||
title: 'Another One Bites the Dust',
|
||||
})
|
||||
|
||||
await this.tick()
|
||||
screen.getByTestId('youtube')
|
||||
screen.getByText('Another One Bites the Dust')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -36,16 +36,18 @@
|
|||
</template>
|
||||
Genres
|
||||
</SidebarItem>
|
||||
<YouTubeSidebarItem v-show="showYouTube" />
|
||||
<YouTubeSidebarItem v-if="youtubeVideoTitle" data-testid="youtube">
|
||||
{{ youtubeVideoTitle }}
|
||||
</YouTubeSidebarItem>
|
||||
</ul>
|
||||
</SidebarSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { faCompactDisc, faHome, faMicrophone, faMusic, faTags } from '@fortawesome/free-solid-svg-icons'
|
||||
import { computed, ref } from 'vue'
|
||||
import { unescape } from 'lodash'
|
||||
import { ref } from 'vue'
|
||||
import { eventBus } from '@/utils'
|
||||
import { useThirdPartyServices } from '@/composables'
|
||||
|
||||
import SidebarSection from '@/components/layout/main-wrapper/sidebar/SidebarSection.vue'
|
||||
import SidebarSectionHeader from '@/components/layout/main-wrapper/sidebar/SidebarSectionHeader.vue'
|
||||
|
@ -53,10 +55,7 @@ import SidebarItem from '@/components/layout/main-wrapper/sidebar/SidebarItem.vu
|
|||
import QueueSidebarItem from '@/components/layout/main-wrapper/sidebar/QueueSidebarItem.vue'
|
||||
import YouTubeSidebarItem from '@/components/layout/main-wrapper/sidebar/YouTubeSidebarItem.vue'
|
||||
|
||||
const { useYouTube } = useThirdPartyServices()
|
||||
const youtubeVideoTitle = ref<string | null>(null)
|
||||
|
||||
const youTubePlaying = ref(false)
|
||||
const showYouTube = computed(() => useYouTube.value && youTubePlaying.value)
|
||||
|
||||
eventBus.on('PLAY_YOUTUBE_VIDEO', () => (youTubePlaying.value = true))
|
||||
eventBus.on('PLAY_YOUTUBE_VIDEO', payload => (youtubeVideoTitle.value = unescape(payload.title)))
|
||||
</script>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { eventBus } from '@/utils'
|
||||
import YouTubeSidebarItem from './YouTubeSidebarItem.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('renders', async () => {
|
||||
const { html } = this.render(YouTubeSidebarItem)
|
||||
|
||||
eventBus.emit('PLAY_YOUTUBE_VIDEO', { id: '123', title: 'A Random Video' })
|
||||
await this.tick()
|
||||
const { html } = this.render(YouTubeSidebarItem, {
|
||||
slots: {
|
||||
default: 'Another One Bites the Dust',
|
||||
},
|
||||
})
|
||||
|
||||
expect(html()).toMatchSnapshot()
|
||||
})
|
||||
|
|
|
@ -3,19 +3,12 @@
|
|||
<template #icon>
|
||||
<Icon :icon="faYoutube" fixed-width />
|
||||
</template>
|
||||
{{ title }}
|
||||
<slot />
|
||||
</SidebarItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { unescape } from 'lodash'
|
||||
import { faYoutube } from '@fortawesome/free-brands-svg-icons'
|
||||
import { ref } from 'vue'
|
||||
import { eventBus } from '@/utils'
|
||||
|
||||
import SidebarItem from './SidebarItem.vue'
|
||||
|
||||
const title = ref('')
|
||||
|
||||
eventBus.on('PLAY_YOUTUBE_VIDEO', payload => (title.value = unescape(payload.title)))
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders 1`] = `<li data-v-7589e0e3="" class="relative before:right-0 px-6 before:top-1/4 before:w-[4px] before:h-1/2 before:absolute before:rounded-full before:transition-[box-shadow,_background-color] before:ease-in-out before:duration-500" data-testid="sidebar-item"><a data-v-7589e0e3="" href="#/youtube" class="flex items-center overflow-x-hidden gap-3 h-11 relative active:pt-0.5 active:pr-0 active:pb-0 active:pl-0.5 !text-k-text-secondary hover:!text-k-text-primary"><span data-v-7589e0e3="" class="opacity-70"><br data-testid="Icon" icon="[object Object]" fixed-width=""></span><span data-v-7589e0e3="" class="overflow-hidden text-ellipsis whitespace-nowrap"> A Random Video</span></a></li>`;
|
||||
exports[`renders 1`] = `<li data-v-7589e0e3="" class="relative before:right-0 px-6 before:top-1/4 before:w-[4px] before:h-1/2 before:absolute before:rounded-full before:transition-[box-shadow,_background-color] before:ease-in-out before:duration-500" data-testid="sidebar-item"><a data-v-7589e0e3="" href="#/youtube" class="flex items-center overflow-x-hidden gap-3 h-11 relative active:pt-0.5 active:pr-0 active:pb-0 active:pl-0.5 !text-k-text-secondary hover:!text-k-text-primary"><span data-v-7589e0e3="" class="opacity-70"><br data-testid="Icon" icon="[object Object]" fixed-width=""></span><span data-v-7589e0e3="" class="overflow-hidden text-ellipsis whitespace-nowrap">Another One Bites the Dust</span></a></li>`;
|
||||
|
|
26
resources/assets/js/components/meta/CreditsBlock.spec.ts
Normal file
26
resources/assets/js/components/meta/CreditsBlock.spec.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { expect, it } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { http } from '@/services'
|
||||
import CreditsBlock from './CreditsBlock.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('renders the credits', async () => {
|
||||
window.IS_DEMO = true
|
||||
|
||||
const getMock = this.mock(http, 'get').mockResolvedValue([
|
||||
{ name: 'Foo', url: 'https://foo.com' },
|
||||
{ name: 'Bar', url: 'https://bar.com' },
|
||||
{ name: 'Something Else', url: 'https://something-else.net' }
|
||||
])
|
||||
|
||||
const { html } = this.render(CreditsBlock)
|
||||
|
||||
await this.tick(3)
|
||||
expect(html()).toMatchSnapshot()
|
||||
expect(getMock).toHaveBeenCalledWith('demo/credits')
|
||||
|
||||
window.IS_DEMO = false
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders the credits 1`] = `
|
||||
<div data-v-7d3b4fcd="" class="max-h-[9rem] overflow-auto" data-testid="demo-credits"> Music by <ul data-v-7d3b4fcd="" class="inline">
|
||||
<li data-v-7d3b4fcd="" class="inline"><a data-v-7d3b4fcd="" href="https://bar.com" target="_blank">Bar</a></li>
|
||||
<li data-v-7d3b4fcd="" class="inline"><a data-v-7d3b4fcd="" href="https://foo.com" target="_blank">Foo</a></li>
|
||||
<li data-v-7d3b4fcd="" class="inline"><a data-v-7d3b4fcd="" href="https://something-else.net" target="_blank">Something Else</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,20 @@
|
|||
import { expect, it, vi } from 'vitest'
|
||||
import UnitTestCase from '@/__tests__/UnitTestCase'
|
||||
import { authService } from '@/services'
|
||||
import QRLogin from '@/components/profile-preferences/QRLogin.vue'
|
||||
|
||||
new class extends UnitTestCase {
|
||||
protected test () {
|
||||
it('renders', async () => {
|
||||
vi.mock('@vueuse/integrations/useQRCode', () => ({
|
||||
useQRCode: () => 'data:image/png;base64,my-qr-code'
|
||||
}))
|
||||
|
||||
const getTokenMock = this.mock(authService, 'getOneTimeToken').mockResolvedValue('my-token')
|
||||
const { html } = this.render(QRLogin)
|
||||
|
||||
expect(getTokenMock).toHaveBeenCalled()
|
||||
expect(html()).toMatchSnapshot()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`renders 1`] = `<article class="text-k-text-secondary"> Instead of using a password, you can scan the QR code below to log in to <a href="https://koel.dev/#mobile" target="_blank" class="text-k-highlight">Koel Player</a> on your mobile device.<br> The QR code will refresh every 10 minutes. <img class="mt-4 rounded-4" src="data:image/png;base64,my-qr-code" alt="QR Code" width="192" height="192"></article>`;
|
|
@ -16,7 +16,8 @@
|
|||
}
|
||||
},
|
||||
"include": [
|
||||
"js/**/*"
|
||||
"js/**/*",
|
||||
"../../node_modules/vitest/globals.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"js/__tests__/**/*"
|
||||
|
|
Loading…
Reference in a new issue