chore(test): improve client unit tests with user-event and screen (#1606)

This commit is contained in:
Phan An 2022-11-29 11:18:58 +01:00 committed by GitHub
parent 7e327f5a0e
commit 20bded3bca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 904 additions and 679 deletions

View file

@ -43,8 +43,8 @@
"@babel/preset-env": "^7.9.6",
"@faker-js/faker": "^6.2.0",
"@floating-ui/dom": "^1.0.3",
"@testing-library/cypress": "^8.0.2",
"@testing-library/vue": "^6.5.1",
"@testing-library/user-event": "^14.4.3",
"@testing-library/vue": "^6.6.1",
"@types/axios": "^0.14.0",
"@types/blueimp-md5": "^2.7.0",
"@types/local-storage": "^1.4.0",

View file

@ -10,6 +10,8 @@ import { DialogBoxKey, MessageToasterKey, OverlayKey, RouterKey } from '@/symbol
import { DialogBoxStub, MessageToasterStub, OverlayStub } from '@/__tests__/stubs'
import { routes } from '@/config'
import Router from '@/router'
import userEvent from '@testing-library/user-event'
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'
// A deep-merge function that
// - supports symbols as keys (_.merge doesn't)
@ -27,10 +29,13 @@ const deepMerge = (first: object, second: object) => {
export default abstract class UnitTestCase {
private backupMethods = new Map()
protected router: Router
protected user: UserEvent
public constructor () {
this.router = new Router(routes)
this.mock(http, 'request') // prevent actual HTTP requests from being made
this.user = userEvent.setup({ delay: null }) // @see https://github.com/testing-library/user-event/issues/833
this.beforeEach()
this.afterEach()
this.test()
@ -150,5 +155,10 @@ export default abstract class UnitTestCase {
})
}
protected async type (element: HTMLElement, value: string) {
await this.user.clear(element)
await this.user.type(element, value)
}
protected abstract test ()
}

View file

@ -1,10 +1,10 @@
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import { downloadService, playbackService } from '@/services'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import AlbumCard from './AlbumCard.vue'
import { songStore } from '@/stores'
import { commonStore, songStore } from '@/stores'
let album: Album
@ -34,20 +34,27 @@ new class extends UnitTestCase {
it('downloads', async () => {
const mock = this.mock(downloadService, 'fromAlbum')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId('download-album'))
await this.user.click(screen.getByTitle('Download all songs in the album IV'))
expect(mock).toHaveBeenCalledTimes(1)
})
it('does not have an option to download if downloading is disabled', async () => {
commonStore.state.allow_download = false
this.renderComponent()
expect(screen.queryByText('Download')).toBeNull()
})
it('shuffles', async () => {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const shuffleMock = this.mock(playbackService, 'queueAndPlay').mockResolvedValue(void 0)
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId('shuffle-album'))
await this.user.click(screen.getByTitle('Shuffle all songs in the album IV'))
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(album)

View file

@ -18,7 +18,6 @@
<a
:title="`Shuffle all songs in the album ${album.name}`"
class="shuffle-album"
data-testid="shuffle-album"
href
role="button"
@click.prevent="shuffle"
@ -29,7 +28,6 @@
v-if="allowDownload"
:title="`Download all songs in the album ${album.name}`"
class="download-album"
data-testid="download-album"
href
role="button"
@click.prevent="download"

View file

@ -1,4 +1,5 @@
import { expect, it } from 'vitest'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import factory from '@/__tests__/factory'
import { eventBus } from '@/utils'
@ -15,25 +16,22 @@ new class extends UnitTestCase {
})
const rendered = this.render(AlbumContextMenu)
eventBus.emit('ALBUM_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, album)
eventBus.emit('ALBUM_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent, album)
await this.tick(2)
return rendered
}
protected test () {
it('renders', async () => {
const { html } = await this.renderComponent()
expect(html()).toMatchSnapshot()
})
it('renders', async () => expect((await this.renderComponent()).html()).toMatchSnapshot())
it('plays all', async () => {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByText } = await this.renderComponent()
await getByText('Play All').click()
await this.renderComponent()
await this.user.click(screen.getByText('Play All'))
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(album)
@ -45,8 +43,8 @@ new class extends UnitTestCase {
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByText } = await this.renderComponent()
await getByText('Shuffle All').click()
await this.renderComponent()
await this.user.click(screen.getByText('Shuffle All'))
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(album)
@ -55,42 +53,42 @@ new class extends UnitTestCase {
it('downloads', async () => {
const downloadMock = this.mock(downloadService, 'fromAlbum')
await this.renderComponent()
const { getByText } = await this.renderComponent()
await getByText('Download').click()
await this.user.click(screen.getByText('Download'))
expect(downloadMock).toHaveBeenCalledWith(album)
})
it('does not have an option to download if downloading is disabled', async () => {
commonStore.state.allow_download = false
const { queryByText } = await this.renderComponent()
await this.renderComponent()
expect(queryByText('Download')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
it('goes to album', async () => {
const mock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await getByText('Go to Album').click()
await this.user.click(screen.getByText('Go to Album'))
expect(mock).toHaveBeenCalledWith(`album/${album.id}`)
})
it('does not have an option to download or go to Unknown Album and Artist', async () => {
const { queryByTestId } = await this.renderComponent(factory.states('unknown')<Album>('album'))
await this.renderComponent(factory.states('unknown')<Album>('album'))
expect(queryByTestId('view-album')).toBeNull()
expect(queryByTestId('view-artist')).toBeNull()
expect(queryByTestId('download')).toBeNull()
expect(screen.queryByText('Go to Album')).toBeNull()
expect(screen.queryByText('Go to Artist')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
it('goes to artist', async () => {
const mock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await getByText('Go to Artist').click()
await this.user.click(screen.getByText('Go to Artist'))
expect(mock).toHaveBeenCalledWith(`artist/${album.artist_id}`)
})

View file

@ -1,14 +1,14 @@
<template>
<ContextMenuBase ref="base" data-testid="album-context-menu" extra-class="album-menu">
<template v-if="album">
<li data-testid="play" @click="play">Play All</li>
<li data-testid="shuffle" @click="shuffle">Shuffle All</li>
<li @click="play">Play All</li>
<li @click="shuffle">Shuffle All</li>
<li class="separator"></li>
<li v-if="isStandardAlbum" data-testid="view-album" @click="viewAlbumDetails">Go to Album</li>
<li v-if="isStandardArtist" data-testid="view-artist" @click="viewArtistDetails">Go to Artist</li>
<li v-if="isStandardAlbum" @click="viewAlbumDetails">Go to Album</li>
<li v-if="isStandardArtist" @click="viewArtistDetails">Go to Artist</li>
<template v-if="isStandardAlbum && allowDownload">
<li class="separator"></li>
<li data-testid="download" @click="download">Download</li>
<li @click="download">Download</li>
</template>
</template>
</ContextMenuBase>

View file

@ -1,8 +1,8 @@
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { commonStore, songStore } from '@/stores'
import { fireEvent } from '@testing-library/vue'
import { mediaInfoService, playbackService } from '@/services'
import AlbumInfoComponent from './AlbumInfo.vue'
@ -40,44 +40,44 @@ new class extends UnitTestCase {
protected test () {
it.each<[MediaInfoDisplayMode]>([['aside'], ['full']])('renders in %s mode', async (mode) => {
const { getByTestId, queryByTestId } = await this.renderComponent(mode)
await this.renderComponent(mode)
getByTestId('album-info-tracks')
screen.getByTestId('album-info-tracks')
if (mode === 'aside') {
getByTestId('thumbnail')
screen.getByTestId('thumbnail')
} else {
expect(queryByTestId('thumbnail')).toBeNull()
expect(screen.queryByTestId('thumbnail')).toBeNull()
}
expect(getByTestId('album-info').classList.contains(mode)).toBe(true)
expect(screen.getByTestId('album-info').classList.contains(mode)).toBe(true)
})
it('triggers showing full wiki for aside mode', async () => {
const { getByTestId, queryByTestId } = await this.renderComponent('aside')
expect(queryByTestId('full')).toBeNull()
await this.renderComponent('aside')
expect(screen.queryByTestId('full')).toBeNull()
await fireEvent.click(getByTestId('more-btn'))
await this.user.click(screen.getByRole('button', { name: 'Full Wiki' }))
expect(queryByTestId('summary')).toBeNull()
expect(queryByTestId('full')).not.toBeNull()
expect(screen.queryByTestId('summary')).toBeNull()
screen.getByTestId('full')
})
it('shows full wiki for full mode', async () => {
const { queryByTestId } = await this.renderComponent('full')
await this.renderComponent('full')
expect(queryByTestId('full')).not.toBeNull()
expect(queryByTestId('summary')).toBeNull()
expect(queryByTestId('more-btn')).toBeNull()
screen.getByTestId('full')
expect(screen.queryByTestId('summary')).toBeNull()
expect(screen.queryByRole('button', { name: 'Full Wiki' })).toBeNull()
})
it('plays', async () => {
const songs = factory<Song>('song', 3)
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByTitle } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByTitle('Play all songs in IV'))
await this.user.click(screen.getByTitle('Play all songs in IV'))
await this.tick(2)
expect(fetchMock).toHaveBeenCalledWith(album)

View file

@ -15,7 +15,7 @@
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.wiki.summary"/>
<div v-if="showFull" class="full" data-testid="full" v-html="info.wiki.full"/>
<button v-if="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullWiki = true">
<button v-if="showSummary" class="more" @click.prevent="showingFullWiki = true">
Full Wiki
</button>
</div>

View file

@ -1,3 +1,4 @@
import { screen } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
@ -10,7 +11,7 @@ new class extends UnitTestCase {
const album = factory<Album>('album')
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(factory<Song>('song', 5))
const { queryAllByTestId } = this.render(AlbumTrackList, {
this.render(AlbumTrackList, {
props: {
album,
tracks: factory<AlbumTrack>('album-track', 3)
@ -20,7 +21,7 @@ new class extends UnitTestCase {
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(album)
expect(queryAllByTestId('album-track-item')).toHaveLength(3)
expect(screen.queryAllByTestId('album-track-item')).toHaveLength(3)
})
}
}

View file

@ -1,4 +1,4 @@
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { queueStore, songStore } from '@/stores'
@ -45,9 +45,9 @@ new class extends UnitTestCase {
const queueMock = this.mock(queueStore, 'queueIfNotQueued')
const playMock = this.mock(playbackService, 'play')
const { getByTitle } = this.renderComponent(matchedSong)
this.renderComponent(matchedSong)
await fireEvent.click(getByTitle('Click to play'))
await this.user.click(screen.getByTitle('Click to play'))
expect(queueMock).toHaveBeenNthCalledWith(1, matchedSong)
expect(playMock).toHaveBeenNthCalledWith(1, matchedSong)

View file

@ -4,7 +4,7 @@ exports[`renders 1`] = `
<article class="item full" draggable="true" tabindex="0" title="IV by Led Zeppelin" data-v-f01bdc56=""><br data-testid="thumbnail" entity="[object Object]" data-v-f01bdc56="">
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/album/42" class="text-normal" data-testid="name">IV</a><a href="#/artist/17">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs in the album IV" class="shuffle-album" data-testid="shuffle-album" href="" role="button"> Shuffle </a><a title="Download all songs in the album IV" class="download-album" data-testid="download-album" href="" role="button"> Download </a></p>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs in the album IV" class="shuffle-album" href="" role="button"> Shuffle </a><a title="Download all songs in the album IV" class="download-album" href="" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -3,13 +3,13 @@
exports[`renders 1`] = `
<nav class="album-menu menu context-menu" style="top: 42px; left: 420px;" tabindex="0" data-testid="album-context-menu" data-v-0408531a="">
<ul data-v-0408531a="">
<li data-testid="play">Play All</li>
<li data-testid="shuffle">Shuffle All</li>
<li>Play All</li>
<li>Shuffle All</li>
<li class="separator"></li>
<li data-testid="view-album">Go to Album</li>
<li data-testid="view-artist">Go to Artist</li>
<li>Go to Album</li>
<li>Go to Artist</li>
<li class="separator"></li>
<li data-testid="download">Download</li>
<li>Download</li>
</ul>
</nav>
`;

View file

@ -1,4 +1,4 @@
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { downloadService, playbackService } from '@/services'
@ -36,19 +36,17 @@ new class extends UnitTestCase {
it('downloads', async () => {
const mock = this.mock(downloadService, 'fromArtist')
this.renderComponent()
const { getByTestId } = this.renderComponent()
await fireEvent.click(getByTestId('download-artist'))
await this.user.click(screen.getByTitle('Download all songs by Led Zeppelin'))
expect(mock).toHaveBeenCalledOnce()
})
it('does not have an option to download if downloading is disabled', async () => {
commonStore.state.allow_download = false
this.renderComponent()
const { queryByTestId } = this.renderComponent()
expect(queryByTestId('download-artist')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
it('shuffles', async () => {
@ -56,9 +54,9 @@ new class extends UnitTestCase {
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId('shuffle-artist'))
await this.user.click(screen.getByTitle('Shuffle all songs by Led Zeppelin'))
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(artist)

View file

@ -15,7 +15,6 @@
<a
:title="`Shuffle all songs by ${artist.name}`"
class="shuffle-artist"
data-testid="shuffle-artist"
href
role="button"
@click.prevent="shuffle"
@ -26,7 +25,6 @@
v-if="allowDownload"
:title="`Download all songs by ${artist.name}`"
class="download-artist"
data-testid="download-artist"
href
role="button"
@click.prevent="download"

View file

@ -5,6 +5,7 @@ import { eventBus } from '@/utils'
import { downloadService, playbackService } from '@/services'
import { commonStore, songStore } from '@/stores'
import ArtistContextMenu from './ArtistContextMenu.vue'
import { screen } from '@testing-library/vue'
let artist: Artist
@ -22,18 +23,15 @@ new class extends UnitTestCase {
}
protected test () {
it('renders', async () => {
const { html } = await this.renderComponent()
expect(html()).toMatchSnapshot()
})
it('renders', async () => expect((await this.renderComponent()).html()).toMatchSnapshot())
it('plays all', async () => {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByText } = await this.renderComponent()
await getByText('Play All').click()
await this.renderComponent()
await screen.getByText('Play All').click()
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(artist)
@ -45,8 +43,8 @@ new class extends UnitTestCase {
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByText } = await this.renderComponent()
await getByText('Shuffle All').click()
await this.renderComponent()
await screen.getByText('Shuffle All').click()
await this.tick()
expect(fetchMock).toHaveBeenCalledWith(artist)
@ -56,40 +54,40 @@ new class extends UnitTestCase {
it('downloads', async () => {
const mock = this.mock(downloadService, 'fromArtist')
const { getByText } = await this.renderComponent()
await getByText('Download').click()
await this.renderComponent()
await screen.getByText('Download').click()
expect(mock).toHaveBeenCalledWith(artist)
})
it('does not have an option to download if downloading is disabled', async () => {
commonStore.state.allow_download = false
const { queryByText } = await this.renderComponent()
await this.renderComponent()
expect(queryByText('Download')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
it('goes to artist', async () => {
const mock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await getByText('Go to Artist').click()
await screen.getByText('Go to Artist').click()
expect(mock).toHaveBeenCalledWith(`artist/${artist.id}`)
})
it('does not have an option to download or go to Unknown Artist', async () => {
const { queryByTestId } = await this.renderComponent(factory.states('unknown')<Artist>('artist'))
await this.renderComponent(factory.states('unknown')<Artist>('artist'))
expect(queryByTestId('view-artist')).toBeNull()
expect(queryByTestId('download')).toBeNull()
expect(screen.queryByText('Go to Artist')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
it('does not have an option to download or go to Various Artist', async () => {
const { queryByTestId } = await this.renderComponent(factory.states('various')<Artist>('artist'))
await this.renderComponent(factory.states('various')<Artist>('artist'))
expect(queryByTestId('view-artist')).toBeNull()
expect(queryByTestId('download')).toBeNull()
expect(screen.queryByText('Go to Artist')).toBeNull()
expect(screen.queryByText('Download')).toBeNull()
})
}
}

View file

@ -1,15 +1,15 @@
<template>
<ContextMenuBase ref="base" data-testid="artist-context-menu" extra-class="artist-menu">
<template v-if="artist">
<li data-testid="play" @click="play">Play All</li>
<li data-testid="shuffle" @click="shuffle">Shuffle All</li>
<li @click="play">Play All</li>
<li @click="shuffle">Shuffle All</li>
<template v-if="isStandardArtist">
<li class="separator"></li>
<li data-testid="view-artist" @click="viewArtistDetails">Go to Artist</li>
<li @click="viewArtistDetails">Go to Artist</li>
</template>
<template v-if="isStandardArtist && allowDownload">
<li class="separator"></li>
<li data-testid="download" @click="download">Download</li>
<li @click="download">Download</li>
</template>
</template>
</ContextMenuBase>

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { commonStore, songStore } from '@/stores'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { mediaInfoService, playbackService } from '@/services'
import ArtistInfoComponent from './ArtistInfo.vue'
@ -11,12 +11,9 @@ let artist: Artist
new class extends UnitTestCase {
private async renderComponent (mode: MediaInfoDisplayMode = 'aside', info?: ArtistInfo) {
commonStore.state.use_last_fm = true
if (info === undefined) {
info = factory<ArtistInfo>('artist-info')
}
info ??= factory<ArtistInfo>('artist-info')
artist = factory<Artist>('artist', { name: 'Led Zeppelin' })
const fetchMock = this.mock(mediaInfoService, 'fetchForArtist').mockResolvedValue(info)
const rendered = this.render(ArtistInfoComponent, {
@ -39,42 +36,42 @@ new class extends UnitTestCase {
protected test () {
it.each<[MediaInfoDisplayMode]>([['aside'], ['full']])('renders in %s mode', async (mode) => {
const { getByTestId, queryByTestId } = await this.renderComponent(mode)
await this.renderComponent(mode)
if (mode === 'aside') {
getByTestId('thumbnail')
screen.getByTestId('thumbnail')
} else {
expect(queryByTestId('thumbnail'))
expect(screen.queryByTestId('thumbnail')).toBeNull()
}
expect(getByTestId('artist-info').classList.contains(mode)).toBe(true)
expect(screen.getByTestId('artist-info').classList.contains(mode)).toBe(true)
})
it('triggers showing full bio for aside mode', async () => {
const { queryByTestId, getByTestId } = await this.renderComponent('aside')
expect(queryByTestId('full')).toBeNull()
await this.renderComponent('aside')
expect(screen.queryByTestId('full')).toBeNull()
await fireEvent.click(getByTestId('more-btn'))
await this.user.click(screen.getByRole('button', { name: 'Full Bio' }))
expect(queryByTestId('summary')).toBeNull()
expect(queryByTestId('full')).not.toBeNull()
expect(screen.queryByTestId('summary')).toBeNull()
screen.getByTestId('full')
})
it('shows full bio for full mode', async () => {
const { queryByTestId } = await this.renderComponent('full')
await this.renderComponent('full')
expect(queryByTestId('full')).not.toBeNull()
expect(queryByTestId('summary')).toBeNull()
expect(queryByTestId('more-btn')).toBeNull()
screen.getByTestId('full')
expect(screen.queryByTestId('summary')).toBeNull()
expect(screen.queryByRole('button', { name: 'Full Bio' })).toBeNull()
})
it('plays', async () => {
const songs = factory<Song>('song', 3)
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByTitle } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByTitle('Play all songs by Led Zeppelin'))
await this.user.click(screen.getByTitle('Play all songs by Led Zeppelin'))
await this.tick(2)
expect(fetchMock).toHaveBeenCalledWith(artist)

View file

@ -15,7 +15,7 @@
<div v-if="showSummary" class="summary" data-testid="summary" v-html="info.bio.summary"/>
<div v-if="showFull" class="full" data-testid="full" v-html="info.bio.full"/>
<button v-if="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullBio = true">
<button v-if="showSummary" class="more" @click.prevent="showingFullBio = true">
Full Bio
</button>
</div>

View file

@ -4,7 +4,7 @@ exports[`renders 1`] = `
<article class="item full" draggable="true" tabindex="0" title="Led Zeppelin" data-v-f01bdc56=""><br data-testid="thumbnail" entity="[object Object]" data-v-f01bdc56="">
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/artist/42" class="text-normal" data-testid="name">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" data-testid="shuffle-artist" href="" role="button"> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" data-testid="download-artist" href="" role="button"> Download </a></p>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" href="" role="button"> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" href="" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -3,12 +3,12 @@
exports[`renders 1`] = `
<nav class="artist-menu menu context-menu" style="top: 42px; left: 420px;" tabindex="0" data-testid="artist-context-menu" data-v-0408531a="">
<ul data-v-0408531a="">
<li data-testid="play">Play All</li>
<li data-testid="shuffle">Shuffle All</li>
<li>Play All</li>
<li>Shuffle All</li>
<li class="separator"></li>
<li data-testid="view-artist">Go to Artist</li>
<li>Go to Artist</li>
<li class="separator"></li>
<li data-testid="download">Download</li>
<li>Download</li>
</ul>
</nav>
`;

View file

@ -1,5 +1,5 @@
import { fireEvent } from '@testing-library/vue'
import { expect, it, SpyInstanceFn } from 'vitest'
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import { userStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import LoginFrom from './LoginForm.vue'
@ -8,9 +8,9 @@ new class extends UnitTestCase {
private async submitForm (loginMock: SpyInstanceFn) {
const rendered = this.render(LoginFrom)
await fireEvent.update(rendered.getByPlaceholderText('Email Address'), 'john@doe.com')
await fireEvent.update(rendered.getByPlaceholderText('Password'), 'secret')
await fireEvent.submit(rendered.getByTestId('login-form'))
await this.type(screen.getByPlaceholderText('Email Address'), 'john@doe.com')
await this.type(screen.getByPlaceholderText('Password'), 'secret')
await this.user.click(screen.getByRole('button', { name: 'Log In' }))
expect(loginMock).toHaveBeenCalledWith('john@doe.com', 'secret')
@ -26,11 +26,11 @@ new class extends UnitTestCase {
it('fails to log in', async () => {
const mock = this.mock(userStore, 'login').mockRejectedValue(new Error('Unauthenticated'))
const { getByTestId, emitted } = await this.submitForm(mock)
const { emitted } = await this.submitForm(mock)
await this.tick()
expect(emitted().loggedin).toBeFalsy()
expect(getByTestId('login-form').classList.contains('error')).toBe(true)
expect(screen.getByTestId('login-form').classList.contains('error')).toBe(true)
})
}
}

View file

@ -1,5 +1,5 @@
import { it } from 'vitest'
import { waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import { eventBus } from '@/utils'
import { Events } from '@/config'
@ -20,7 +20,7 @@ new class extends UnitTestCase {
['edit-smart-playlist-form', 'MODAL_SHOW_EDIT_PLAYLIST_FORM', factory<Playlist>('playlist', { is_smart: true })],
['about-koel', 'MODAL_SHOW_ABOUT_KOEL', undefined]
])('shows %s modal', async (modalName, eventName, eventParams?: any) => {
const { getByTestId } = this.render(ModalWrapper, {
this.render(ModalWrapper, {
global: {
stubs: {
AddUserForm: this.stub('add-user-form'),
@ -39,7 +39,7 @@ new class extends UnitTestCase {
eventBus.emit(eventName, eventParams)
await waitFor(() => getByTestId(modalName))
await waitFor(() => screen.getByTestId(modalName))
})
}
}

View file

@ -3,9 +3,9 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { CurrentSongKey } from '@/symbols'
import { fireEvent } from '@testing-library/vue'
import { playbackService } from '@/services'
import FooterPlaybackControls from './FooterPlaybackControls.vue'
import { screen } from '@testing-library/vue'
new class extends UnitTestCase {
private renderComponent (song?: Song | null) {
@ -27,7 +27,7 @@ new class extends UnitTestCase {
PlayButton: this.stub('PlayButton')
},
provide: {
[CurrentSongKey]: ref(song)
[<symbol>CurrentSongKey]: ref(song)
}
}
})
@ -39,18 +39,18 @@ new class extends UnitTestCase {
it('plays the previous song', async () => {
const playMock = this.mock(playbackService, 'playPrev')
const { getByTitle } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTitle('Play previous song'))
await this.user.click(screen.getByRole('button', { name: 'Play previous song' }))
expect(playMock).toHaveBeenCalled()
})
it('plays the next song', async () => {
const playMock = this.mock(playbackService, 'playNext')
const { getByTitle } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTitle('Play next song'))
await this.user.click(screen.getByRole('button', { name: 'Play next song' }))
expect(playMock).toHaveBeenCalled()
})

View file

@ -2,7 +2,7 @@
exports[`renders with a current song 1`] = `
<div class="playback-controls" data-testid="footer-middle-pane" data-v-2e8b419d="">
<div class="buttons" data-v-2e8b419d=""><button title="Unlike Fahrstuhl to Heaven by Led Zeppelin" data-testid="like-btn" type="button" class="like-btn" data-v-2e8b419d=""><br data-testid="btn-like-liked" icon="[object Object]"></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
<div class="buttons" data-v-2e8b419d=""><button title="Unlike Fahrstuhl to Heaven by Led Zeppelin" type="button" class="like-btn" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]"></button><!-- a placeholder to maintain the flex layout --><button type="button" title="Play previous song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><br data-testid="PlayButton" data-v-2e8b419d=""><button type="button" title="Play next song" data-v-2e8b419d=""><br data-testid="icon" icon="[object Object]" data-v-2e8b419d=""></button><button class="repeat-mode-btn" title="Change repeat mode (current: No Repeat)" data-testid="repeat-mode-switch" type="button" data-v-cab48a7c="" data-v-2e8b419d="">
<div class="fa-layers" data-v-cab48a7c=""><br data-testid="icon" icon="[object Object]" data-v-cab48a7c="">
<!--v-if-->
</div>

View file

@ -1,12 +1,12 @@
import { ref, Ref } from 'vue'
import { expect, it } from 'vitest'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import { albumStore, artistStore, commonStore, preferenceStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { CurrentSongKey } from '@/symbols'
import ExtraPanel from './ExtraPanel.vue'
import { eventBus } from '@/utils'
import ExtraPanel from './ExtraPanel.vue'
new class extends UnitTestCase {
private renderComponent (songRef: Ref<Song | null> = ref(null)) {
@ -32,8 +32,8 @@ new class extends UnitTestCase {
it('sets the active tab to the preference', async () => {
preferenceStore.activeExtraPanelTab = 'YouTube'
const { getByTestId } = this.renderComponent(ref(factory<Song>('song')))
const tab = getByTestId<HTMLElement>('extra-panel-youtube')
this.renderComponent(ref(factory<Song>('song')))
const tab = screen.getByTestId<HTMLElement>('extra-panel-youtube')
expect(tab.style.display).toBe('none')
await this.tick()
@ -52,21 +52,21 @@ new class extends UnitTestCase {
const songRef = ref<Song | null>(null)
const { getByTestId } = this.renderComponent(songRef)
this.renderComponent(songRef)
songRef.value = song
await waitFor(() => {
expect(resolveArtistMock).toHaveBeenCalledWith(song.artist_id)
expect(resolveAlbumMock).toHaveBeenCalledWith(song.album_id)
;['lyrics', 'album-info', 'artist-info', 'youtube-video-list'].forEach(id => getByTestId(id))
;['lyrics', 'album-info', 'artist-info', 'youtube-video-list'].forEach(id => screen.getByTestId(id))
})
})
it('shows About Koel model', async () => {
const emitMock = this.mock(eventBus, 'emit')
const { getByTitle } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTitle('About Koel'))
await this.user.click(screen.getByRole('button', { name: 'About Koel' }))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_ABOUT_KOEL')
})
@ -75,15 +75,15 @@ new class extends UnitTestCase {
it('shows new version', () => {
commonStore.state.current_version = 'v1.0.0'
commonStore.state.latest_version = 'v1.0.1'
this.actingAsAdmin().renderComponent().getByTitle('New version available!')
this.actingAsAdmin().renderComponent().getByRole('button', { name: 'New version available!' })
})
})
it('logs out', async () => {
const emitMock = this.mock(eventBus, 'emit')
const { getByTitle } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTitle('Log out'))
await this.user.click(screen.getByRole('button', { name: 'Log out' }))
expect(emitMock).toHaveBeenCalledWith('LOG_OUT')
})

View file

@ -1,5 +1,5 @@
import { ref } from 'vue'
import { waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { albumStore, preferenceStore } from '@/stores'
@ -28,17 +28,17 @@ new class extends UnitTestCase {
it('has a translucent overlay per album', async () => {
this.mock(albumStore, 'fetchThumbnail').mockResolvedValue('http://test/foo.jpg')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await waitFor(() => getByTestId('album-art-overlay'))
await waitFor(() => screen.getByTestId('album-art-overlay'))
})
it('does not have a translucent over if configured not so', async () => {
preferenceStore.state.showAlbumArtOverlay = false
const { queryByTestId } = this.renderComponent()
this.renderComponent()
await waitFor(() => expect(queryByTestId('album-art-overlay')).toBeNull())
await waitFor(() => expect(screen.queryByTestId('album-art-overlay')).toBeNull())
})
}
}

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import { commonStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { http } from '@/services'
import { waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import AboutKoelModel from './AboutKoelModal.vue'
new class extends UnitTestCase {
@ -35,10 +35,10 @@ new class extends UnitTestCase {
// @ts-ignore
import.meta.env.VITE_KOEL_ENV = 'demo'
const { getByTestId } = this.renderComponent()
this.renderComponent()
await waitFor(() => {
getByTestId('demo-credits')
screen.getByTestId('demo-credits')
expect(getMock).toHaveBeenCalledWith('demo/credits')
})
})

View file

@ -1,5 +1,5 @@
import { expect, it, vi } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { preferenceStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import SupportKoel from './SupportKoel.vue'
@ -27,9 +27,7 @@ new class extends UnitTestCase {
}
protected test () {
it('shows after a delay', async () => {
expect((await this.renderComponent()).html()).toMatchSnapshot()
})
it('shows after a delay', async () => expect((await this.renderComponent()).html()).toMatchSnapshot())
it('does not show if user so demands', async () => {
preferenceStore.state.supportBarNoBugging = true
@ -38,19 +36,17 @@ new class extends UnitTestCase {
})
it('hides', async () => {
const { getByTestId, queryByTestId } = await this.renderComponent()
await this.renderComponent()
await this.user.click(screen.getByRole('button', { name: 'Hide' }))
await fireEvent.click(getByTestId('hide-support-koel'))
expect(await queryByTestId('support-bar')).toBeNull()
expect(screen.queryByTestId('support-bar')).toBeNull()
})
it('hides and does not bug again', async () => {
const { getByTestId, queryByTestId } = await this.renderComponent()
await this.renderComponent()
await this.user.click(screen.getByRole('button', { name: 'Don\'t bug me again' }))
await fireEvent.click(getByTestId('stop-support-koel-bugging'))
expect(await queryByTestId('btn-stop-support-koel-bugging')).toBeNull()
expect(await screen.queryByTestId('support-bar')).toBeNull()
expect(preferenceStore.state.supportBarNoBugging).toBe(true)
})
}

View file

@ -6,9 +6,9 @@
and/or
<a href="https://opencollective.com/koel" rel="noopener" target="_blank">OpenCollective</a>.
</p>
<button data-testid="hide-support-koel" type="button" @click.prevent="close">Hide</button>
<button type="button" @click.prevent="close">Hide</button>
<span class="sep"></span>
<button data-testid="stop-support-koel-bugging" type="button" @click.prevent="stopBugging">
<button type="button" @click.prevent="stopBugging">
Don't bug me again
</button>
</div>

View file

@ -2,6 +2,6 @@
exports[`shows after a delay 1`] = `
<div class="support-bar" data-testid="support-bar" data-v-c8ee2518="">
<p data-v-c8ee2518=""> Loving Koel? Please consider supporting its development via <a href="https://github.com/users/phanan/sponsorship" rel="noopener" target="_blank" data-v-c8ee2518="">GitHub Sponsors</a> and/or <a href="https://opencollective.com/koel" rel="noopener" target="_blank" data-v-c8ee2518="">OpenCollective</a>. </p><button data-testid="hide-support-koel" type="button" data-v-c8ee2518="">Hide</button><span class="sep" data-v-c8ee2518=""></span><button data-testid="stop-support-koel-bugging" type="button" data-v-c8ee2518=""> Don't bug me again </button>
<p data-v-c8ee2518=""> Loving Koel? Please consider supporting its development via <a href="https://github.com/users/phanan/sponsorship" rel="noopener" target="_blank" data-v-c8ee2518="">GitHub Sponsors</a> and/or <a href="https://opencollective.com/koel" rel="noopener" target="_blank" data-v-c8ee2518="">OpenCollective</a>. </p><button type="button" data-v-c8ee2518="">Hide</button><span class="sep" data-v-c8ee2518=""></span><button type="button" data-v-c8ee2518=""> Don't bug me again </button>
</div>
`;

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { eventBus } from '@/utils'
import { Events } from '@/config'
@ -7,10 +7,9 @@ import CreateNewPlaylistContextMenu from './CreateNewPlaylistContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent () {
const rendered = await this.render(CreateNewPlaylistContextMenu)
eventBus.emit('CREATE_NEW_PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 })
await this.render(CreateNewPlaylistContextMenu)
eventBus.emit('CREATE_NEW_PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent)
await this.tick(2)
return rendered
}
protected test () {
@ -19,9 +18,9 @@ new class extends UnitTestCase {
['playlist-context-menu-create-smart', 'MODAL_SHOW_CREATE_SMART_PLAYLIST_FORM'],
['playlist-context-menu-create-folder', 'MODAL_SHOW_CREATE_PLAYLIST_FOLDER_FORM']
])('when clicking on %s, should emit %s', async (id, eventName) => {
const { getByTestId } = await this.renderComponent()
await this.renderComponent()
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByTestId(id))
await this.user.click(screen.getByTestId(id))
await waitFor(() => expect(emitMock).toHaveBeenCalledWith(eventName))
})
}

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playlistFolderStore } from '@/stores'
import factory from '@/__tests__/factory'
@ -11,10 +11,10 @@ new class extends UnitTestCase {
const storeMock = this.mock(playlistFolderStore, 'store')
.mockResolvedValue(factory<PlaylistFolder>('playlist-folder'))
const { getByPlaceholderText, getByRole } = await this.render(CreatePlaylistFolderForm)
await this.render(CreatePlaylistFolderForm)
await fireEvent.update(getByPlaceholderText('Folder name'), 'My folder')
await fireEvent.click(getByRole('button', { name: 'Save' }))
await this.type(screen.getByPlaceholderText('Folder name'), 'My folder')
await this.user.click(screen.getByRole('button', { name: 'Save' }))
expect(storeMock).toHaveBeenCalledWith('My folder')
})

View file

@ -1,18 +1,18 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playlistStore } from '@/stores'
import factory from '@/__tests__/factory'
import CreatePlaylistForm from './CreatePlaylistForm.vue'
import { ref } from 'vue'
import { ModalContextKey } from '@/symbols'
import CreatePlaylistForm from './CreatePlaylistForm.vue'
new class extends UnitTestCase {
protected test () {
it('submits', async () => {
const folder = factory<PlaylistFolder>('playlist-folder')
const storeMock = this.mock(playlistStore, 'store').mockResolvedValue(factory<Playlist>('playlist'))
const { getByPlaceholderText, getByRole } = await this.render(CreatePlaylistForm, {
this.render(CreatePlaylistForm, {
global: {
provide: {
[<symbol>ModalContextKey]: [ref({ folder })]
@ -20,8 +20,8 @@ new class extends UnitTestCase {
}
})
await fireEvent.update(getByPlaceholderText('Playlist name'), 'My playlist')
await fireEvent.click(getByRole('button', { name: 'Save' }))
await this.type(screen.getByPlaceholderText('Playlist name'), 'My playlist')
await this.user.click(screen.getByRole('button', { name: 'Save' }))
expect(storeMock).toHaveBeenCalledWith('My playlist', {
folder_id: folder.id

View file

@ -1,6 +1,6 @@
import { ref } from 'vue'
import { fireEvent, waitFor } from '@testing-library/vue'
import { expect, it, vi } from 'vitest'
import { screen, waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playlistFolderStore } from '@/stores'
@ -11,9 +11,8 @@ new class extends UnitTestCase {
protected test () {
it('submits', async () => {
const folder = factory<PlaylistFolder>('playlist-folder', { name: 'My folder' })
const updateFolderNameMock = vi.fn()
const renameMock = this.mock(playlistFolderStore, 'rename')
const { getByPlaceholderText, getByRole } = this.render(EditPlaylistFolderForm, {
this.render(EditPlaylistFolderForm, {
global: {
provide: {
[<symbol>ModalContextKey]: [ref({ folder })]
@ -21,8 +20,8 @@ new class extends UnitTestCase {
}
})
await fireEvent.update(getByPlaceholderText('Folder name'), 'Your folder')
await fireEvent.click(getByRole('button', { name: 'Save' }))
await this.type(screen.getByPlaceholderText('Folder name'), 'Your folder')
await this.user.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => {
expect(renameMock).toHaveBeenCalledWith(folder, 'Your folder')

View file

@ -3,7 +3,7 @@ import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playlistFolderStore, playlistStore } from '@/stores'
import { ref } from 'vue'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { ModalContextKey } from '@/symbols'
import EditPlaylistForm from './EditPlaylistForm.vue'
@ -21,7 +21,7 @@ new class extends UnitTestCase {
const updateMock = this.mock(playlistStore, 'update')
const { getByPlaceholderText, getByRole } = this.render(EditPlaylistForm, {
this.render(EditPlaylistForm, {
global: {
provide: {
[<symbol>ModalContextKey]: [ref({ playlist })]
@ -29,8 +29,8 @@ new class extends UnitTestCase {
}
})
await fireEvent.update(getByPlaceholderText('Playlist name'), 'Your playlist')
await fireEvent.click(getByRole('button', { name: 'Save' }))
await this.type(screen.getByPlaceholderText('Playlist name'), 'Your playlist')
await this.user.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => {
expect(updateMock).toHaveBeenCalledWith(playlist, {

View file

@ -2,44 +2,43 @@ import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { eventBus } from '@/utils'
import factory from '@/__tests__/factory'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import PlaylistContextMenu from './PlaylistContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent (playlist: Playlist) {
const rendered = await this.render(PlaylistContextMenu)
await this.render(PlaylistContextMenu)
eventBus.emit('PLAYLIST_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, playlist)
await this.tick(2)
return rendered
}
protected test () {
it('edits a standard playlist', async () => {
const playlist = factory<Playlist>('playlist')
const { getByText } = await this.renderComponent(playlist)
await this.renderComponent(playlist)
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Edit'))
await this.user.click(screen.getByText('Edit'))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist)
})
it('edits a smart playlist', async () => {
const playlist = factory.states('smart')<Playlist>('playlist')
const { getByText } = await this.renderComponent(playlist)
await this.renderComponent(playlist)
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Edit'))
await this.user.click(screen.getByText('Edit'))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_EDIT_PLAYLIST_FORM', playlist)
})
it('deletes a playlist', async () => {
const playlist = factory<Playlist>('playlist')
const { getByText } = await this.renderComponent(playlist)
await this.renderComponent(playlist)
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Delete'))
await this.user.click(screen.getByText('Delete'))
expect(emitMock).toHaveBeenCalledWith('PLAYLIST_DELETE', playlist)
})

View file

@ -2,36 +2,35 @@ import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { eventBus } from '@/utils'
import factory from '@/__tests__/factory'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { playlistStore, songStore } from '@/stores'
import { playbackService } from '@/services'
import PlaylistFolderContextMenu from './PlaylistFolderContextMenu.vue'
new class extends UnitTestCase {
private async renderComponent (folder: PlaylistFolder) {
const rendered = await this.render(PlaylistFolderContextMenu)
await this.render(PlaylistFolderContextMenu)
eventBus.emit('PLAYLIST_FOLDER_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, folder)
await this.tick(2)
return rendered
}
protected test () {
it('renames', async () => {
const folder = factory<PlaylistFolder>('playlist-folder')
const { getByText } = await this.renderComponent(folder)
await this.renderComponent(folder)
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Rename'))
await this.user.click(screen.getByText('Rename'))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_EDIT_PLAYLIST_FOLDER_FORM', folder)
})
it('deletes', async () => {
const folder = factory<PlaylistFolder>('playlist-folder')
const { getByText } = await this.renderComponent(folder)
await this.renderComponent(folder)
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Delete'))
await this.user.click(screen.getByText('Delete'))
expect(emitMock).toHaveBeenCalledWith('PLAYLIST_FOLDER_DELETE', folder)
})
@ -42,9 +41,9 @@ new class extends UnitTestCase {
const fetchMock = this.mock(songStore, 'fetchForPlaylistFolder').mockResolvedValue(songs)
const queueMock = this.mock(playbackService, 'queueAndPlay')
const goMock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent(folder)
await this.renderComponent(folder)
await fireEvent.click(getByText('Play All'))
await this.user.click(screen.getByText('Play All'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(folder)
@ -59,9 +58,9 @@ new class extends UnitTestCase {
const fetchMock = this.mock(songStore, 'fetchForPlaylistFolder').mockResolvedValue(songs)
const queueMock = this.mock(playbackService, 'queueAndPlay')
const goMock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent(folder)
await this.renderComponent(folder)
await fireEvent.click(getByText('Shuffle All'))
await this.user.click(screen.getByText('Shuffle All'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(folder)
@ -72,10 +71,10 @@ new class extends UnitTestCase {
it('does not show shuffle option if folder is empty', async () => {
const folder = factory<PlaylistFolder>('playlist-folder')
const { queryByText } = await this.renderComponent(folder)
await this.renderComponent(folder)
expect(queryByText('Shuffle All')).toBeNull()
expect(queryByText('Play All')).toBeNull()
expect(screen.queryByText('Shuffle All')).toBeNull()
expect(screen.queryByText('Play All')).toBeNull()
})
}

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { fireEvent, screen } from '@testing-library/vue'
import { eventBus } from '@/utils'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
@ -7,7 +7,7 @@ import PlaylistSidebarItem from './PlaylistSidebarItem.vue'
new class extends UnitTestCase {
renderComponent (list: PlaylistLike) {
return this.render(PlaylistSidebarItem, {
this.render(PlaylistSidebarItem, {
props: {
list
}
@ -18,9 +18,9 @@ new class extends UnitTestCase {
it('requests context menu if is playlist', async () => {
const emitMock = this.mock(eventBus, 'emit')
const playlist = factory<Playlist>('playlist')
const { getByTestId } = this.renderComponent(playlist)
this.renderComponent(playlist)
await fireEvent.contextMenu(getByTestId('playlist-sidebar-item'))
await fireEvent.contextMenu(screen.getByRole('listitem'))
expect(emitMock).toHaveBeenCalledWith('PLAYLIST_CONTEXT_MENU_REQUESTED', expect.anything(), playlist)
})
@ -33,9 +33,9 @@ new class extends UnitTestCase {
}
const emitMock = this.mock(eventBus, 'emit')
const { getByTestId } = this.renderComponent(list)
this.renderComponent(list)
await fireEvent.contextMenu(getByTestId('playlist-sidebar-item'))
await fireEvent.contextMenu(screen.getByRole('listitem'))
expect(emitMock).not.toHaveBeenCalledWith('PLAYLIST_CONTEXT_MENU_REQUESTED', list)
})

View file

@ -3,7 +3,6 @@
ref="el"
:class="{ droppable }"
class="playlist"
data-testid="playlist-sidebar-item"
draggable="true"
@contextmenu="onContextMenu"
@dragleave="onDragLeave"

View file

@ -1,3 +1,4 @@
import { screen } from '@testing-library/vue'
import { it } from 'vitest'
import { playlistFolderStore, playlistStore } from '@/stores'
import factory from '@/__tests__/factory'
@ -8,7 +9,7 @@ import PlaylistFolderSidebarItem from './PlaylistFolderSidebarItem.vue'
new class extends UnitTestCase {
private renderComponent () {
return this.render(PlaylistSidebarList, {
this.render(PlaylistSidebarList, {
global: {
stubs: {
PlaylistSidebarItem,
@ -26,9 +27,11 @@ new class extends UnitTestCase {
factory.states('smart', 'orphan')<Playlist>('playlist', { name: 'Smart Playlist' })
]
const { getByText } = this.renderComponent()
this.renderComponent()
;['Favorites', 'Recently Played', 'Foo Playlist', 'Bar Playlist', 'Smart Playlist'].forEach(t => getByText(t))
;['Favorites', 'Recently Played', 'Foo Playlist', 'Bar Playlist', 'Smart Playlist'].forEach(text => {
screen.getByText(text)
})
})
it('displays playlist folders', () => {
@ -37,8 +40,8 @@ new class extends UnitTestCase {
factory<PlaylistFolder>('playlist-folder', { name: 'Bar Folder' })
]
const { getByText } = this.renderComponent()
;['Foo Folder', 'Bar Folder'].forEach(t => getByText(t))
this.renderComponent()
;['Foo Folder', 'Bar Folder'].forEach(text => screen.getByText(text))
})
}
}

View file

@ -1,4 +1,5 @@
import { expect, it } from 'vitest'
import { screen } from '@testing-library/vue'
import isMobile from 'ismobilejs'
import UnitTestCase from '@/__tests__/UnitTestCase'
import PreferencesForm from './PreferencesForm.vue'
@ -7,14 +8,14 @@ new class extends UnitTestCase {
protected test () {
it('has "Transcode on mobile" option for mobile users', () => {
isMobile.phone = true
const { getByLabelText } = this.render(PreferencesForm)
getByLabelText('Convert and play media at 128kbps on mobile')
this.render(PreferencesForm)
screen.getByRole('checkbox', { name: 'Convert and play media at 128kbps on mobile' })
})
it('does not have "Transcode on mobile" option for non-mobile users', async () => {
isMobile.phone = false
const { queryByLabelText } = this.render(PreferencesForm)
expect(await queryByLabelText('Convert and play media at 128kbps on mobile')).toBeNull()
this.render(PreferencesForm)
expect(screen.queryByRole('checkbox', { name: 'Convert and play media at 128kbps on mobile' })).toBeNull()
})
}
}

View file

@ -1,6 +1,6 @@
import UnitTestCase from '@/__tests__/UnitTestCase'
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import ThemeCard from './ThemeCard.vue'
const theme: Theme = {
@ -18,13 +18,13 @@ new class extends UnitTestCase {
}
protected test () {
it('renders', () => {
expect(this.renderComponent().html()).toMatchSnapshot()
})
it('renders', () => expect(this.renderComponent().html()).toMatchSnapshot())
it('emits an event when selected', async () => {
const { emitted, getByTestId } = this.renderComponent()
await fireEvent.click(getByTestId('theme-card-sample'))
const { emitted } = this.renderComponent()
await this.user.click(screen.getByRole('button', { name: 'Sample' }))
expect(emitted().selected[0]).toEqual([theme])
})
}

View file

@ -1,9 +1,8 @@
<template>
<div
:class="{ selected: theme.selected }"
:data-testid="`theme-card-${theme.id}`"
:style="thumbnailStyles"
:title="`Set current them to ${name}`"
:title="`Set current theme to ${name}`"
class="theme"
role="button"
@click="$emit('selected', theme)"

View file

@ -1,7 +1,7 @@
// Vitest Snapshot v1
exports[`renders 1`] = `
<div class="theme" data-testid="theme-card-sample" style="background-color: rgb(255, 0, 0);" title="Set current them to Sample" role="button" data-v-1467c50f="">
<div class="theme" style="background-color: rgb(255, 0, 0);" title="Set current theme to Sample" role="button" data-v-1467c50f="">
<div class="name" data-v-1467c50f="">Sample</div>
</div>
`;

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { albumStore, preferenceStore } from '@/stores'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import AlbumListScreen from './AlbumListScreen.vue'
new class extends UnitTestCase {
@ -13,7 +13,7 @@ new class extends UnitTestCase {
private async renderComponent () {
albumStore.state.albums = factory<Album>('album', 9)
const rendered = this.render(AlbumListScreen, {
this.render(AlbumListScreen, {
global: {
stubs: {
AlbumCard: this.stub('album-card')
@ -22,31 +22,30 @@ new class extends UnitTestCase {
})
await this.router.activateRoute({ path: 'albums', screen: 'Albums' })
return rendered
}
protected test () {
it('renders', async () => {
const { getAllByTestId } = await this.renderComponent()
expect(getAllByTestId('album-card')).toHaveLength(9)
await this.renderComponent()
expect(screen.getAllByTestId('album-card')).toHaveLength(9)
})
it.each<[ArtistAlbumViewMode]>([['list'], ['thumbnails']])('sets layout from preferences', async (mode) => {
preferenceStore.albumsViewMode = mode
const { getByTestId } = await this.renderComponent()
await this.renderComponent()
await waitFor(() => expect(getByTestId('album-list').classList.contains(`as-${mode}`)).toBe(true))
await waitFor(() => expect(screen.getByTestId('album-list').classList.contains(`as-${mode}`)).toBe(true))
})
it('switches layout', async () => {
const { getByTestId, getByTitle } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByTitle('View as list'))
await waitFor(() => expect(getByTestId('album-list').classList.contains(`as-list`)).toBe(true))
await this.user.click(screen.getByRole('radio', { name: 'View as list' }))
await waitFor(() => expect(screen.getByTestId('album-list').classList.contains(`as-list`)).toBe(true))
await fireEvent.click(getByTitle('View as thumbnails'))
await waitFor(() => expect(getByTestId('album-list').classList.contains(`as-thumbnails`)).toBe(true))
await this.user.click(screen.getByRole('radio', { name: 'View as thumbnails' }))
await waitFor(() => expect(screen.getByTestId('album-list').classList.contains(`as-thumbnails`)).toBe(true))
})
}
}

View file

@ -1,4 +1,4 @@
import { fireEvent, getByTestId, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
@ -32,7 +32,7 @@ new class extends UnitTestCase {
screen: 'Album'
}, { id: '42' })
const rendered = this.render(AlbumScreen, {
this.render(AlbumScreen, {
global: {
stubs: {
SongList: this.stub('song-list'),
@ -48,16 +48,14 @@ new class extends UnitTestCase {
})
await this.tick(2)
return rendered
}
protected test () {
it('downloads', async () => {
const downloadMock = this.mock(downloadService, 'fromAlbum')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Download All'))
await this.user.click(screen.getByRole('button', { name: 'Download All' }))
expect(downloadMock).toHaveBeenCalledWith(album)
})
@ -76,21 +74,21 @@ new class extends UnitTestCase {
})
it('shows the song list', async () => {
const { getByTestId } = await this.renderComponent()
getByTestId('song-list')
await this.renderComponent()
screen.getByTestId('song-list')
})
it('shows other albums from the same artist', async () => {
const albums = factory<Album>('album', 3)
albums.push(album)
const fetchMock = this.mock(albumStore, 'fetchForArtist').mockResolvedValue(albums)
const { getByLabelText, getAllByTestId } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByLabelText('Other Albums'))
await this.user.click(screen.getByRole('radio', { name: 'Other Albums' }))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(album.artist_id)
expect(getAllByTestId('album-card')).toHaveLength(3) // current album is excluded
expect(screen.getAllByTestId('album-card')).toHaveLength(3) // current album is excluded
})
})
}

View file

@ -19,7 +19,8 @@
<a
v-if="allowDownload"
class="download"
href role="button"
href
role="button"
title="Download all songs in album"
@click.prevent="download"
>

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { commonStore, queueStore, songStore } from '@/stores'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { playbackService } from '@/services'
import AllSongsScreen from './AllSongsScreen.vue'
@ -40,9 +40,9 @@ new class extends UnitTestCase {
const queueMock = this.mock(queueStore, 'fetchRandom')
const playMock = this.mock(playbackService, 'playFirstInQueue')
const goMock = this.mock(this.router, 'go')
const { getByTitle } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByTitle('Shuffle all songs'))
await this.user.click(screen.getByTitle('Shuffle all songs'))
await waitFor(() => {
expect(queueMock).toHaveBeenCalled()

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { artistStore, preferenceStore } from '@/stores'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import ArtistListScreen from './ArtistListScreen.vue'
new class extends UnitTestCase {
@ -27,26 +27,26 @@ new class extends UnitTestCase {
protected test () {
it('renders', async () => {
const { getAllByTestId } = await this.renderComponent()
expect(getAllByTestId('artist-card')).toHaveLength(9)
await this.renderComponent()
expect(screen.getAllByTestId('artist-card')).toHaveLength(9)
})
it.each<[ArtistAlbumViewMode]>([['list'], ['thumbnails']])('sets layout:%s from preferences', async (mode) => {
preferenceStore.artistsViewMode = mode
const { getByTestId } = await this.renderComponent()
await this.renderComponent()
await waitFor(() => expect(getByTestId('artist-list').classList.contains(`as-${mode}`)).toBe(true))
await waitFor(() => expect(screen.getByTestId('artist-list').classList.contains(`as-${mode}`)).toBe(true))
})
it('switches layout', async () => {
const { getByTestId, getByTitle } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByTitle('View as list'))
await waitFor(() => expect(getByTestId('artist-list').classList.contains(`as-list`)).toBe(true))
await this.user.click(screen.getByRole('radio', { name: 'View as list' }))
await waitFor(() => expect(screen.getByTestId('artist-list').classList.contains(`as-list`)).toBe(true))
await fireEvent.click(getByTitle('View as thumbnails'))
await waitFor(() => expect(getByTestId('artist-list').classList.contains(`as-thumbnails`)).toBe(true))
await this.user.click(screen.getByRole('radio', { name: 'View as thumbnails' }))
await waitFor(() => expect(screen.getByTestId('artist-list').classList.contains(`as-thumbnails`)).toBe(true))
})
}
}

View file

@ -1,4 +1,4 @@
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
@ -31,7 +31,7 @@ new class extends UnitTestCase {
screen: 'Artist'
}, { id: '42' })
const rendered = this.render(ArtistScreen, {
this.render(ArtistScreen, {
global: {
stubs: {
ArtistInfo: this.stub('artist-info'),
@ -47,16 +47,14 @@ new class extends UnitTestCase {
})
await this.tick(2)
return rendered
}
protected test () {
it('downloads', async () => {
const downloadMock = this.mock(downloadService, 'fromArtist')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Download All'))
await this.user.click(screen.getByRole('button', { name: 'Download All' }))
expect(downloadMock).toHaveBeenCalledWith(artist)
})
@ -75,8 +73,8 @@ new class extends UnitTestCase {
})
it('shows the song list', async () => {
const { getByTestId } = await this.renderComponent()
getByTestId('song-list')
await this.renderComponent()
screen.getByTestId('song-list')
})
}
}

View file

@ -1,4 +1,4 @@
import { waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
@ -8,32 +8,30 @@ import FavoritesScreen from './FavoritesScreen.vue'
new class extends UnitTestCase {
private async renderComponent () {
const fetchMock = this.mock(favoriteStore, 'fetch')
const rendered = this.render(FavoritesScreen)
this.render(FavoritesScreen)
await this.router.activateRoute({ path: 'favorites', screen: 'Favorites' })
await waitFor(() => expect(fetchMock).toHaveBeenCalled())
return rendered
}
protected test () {
it('renders a list of favorites', async () => {
favoriteStore.state.songs = factory<Song>('song', 13)
const { queryByTestId } = await this.renderComponent()
await this.renderComponent()
await waitFor(() => {
expect(queryByTestId('screen-empty-state')).toBeNull()
expect(queryByTestId('song-list')).not.toBeNull()
expect(screen.queryByTestId('screen-empty-state')).toBeNull()
screen.getByTestId('song-list')
})
})
it('shows empty state', async () => {
favoriteStore.state.songs = []
const { queryByTestId } = await this.renderComponent()
await this.renderComponent()
expect(queryByTestId('screen-empty-state')).not.toBeNull()
expect(queryByTestId('song-list')).toBeNull()
screen.getByTestId('screen-empty-state')
expect(screen.queryByTestId('song-list')).toBeNull()
})
}
}

View file

@ -2,8 +2,8 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { genreStore } from '@/stores'
import { screen, waitFor } from '@testing-library/vue'
import GenreListScreen from './GenreListScreen.vue'
import { waitFor } from '@testing-library/vue'
new class extends UnitTestCase {
protected test () {
@ -17,11 +17,11 @@ new class extends UnitTestCase {
const fetchMock = this.mock(genreStore, 'fetchAll').mockResolvedValue(genres)
const { getByTitle } = this.render(GenreListScreen)
this.render(GenreListScreen)
await waitFor(() => {
expect(fetchMock).toHaveBeenCalled()
genres.forEach(genre => getByTitle(`${genre.name}: ${genre.song_count} songs`))
genres.forEach(genre => screen.getByTitle(`${genre.name}: ${genre.song_count} songs`))
})
})
}

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { genreStore, songStore } from '@/stores'
@ -41,9 +41,8 @@ new class extends UnitTestCase {
protected test () {
it('renders the song list', async () => {
const { getByTestId } = await this.renderComponent()
expect(getByTestId('song-list')).toBeTruthy()
await this.renderComponent()
expect(screen.getByTestId('song-list')).toBeTruthy()
})
it('shuffles all songs without fetching if genre has <= 500 songs', async () => {
@ -51,9 +50,9 @@ new class extends UnitTestCase {
const songs = factory<Song>('song', 10)
const playbackMock = this.mock(playbackService, 'queueAndPlay')
const { getByTitle } = await this.renderComponent(genre, songs)
await this.renderComponent(genre, songs)
await fireEvent.click(getByTitle('Shuffle all songs'))
await this.user.click(screen.getByTitle('Shuffle all songs'))
expect(playbackMock).toHaveBeenCalledWith(songs, true)
})
@ -64,9 +63,9 @@ new class extends UnitTestCase {
const playbackMock = this.mock(playbackService, 'queueAndPlay')
const fetchMock = this.mock(songStore, 'fetchRandomForGenre').mockResolvedValue(songs)
const { getByTitle } = await this.renderComponent(genre, songs)
await this.renderComponent(genre, songs)
await fireEvent.click(getByTitle('Shuffle all songs'))
await this.user.click(screen.getByTitle('Shuffle all songs'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(genre, 500)

View file

@ -3,13 +3,13 @@ import UnitTestCase from '@/__tests__/UnitTestCase'
import { commonStore, overviewStore } from '@/stores'
import { Events } from '@/config'
import { eventBus } from '@/utils'
import { screen } from '@testing-library/vue'
import HomeScreen from './HomeScreen.vue'
new class extends UnitTestCase {
private async renderComponent () {
const rendered = this.render(HomeScreen)
this.render(HomeScreen)
await this.router.activateRoute({ path: 'home', screen: 'Home' })
return rendered
}
protected test () {
@ -17,16 +17,16 @@ new class extends UnitTestCase {
commonStore.state.song_length = 0
this.mock(overviewStore, 'init')
const { getByTestId } = await this.render(HomeScreen)
await this.render(HomeScreen)
getByTestId('screen-empty-state')
screen.getByTestId('screen-empty-state')
})
it('renders overview components if applicable', async () => {
commonStore.state.song_length = 100
const initMock = this.mock(overviewStore, 'init')
const { getByTestId, queryByTestId } = await this.renderComponent()
await this.renderComponent()
expect(initMock).toHaveBeenCalled()
@ -37,13 +37,13 @@ new class extends UnitTestCase {
'recently-added-songs',
'most-played-artists',
'most-played-albums'
].forEach(id => getByTestId(id))
].forEach(id => screen.getByTestId(id))
expect(queryByTestId('screen-empty-state')).toBeNull()
expect(screen.queryByTestId('screen-empty-state')).toBeNull()
})
it.each<[keyof Events]>([['SONGS_UPDATED'], ['SONGS_DELETED']])
('refreshes the overviews on %s event', async (eventName) => {
('refreshes the overviews on %s event', async eventName => {
const initMock = this.mock(overviewStore, 'init')
const refreshMock = this.mock(overviewStore, 'refresh')
await this.renderComponent()

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { eventBus } from '@/utils'
import { fireEvent, getByTestId, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { playlistStore, songStore } from '@/stores'
import { downloadService } from '@/services'
import PlaylistScreen from './PlaylistScreen.vue'
@ -11,7 +11,7 @@ let playlist: Playlist
new class extends UnitTestCase {
private async renderComponent (songs: Song[]) {
playlist = playlist || factory<Playlist>('playlist')
playlist ||= factory<Playlist>('playlist')
playlistStore.init([playlist])
const fetchMock = this.mock(songStore, 'fetchForPlaylist').mockResolvedValue(songs)
@ -25,51 +25,51 @@ new class extends UnitTestCase {
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(playlist, false))
return { rendered, fetchMock }
return { fetchMock }
}
protected test () {
it('renders the playlist', async () => {
const { rendered: { getByTestId, queryByTestId } } = (await this.renderComponent(factory<Song>('song', 10)))
await this.renderComponent(factory<Song>('song', 10))
await waitFor(() => {
getByTestId('song-list')
expect(queryByTestId('screen-empty-state')).toBeNull()
screen.getByTestId('song-list')
expect(screen.queryByTestId('screen-empty-state')).toBeNull()
})
})
it('displays the empty state if playlist is empty', async () => {
const { rendered: { getByTestId, queryByTestId } } = (await this.renderComponent([]))
await this.renderComponent([])
await waitFor(() => {
getByTestId('screen-empty-state')
expect(queryByTestId('song-list')).toBeNull()
screen.getByTestId('screen-empty-state')
expect(screen.queryByTestId('song-list')).toBeNull()
})
})
it('downloads the playlist', async () => {
const downloadMock = this.mock(downloadService, 'fromPlaylist')
const { rendered: { getByText } } = (await this.renderComponent(factory<Song>('song', 10)))
await this.renderComponent(factory<Song>('song', 10))
await this.tick()
await fireEvent.click(getByText('Download All'))
await this.user.click(screen.getByRole('button', { name: 'Download All' }))
await waitFor(() => expect(downloadMock).toHaveBeenCalledWith(playlist))
})
it('deletes the playlist', async () => {
const emitMock = this.mock(eventBus, 'emit')
const { rendered: { getByTitle } } = (await this.renderComponent([]))
await this.renderComponent([])
await fireEvent.click(getByTitle('Delete this playlist'))
await this.user.click(screen.getByRole('button', { name: 'Delete this playlist' }))
await waitFor(() => expect(emitMock).toHaveBeenCalledWith('PLAYLIST_DELETE', playlist))
})
it('refreshes the playlist', async () => {
const { rendered: { getByTitle }, fetchMock } = (await this.renderComponent([]))
const { fetchMock } = await this.renderComponent([])
await fireEvent.click(getByTitle('Refresh'))
await this.user.click(screen.getByRole('button', { name: 'Refresh' }))
expect(fetchMock).toHaveBeenCalledWith(playlist, true)
})

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { commonStore, queueStore } from '@/stores'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { playbackService } from '@/services'
import QueueScreen from './QueueScreen.vue'
@ -10,7 +10,7 @@ new class extends UnitTestCase {
private renderComponent (songs: Song[]) {
queueStore.state.songs = songs
return this.render(QueueScreen, {
this.render(QueueScreen, {
global: {
stubs: {
SongList: this.stub('song-list')
@ -21,17 +21,17 @@ new class extends UnitTestCase {
protected test () {
it('renders the queue', () => {
const { queryByTestId } = this.renderComponent(factory<Song>('song', 3))
this.renderComponent(factory<Song>('song', 3))
expect(queryByTestId('song-list')).toBeTruthy()
expect(queryByTestId('screen-empty-state')).toBeNull()
expect(screen.queryByTestId('song-list')).toBeTruthy()
expect(screen.queryByTestId('screen-empty-state')).toBeNull()
})
it('renders an empty state if no songs queued', () => {
const { queryByTestId } = this.renderComponent([])
this.renderComponent([])
expect(queryByTestId('song-list')).toBeNull()
expect(queryByTestId('screen-empty-state')).toBeTruthy()
expect(screen.queryByTestId('song-list')).toBeNull()
expect(screen.queryByTestId('screen-empty-state')).toBeTruthy()
})
it('has an option to plays some random songs if the library is not empty', async () => {
@ -39,8 +39,8 @@ new class extends UnitTestCase {
const fetchRandomMock = this.mock(queueStore, 'fetchRandom')
const playMock = this.mock(playbackService, 'playFirstInQueue')
const { getByText } = this.renderComponent([])
await fireEvent.click(getByText('playing some random songs'))
this.renderComponent([])
await this.user.click(screen.getByText('playing some random songs'))
await waitFor(() => {
expect(fetchRandomMock).toHaveBeenCalled()
@ -50,10 +50,10 @@ new class extends UnitTestCase {
it('Shuffles all', async () => {
const songs = factory<Song>('song', 3)
const { getByTitle } = this.renderComponent(songs)
this.renderComponent(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
await fireEvent.click(getByTitle('Shuffle all songs'))
await this.user.click(screen.getByTitle('Shuffle all songs'))
await waitFor(() => expect(playMock).toHaveBeenCalledWith(songs, true))
})
}

View file

@ -42,7 +42,7 @@
No songs queued.
<span v-if="libraryNotEmpty" class="d-block secondary">
How about
<a class="start" data-testid="shuffle-library" @click.prevent="shuffleSome">playing some random songs</a>?
<a class="start" @click.prevent="shuffleSome">playing some random songs</a>?
</span>
</ScreenEmptyState>
</section>

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { recentlyPlayedStore } from '@/stores'
import { waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import RecentlyPlayedScreen from './RecentlyPlayedScreen.vue'
new class extends UnitTestCase {
@ -10,7 +10,7 @@ new class extends UnitTestCase {
recentlyPlayedStore.state.songs = songs
const fetchMock = this.mock(recentlyPlayedStore, 'fetch')
const rendered = this.render(RecentlyPlayedScreen, {
this.render(RecentlyPlayedScreen, {
global: {
stubs: {
SongList: this.stub('song-list')
@ -21,23 +21,21 @@ new class extends UnitTestCase {
await this.router.activateRoute({ path: 'recently-played', screen: 'RecentlyPlayed' })
await waitFor(() => expect(fetchMock).toHaveBeenCalled())
return rendered
}
protected test () {
it('displays the songs', async () => {
const { queryByTestId } = await this.renderComponent(factory<Song>('song', 3))
await this.renderComponent(factory<Song>('song', 3))
expect(queryByTestId('song-list')).toBeTruthy()
expect(queryByTestId('screen-empty-state')).toBeNull()
screen.getByTestId('song-list')
expect(screen.queryByTestId('screen-empty-state')).toBeNull()
})
it('displays the empty state', async () => {
const { queryByTestId } = await this.renderComponent([])
await this.renderComponent([])
expect(queryByTestId('song-list')).toBeNull()
expect(queryByTestId('screen-empty-state')).toBeTruthy()
expect(screen.queryByTestId('song-list')).toBeNull()
screen.getByTestId('screen-empty-state')
})
}
}

View file

@ -1,9 +1,9 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import SettingsScreen from './SettingsScreen.vue'
import { settingStore } from '@/stores'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { DialogBoxStub } from '@/__tests__/stubs'
import SettingsScreen from './SettingsScreen.vue'
new class extends UnitTestCase {
protected test () {
@ -14,10 +14,10 @@ new class extends UnitTestCase {
const goMock = this.mock(this.router, 'go')
settingStore.state.media_path = ''
const { getByLabelText, getByText } = this.render(SettingsScreen)
this.render(SettingsScreen)
await fireEvent.update(getByLabelText('Media Path'), '/media')
await fireEvent.click(getByText('Scan'))
await this.type(screen.getByLabelText('Media Path'), '/media')
await this.user.click(screen.getByRole('button', { name: 'Scan' }))
await waitFor(() => {
expect(updateMock).toHaveBeenCalledWith({ media_path: '/media' })
@ -31,10 +31,10 @@ new class extends UnitTestCase {
const confirmMock = this.mock(DialogBoxStub.value, 'confirm')
settingStore.state.media_path = '/old'
const { getByLabelText, getByText } = this.render(SettingsScreen)
this.render(SettingsScreen)
await fireEvent.update(getByLabelText('Media Path'), '/new')
await fireEvent.click(getByText('Scan'))
await this.type(screen.getByLabelText('Media Path'), '/new')
await this.user.click(screen.getByRole('button', { name: 'Scan' }))
await waitFor(() => {
expect(updateMock).not.toHaveBeenCalled()

View file

@ -1,10 +1,45 @@
import { it } from 'vitest'
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import factory from '@/__tests__/factory'
import { screen } from '@testing-library/vue'
import { http } from '@/services'
import { eventBus } from '@/utils'
import Btn from '@/components/ui/Btn.vue'
import BtnGroup from '@/components/ui/BtnGroup.vue'
import UserListScreen from './UserListScreen.vue'
new class extends UnitTestCase {
protected test () {
it('displays a list of users', () => {
private async renderComponent () {
const fetchMock = this.mock(http, 'get').mockResolvedValue(factory<User>('user', 6))
this.render(UserListScreen, {
global: {
stubs: {
Btn,
BtnGroup,
UserCard: this.stub('user-card')
}
}
})
expect(fetchMock).toHaveBeenCalledWith('users')
await this.tick(2)
}
protected test () {
it('displays a list of users', async () => {
await this.renderComponent()
expect(screen.getAllByTestId('user-card')).toHaveLength(6)
})
it('triggers create user modal', async () => {
const emitMock = this.mock(eventBus, 'emit')
await this.renderComponent()
await this.user.click(screen.getByRole('button', { name: 'Add' }))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_ADD_USER_FORM')
})
}
}

View file

@ -6,7 +6,7 @@
<template v-slot:controls>
<BtnGroup uppercased v-if="showingControls || !isPhone">
<Btn class="btn-add" data-testid="add-user-btn" green @click="showAddUserForm">
<Btn class="btn-add" green @click="showAddUserForm">
<icon :icon="faPlus"/>
Add
</Btn>

View file

@ -8,7 +8,13 @@ new class extends UnitTestCase {
protected test () {
it('displays the songs', () => {
overviewStore.state.mostPlayedSongs = factory<Song>('song', 6)
expect(this.render(MostPlayedSongs).getAllByTestId('song-card')).toHaveLength(6)
expect(this.render(MostPlayedSongs, {
global: {
stubs: {
SongCard: this.stub('song-card')
}
}
}).getAllByTestId('song-card')).toHaveLength(6)
})
}
}

View file

@ -8,7 +8,13 @@ new class extends UnitTestCase {
protected test () {
it('displays the songs', () => {
overviewStore.state.recentlyAddedSongs = factory<Song>('song', 6)
expect(this.render(RecentlyAddedSongs).getAllByTestId('song-card')).toHaveLength(6)
expect(this.render(RecentlyAddedSongs, {
global: {
stubs: {
SongCard: this.stub('song-card')
}
}
}).getAllByTestId('song-card')).toHaveLength(6)
})
}
}

View file

@ -1,22 +1,28 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import factory from '@/__tests__/factory'
import { fireEvent } from '@testing-library/vue'
import { overviewStore } from '@/stores'
import { screen } from '@testing-library/vue'
import RecentlyPlayedSongs from './RecentlyPlayedSongs.vue'
new class extends UnitTestCase {
protected test () {
it('displays the songs', () => {
overviewStore.state.recentlyPlayed = factory<Song>('song', 6)
expect(this.render(RecentlyPlayedSongs).getAllByTestId('song-card')).toHaveLength(6)
expect(this.render(RecentlyPlayedSongs, {
global: {
stubs: {
SongCard: this.stub('song-card')
}
}
}).getAllByTestId('song-card')).toHaveLength(6)
})
it('goes to dedicated screen', async () => {
const mock = this.mock(this.router, 'go')
const { getByTestId } = this.render(RecentlyPlayedSongs)
this.render(RecentlyPlayedSongs)
await fireEvent.click(getByTestId('home-view-all-recently-played-btn'))
await this.user.click(screen.getByRole('button', { name: 'View All' }))
expect(mock).toHaveBeenCalledWith('recently-played')
})

View file

@ -4,7 +4,6 @@
Recently Played
<Btn
v-if="songs.length"
data-testid="home-view-all-recently-played-btn"
orange
rounded
small

View file

@ -1,12 +1,12 @@
import { clone } from 'lodash'
import { screen } from '@testing-library/vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { favoriteStore, playlistStore, queueStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { arrayify } from '@/utils'
import Btn from '@/components/ui/Btn.vue'
import AddToMenu from './AddToMenu.vue'
import { arrayify } from '@/utils'
import { fireEvent } from '@testing-library/vue'
let songs: Song[]
@ -52,8 +52,8 @@ new class extends UnitTestCase {
['playlists', 'add-to-playlist'],
['newPlaylist', 'new-playlist']
])('renders disabling %s config', (configKey: keyof AddToMenuConfig, testIds: string | string[]) => {
const { queryByTestId } = this.renderComponent({ [configKey]: false })
arrayify(testIds).forEach(async (id) => expect(await queryByTestId(id)).toBeNull())
this.renderComponent({ [configKey]: false })
arrayify(testIds).forEach(id => expect(screen.queryByTestId(id)).toBeNull())
})
it.each<[string, string, MethodOf<typeof queueStore>]>([
@ -65,18 +65,18 @@ new class extends UnitTestCase {
queueStore.state.songs[2].playback_state = 'Playing'
const mock = this.mock(queueStore, queueMethod)
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId(testId))
await this.user.click(screen.getByTestId(testId))
expect(mock).toHaveBeenCalledWith(songs)
})
it('adds songs to Favorites', async () => {
const mock = this.mock(favoriteStore, 'like')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId('add-to-favorites'))
await this.user.click(screen.getByTestId('add-to-favorites'))
expect(mock).toHaveBeenCalledWith(songs)
})
@ -84,9 +84,9 @@ new class extends UnitTestCase {
it('adds songs to existing playlist', async () => {
const mock = this.mock(playlistStore, 'addSongs')
playlistStore.state.playlists = factory<Playlist>('playlist', 3)
const { getAllByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getAllByTestId('add-to-playlist')[1])
await this.user.click(screen.getAllByTestId('add-to-playlist')[1])
expect(mock).toHaveBeenCalledWith(playlistStore.state.playlists[1], songs)
})

View file

@ -4,7 +4,7 @@ import UnitTestCase from '@/__tests__/UnitTestCase'
import { arrayify, eventBus } from '@/utils'
import { ModalContextKey } from '@/symbols'
import { ref } from 'vue'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { songStore } from '@/stores'
import { MessageToasterStub } from '@/__tests__/stubs'
import EditSongForm from './EditSongForm.vue'
@ -37,7 +37,7 @@ new class extends UnitTestCase {
const emitMock = this.mock(eventBus, 'emit')
const alertMock = this.mock(MessageToasterStub.value, 'success')
const { html, getByTestId, getByRole } = await this.renderComponent(factory<Song>('song', {
const { html } = await this.renderComponent(factory<Song>('song', {
title: 'Rocket to Heaven',
artist_name: 'Led Zeppelin',
album_name: 'IV',
@ -47,17 +47,17 @@ new class extends UnitTestCase {
expect(html()).toMatchSnapshot()
await fireEvent.update(getByTestId('title-input'), 'Highway to Hell')
await fireEvent.update(getByTestId('artist-input'), 'AC/DC')
await fireEvent.update(getByTestId('albumArtist-input'), 'AC/DC')
await fireEvent.update(getByTestId('album-input'), 'Back in Black')
await fireEvent.update(getByTestId('disc-input'), '1')
await fireEvent.update(getByTestId('track-input'), '10')
await fireEvent.update(getByTestId('genre-input'), 'Rock & Roll')
await fireEvent.update(getByTestId('year-input'), '1971')
await fireEvent.update(getByTestId('lyrics-input'), 'I\'m gonna make him an offer he can\'t refuse')
await this.type(screen.getByTestId('title-input'), 'Highway to Hell')
await this.type(screen.getByTestId('artist-input'), 'AC/DC')
await this.type(screen.getByTestId('albumArtist-input'), 'AC/DC')
await this.type(screen.getByTestId('album-input'), 'Back in Black')
await this.type(screen.getByTestId('disc-input'), '1')
await this.type(screen.getByTestId('track-input'), '10')
await this.type(screen.getByTestId('genre-input'), 'Rock & Roll')
await this.type(screen.getByTestId('year-input'), '1971')
await this.type(screen.getByTestId('lyrics-input'), 'I\'m gonna make him an offer he can\'t refuse')
await fireEvent.click(getByRole('button', { name: 'Update' }))
await this.user.click(screen.getByRole('button', { name: 'Update' }))
expect(updateMock).toHaveBeenCalledWith(songs, {
title: 'Highway to Hell',
@ -80,21 +80,21 @@ new class extends UnitTestCase {
const emitMock = this.mock(eventBus, 'emit')
const alertMock = this.mock(MessageToasterStub.value, 'success')
const { html, getByTestId, getByRole, queryByTestId } = await this.renderComponent(factory<Song>('song', 3))
const { html } = await this.renderComponent(factory<Song>('song', 3))
expect(html()).toMatchSnapshot()
expect(queryByTestId('title-input')).toBeNull()
expect(queryByTestId('lyrics-input')).toBeNull()
expect(screen.queryByTestId('title-input')).toBeNull()
expect(screen.queryByTestId('lyrics-input')).toBeNull()
await fireEvent.update(getByTestId('artist-input'), 'AC/DC')
await fireEvent.update(getByTestId('albumArtist-input'), 'AC/DC')
await fireEvent.update(getByTestId('album-input'), 'Back in Black')
await fireEvent.update(getByTestId('disc-input'), '1')
await fireEvent.update(getByTestId('track-input'), '10')
await fireEvent.update(getByTestId('year-input'), '1990')
await fireEvent.update(getByTestId('genre-input'), 'Pop')
await this.type(screen.getByTestId('artist-input'), 'AC/DC')
await this.type(screen.getByTestId('albumArtist-input'), 'AC/DC')
await this.type(screen.getByTestId('album-input'), 'Back in Black')
await this.type(screen.getByTestId('disc-input'), '1')
await this.type(screen.getByTestId('track-input'), '10')
await this.type(screen.getByTestId('year-input'), '1990')
await this.type(screen.getByTestId('genre-input'), 'Pop')
await fireEvent.click(getByRole('button', { name: 'Update' }))
await this.user.click(screen.getByRole('button', { name: 'Update' }))
expect(updateMock).toHaveBeenCalledWith(songs, {
album_name: 'Back in Black',
@ -111,15 +111,15 @@ new class extends UnitTestCase {
})
it('displays artist name if all songs have the same artist', async () => {
const { getByTestId } = await this.renderComponent(factory<Song>('song', 4, {
await this.renderComponent(factory<Song>('song', 4, {
artist_id: 1000,
artist_name: 'Led Zeppelin',
album_id: 1001,
album_name: 'IV'
}))
expect(getByTestId('displayed-artist-name').textContent).toBe('Led Zeppelin')
expect(getByTestId('displayed-album-name').textContent).toBe('IV')
expect(screen.getByTestId('displayed-artist-name').textContent).toBe('Led Zeppelin')
expect(screen.getByTestId('displayed-album-name').textContent).toBe('IV')
})
}
}

View file

@ -2,7 +2,7 @@ import factory from '@/__tests__/factory'
import { queueStore } from '@/stores'
import { playbackService } from '@/services'
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import SongCard from './SongCard.vue'
@ -32,17 +32,17 @@ new class extends UnitTestCase {
protected test () {
it('has a thumbnail and a like button', () => {
const { getByTestId } = this.renderComponent()
getByTestId('thumbnail')
getByTestId('like-button')
this.renderComponent()
screen.getByTestId('thumbnail')
screen.getByTestId('like-button')
})
it('queues and plays on double-click', async () => {
const queueMock = this.mock(queueStore, 'queueIfNotQueued')
const playMock = this.mock(playbackService, 'play')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.dblClick(getByTestId('song-card'))
await this.user.dblClick(screen.getByRole('article'))
expect(queueMock).toHaveBeenCalledWith(song)
expect(playMock).toHaveBeenCalledWith(song)

View file

@ -1,7 +1,6 @@
<template>
<article
:class="{ playing: song.playback_state === 'Playing' || song.playback_state === 'Paused' }"
data-testid="song-card"
draggable="true"
tabindex="0"
@dragstart="onDragStart"

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { arrayify, eventBus } from '@/utils'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { downloadService, playbackService } from '@/services'
import { favoriteStore, playlistStore, queueStore, songStore } from '@/stores'
import { DialogBoxStub, MessageToasterStub } from '@/__tests__/stubs'
@ -19,7 +19,7 @@ new class extends UnitTestCase {
songs = arrayify(_songs || factory<Song>('song', 5))
const rendered = this.render(SongContextMenu)
eventBus.emit('SONG_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 }, songs)
eventBus.emit('SONG_CONTEXT_MENU_REQUESTED', { pageX: 420, pageY: 42 } as MouseEvent, songs)
await this.tick(2)
return rendered
@ -35,9 +35,9 @@ new class extends UnitTestCase {
const queueMock = this.mock(queueStore, 'queueIfNotQueued')
const playMock = this.mock(playbackService, 'play')
const song = factory<Song>('song', { playback_state: 'Stopped' })
const { getByText } = await this.renderComponent(song)
await this.renderComponent(song)
await fireEvent.click(getByText('Play'))
await this.user.click(screen.getByText('Play'))
expect(queueMock).toHaveBeenCalledWith(song)
expect(playMock).toHaveBeenCalledWith(song)
@ -45,54 +45,54 @@ new class extends UnitTestCase {
it('pauses playback', async () => {
const pauseMock = this.mock(playbackService, 'pause')
const { getByText } = await this.renderComponent(factory<Song>('song', { playback_state: 'Playing' }))
await this.renderComponent(factory<Song>('song', { playback_state: 'Playing' }))
await fireEvent.click(getByText('Pause'))
await this.user.click(screen.getByText('Pause'))
expect(pauseMock).toHaveBeenCalled()
})
it('resumes playback', async () => {
const resumeMock = this.mock(playbackService, 'resume')
const { getByText } = await this.renderComponent(factory<Song>('song', { playback_state: 'Paused' }))
await this.renderComponent(factory<Song>('song', { playback_state: 'Paused' }))
await fireEvent.click(getByText('Play'))
await this.user.click(screen.getByText('Play'))
expect(resumeMock).toHaveBeenCalled()
})
it('goes to album details screen', async () => {
const goMock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent(factory<Song>('song'))
await this.renderComponent(factory<Song>('song'))
await fireEvent.click(getByText('Go to Album'))
await this.user.click(screen.getByText('Go to Album'))
expect(goMock).toHaveBeenCalledWith(`album/${songs[0].album_id}`)
})
it('goes to artist details screen', async () => {
const goMock = this.mock(this.router, 'go')
const { getByText } = await this.renderComponent(factory<Song>('song'))
await this.renderComponent(factory<Song>('song'))
await fireEvent.click(getByText('Go to Artist'))
await this.user.click(screen.getByText('Go to Artist'))
expect(goMock).toHaveBeenCalledWith(`artist/${songs[0].artist_id}`)
})
it('downloads', async () => {
const downloadMock = this.mock(downloadService, 'fromSongs')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Download'))
await this.user.click(screen.getByText('Download'))
expect(downloadMock).toHaveBeenCalledWith(songs)
})
it('queues', async () => {
const queueMock = this.mock(queueStore, 'queue')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Queue'))
await this.user.click(screen.getByText('Queue'))
expect(queueMock).toHaveBeenCalledWith(songs)
})
@ -100,9 +100,9 @@ new class extends UnitTestCase {
it('queues after current song', async () => {
this.fillQueue()
const queueMock = this.mock(queueStore, 'queueAfterCurrent')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('After Current Song'))
await this.user.click(screen.getByText('After Current Song'))
expect(queueMock).toHaveBeenCalledWith(songs)
})
@ -110,9 +110,9 @@ new class extends UnitTestCase {
it('queues to bottom', async () => {
this.fillQueue()
const queueMock = this.mock(queueStore, 'queue')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Bottom of Queue'))
await this.user.click(screen.getByText('Bottom of Queue'))
expect(queueMock).toHaveBeenCalledWith(songs)
})
@ -120,9 +120,9 @@ new class extends UnitTestCase {
it('queues to top', async () => {
this.fillQueue()
const queueMock = this.mock(queueStore, 'queueToTop')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Top of Queue'))
await this.user.click(screen.getByText('Top of Queue'))
expect(queueMock).toHaveBeenCalledWith(songs)
})
@ -136,9 +136,9 @@ new class extends UnitTestCase {
screen: 'Queue'
})
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Remove from Queue'))
await this.user.click(screen.getByText('Remove from Queue'))
expect(removeMock).toHaveBeenCalledWith(songs)
})
@ -151,16 +151,16 @@ new class extends UnitTestCase {
screen: 'Songs'
})
const { queryByText } = await this.renderComponent()
await this.renderComponent()
expect(queryByText('Remove from Queue')).toBeNull()
expect(screen.queryByText('Remove from Queue')).toBeNull()
})
it('adds to favorites', async () => {
const likeMock = this.mock(favoriteStore, 'like')
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Favorites'))
await this.user.click(screen.getByText('Favorites'))
expect(likeMock).toHaveBeenCalledWith(songs)
})
@ -171,9 +171,9 @@ new class extends UnitTestCase {
screen: 'Favorites'
})
const { queryByText } = await this.renderComponent()
this.renderComponent()
expect(queryByText('Favorites')).toBeNull()
expect(screen.queryByText('Favorites')).toBeNull()
})
it('removes from favorites', async () => {
@ -184,9 +184,9 @@ new class extends UnitTestCase {
screen: 'Favorites'
})
const { getByText } = await this.renderComponent()
await this.renderComponent()
await fireEvent.click(getByText('Remove from Favorites'))
await this.user.click(screen.getByText('Remove from Favorites'))
expect(unlikeMock).toHaveBeenCalledWith(songs)
})
@ -195,11 +195,11 @@ new class extends UnitTestCase {
playlistStore.state.playlists = factory<Playlist>('playlist', 3)
const addMock = this.mock(playlistStore, 'addSongs')
this.mock(MessageToasterStub.value, 'success')
const { queryByText, getByText } = await this.renderComponent()
await this.renderComponent()
playlistStore.state.playlists.forEach(playlist => queryByText(playlist.name))
playlistStore.state.playlists.forEach(playlist => screen.queryByText(playlist.name))
await fireEvent.click(getByText(playlistStore.state.playlists[0].name))
await this.user.click(screen.getByText(playlistStore.state.playlists[0].name))
expect(addMock).toHaveBeenCalledWith(playlistStore.state.playlists[0], songs)
})
@ -208,9 +208,9 @@ new class extends UnitTestCase {
playlistStore.state.playlists = factory<Playlist>('playlist', 3)
playlistStore.state.playlists.push(factory.states('smart')<Playlist>('playlist', { name: 'My Smart Playlist' }))
const { queryByText } = await this.renderComponent()
await this.renderComponent()
expect(queryByText('My Smart Playlist')).toBeNull()
expect(screen.queryByText('My Smart Playlist')).toBeNull()
})
it('removes from playlist', async () => {
@ -222,12 +222,12 @@ new class extends UnitTestCase {
screen: 'Playlist'
}, { id: String(playlist.id) })
const { getByText } = await this.renderComponent()
await this.renderComponent()
const removeSongsMock = this.mock(playlistStore, 'removeSongs')
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Remove from Playlist'))
await this.user.click(screen.getByText('Remove from Playlist'))
await waitFor(() => {
expect(removeSongsMock).toHaveBeenCalledWith(playlist, songs)
@ -241,41 +241,40 @@ new class extends UnitTestCase {
screen: 'Songs'
})
const { queryByText } = await this.renderComponent()
await this.renderComponent()
expect(queryByText('Remove from Playlist')).toBeNull()
expect(screen.queryByText('Remove from Playlist')).toBeNull()
})
it('allows edit songs if current user is admin', async () => {
const { getByText } = await this.actingAsAdmin().renderComponent()
await this.actingAsAdmin().renderComponent()
// mock after render to ensure that the component is mounted properly
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Edit'))
await this.user.click(screen.getByText('Edit'))
expect(emitMock).toHaveBeenCalledWith('MODAL_SHOW_EDIT_SONG_FORM', songs)
})
it('does not allow edit songs if current user is not admin', async () => {
const { queryByText } = await this.actingAs().renderComponent()
expect(queryByText('Edit')).toBeNull()
await this.actingAs().renderComponent()
expect(screen.queryByText('Edit')).toBeNull()
})
it('has an option to copy shareable URL', async () => {
const { getByText } = await this.renderComponent(factory<Song>('song'))
getByText('Copy Shareable URL')
await this.renderComponent(factory<Song>('song'))
screen.getByText('Copy Shareable URL')
})
it('deletes song', async () => {
const confirmMock = this.mock(DialogBoxStub.value, 'confirm', true)
const toasterMock = this.mock(MessageToasterStub.value, 'success')
const deleteMock = this.mock(songStore, 'deleteFromFilesystem')
const { getByText } = await this.actingAsAdmin().renderComponent()
await this.actingAsAdmin().renderComponent()
const emitMock = this.mock(eventBus, 'emit')
await fireEvent.click(getByText('Delete from Filesystem'))
await this.user.click(screen.getByText('Delete from Filesystem'))
await waitFor(() => {
expect(confirmMock).toHaveBeenCalled()
@ -286,8 +285,8 @@ new class extends UnitTestCase {
})
it('does not have an option to delete songs if current user is not admin', async () => {
const { queryByText } = await this.actingAs().renderComponent()
expect(queryByText('Delete from Filesystem')).toBeNull()
await this.actingAs().renderComponent()
expect(screen.queryByText('Delete from Filesystem')).toBeNull()
})
}
}

View file

@ -1,26 +1,30 @@
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { favoriteStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import SongLikeButton from './SongLikeButton.vue'
new class extends UnitTestCase {
protected test () {
it.each<[boolean, string]>([
[true, 'btn-like-liked'],
[false, 'btn-like-unliked']
])('likes or unlikes', async (liked: boolean, testId: string) => {
it.each<[string, boolean]>([
['Unlike Foo by Bar', true],
['Like Foo by Bar', false]
])('%s', async (name: string, liked: boolean) => {
const mock = this.mock(favoriteStore, 'toggleOne')
const song = factory<Song>('song', { liked })
const song = factory<Song>('song', {
liked,
title: 'Foo',
artist_name: 'Bar'
})
const { getByTestId } = this.render(SongLikeButton, {
this.render(SongLikeButton, {
props: {
song
}
})
await fireEvent.click(getByTestId(testId))
await this.user.click(screen.getByRole('button', { name }))
expect(mock).toHaveBeenCalledWith(song)
})

View file

@ -1,7 +1,7 @@
<template>
<button :title="title" data-testid="like-btn" type="button" @click.stop="toggleLike">
<icon v-if="song.liked" :icon="faHeart" data-testid="btn-like-liked"/>
<icon v-else :icon="faEmptyHeart" data-testid="btn-like-unliked"/>
<button :title="title" type="button" @click.stop="toggleLike">
<icon v-if="song.liked" :icon="faHeart"/>
<icon v-else :icon="faEmptyHeart"/>
</button>
</template>

View file

@ -1,10 +1,10 @@
import { ref } from 'vue'
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { arrayify } from '@/utils'
import { SelectedSongsKey, SongListConfigKey, SongListSortFieldKey, SongListSortOrderKey, SongsKey } from '@/symbols'
import { screen } from '@testing-library/vue'
import SongList from './SongList.vue'
let songs: Song[]
@ -59,22 +59,22 @@ new class extends UnitTestCase {
['album_name', 'header-album'],
['length', 'header-length']
])('sorts by %s upon %s clicked', async (field, testId) => {
const { getByTestId, emitted } = await this.renderComponent(factory<Song>('song', 5))
const { emitted } = await this.renderComponent(factory<Song>('song', 5))
await fireEvent.click(getByTestId(testId))
await this.user.click(screen.getByTestId(testId))
expect(emitted().sort[0]).toEqual([field, 'desc'])
await fireEvent.click(getByTestId(testId))
await this.user.click(screen.getByTestId(testId))
expect(emitted().sort[1]).toEqual([field, 'asc'])
})
it('cannot be sorted if configured so', async () => {
const { getByTestId, emitted } = await this.renderComponent(factory<Song>('song', 5), {
const { emitted } = await this.renderComponent(factory<Song>('song', 5), {
sortable: false,
reorderable: true
})
await fireEvent.click(getByTestId('header-track-number'))
await this.user.click(screen.getByTestId('header-track-number'))
expect(emitted().sort).toBeUndefined()
})
}

View file

@ -2,9 +2,9 @@ import { take } from 'lodash'
import { ref } from 'vue'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { fireEvent } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { SelectedSongsKey, SongsKey } from '@/symbols'
import { screen } from '@testing-library/vue'
import SongListControls from './SongListControls.vue'
new class extends UnitTestCase {
@ -26,51 +26,53 @@ new class extends UnitTestCase {
protected test () {
it.each([[0], [1]])('shuffles all if %s songs are selected', async (selectedCount: number) => {
const { emitted, getByTitle } = this.renderComponent(selectedCount)
const { emitted } = this.renderComponent(selectedCount)
await fireEvent.click(getByTitle('Shuffle all songs'))
await this.user.click(screen.getByTitle('Shuffle all songs'))
expect(emitted().playAll[0]).toEqual([true])
})
it.each([[0], [1]])('plays all if %s songs are selected with Alt pressed', async (selectedCount: number) => {
const { emitted, getByTitle } = this.renderComponent(selectedCount)
const { emitted } = this.renderComponent(selectedCount)
await fireEvent.keyDown(window, { key: 'Alt' })
await fireEvent.click(getByTitle('Play all songs'))
await this.user.keyboard('{Alt>}')
await this.user.click(screen.getByTitle('Play all songs'))
await this.user.keyboard('{/Alt}')
expect(emitted().playAll[0]).toEqual([false])
})
it('shuffles selected if more than one song are selected', async () => {
const { emitted, getByTitle } = this.renderComponent(2)
const { emitted } = this.renderComponent(2)
await fireEvent.click(getByTitle('Shuffle selected songs'))
await this.user.click(screen.getByTitle('Shuffle selected songs'))
expect(emitted().playSelected[0]).toEqual([true])
})
it('plays selected if more than one song are selected with Alt pressed', async () => {
const { emitted, getByTitle } = this.renderComponent(2)
const { emitted } = this.renderComponent(2)
await fireEvent.keyDown(window, { key: 'Alt' })
await fireEvent.click(getByTitle('Play selected songs'))
await this.user.keyboard('{Alt>}')
await this.user.click(screen.getByTitle('Play selected songs'))
await this.user.keyboard('{/Alt}')
expect(emitted().playSelected[0]).toEqual([false])
})
it('clears queue', async () => {
const { emitted, getByTitle } = this.renderComponent(0, { clearQueue: true })
const { emitted } = this.renderComponent(0, { clearQueue: true })
await fireEvent.click(getByTitle('Clear current queue'))
await this.user.click(screen.getByTitle('Clear current queue'))
expect(emitted().clearQueue).toBeTruthy()
})
it('deletes current playlist', async () => {
const { emitted, getByTitle } = this.renderComponent(0, { deletePlaylist: true })
const { emitted } = this.renderComponent(0, { deletePlaylist: true })
await fireEvent.click(getByTitle('Delete this playlist'))
await this.user.click(screen.getByTitle('Delete this playlist'))
expect(emitted().deletePlaylist).toBeTruthy()
})

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { queueStore } from '@/stores'
import { playbackService } from '@/services'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import SongListItem from './SongListItem.vue'
@ -44,9 +44,9 @@ new class extends UnitTestCase {
it('plays on double click', async () => {
const queueMock = this.mock(queueStore, 'queueIfNotQueued')
const playMock = this.mock(playbackService, 'play')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.dblClick(getByTestId('song-item'))
await this.user.dblClick(screen.getByTestId('song-item'))
expect(queueMock).toHaveBeenCalledWith(row.song)
expect(playMock).toHaveBeenCalledWith(row.song)

View file

@ -2,8 +2,8 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playbackService } from '@/services'
import { fireEvent } from '@testing-library/vue'
import SongThumbnail from '@/components/song/SongThumbnail.vue'
import { screen } from '@testing-library/vue'
let song: Song
@ -23,15 +23,15 @@ new class extends UnitTestCase {
}
protected test () {
it.each<[PlaybackState, MethodOf<typeof playbackService>]>([
['Stopped', 'play'],
['Playing', 'pause'],
['Paused', 'resume']
])('if state is currently "%s", %ss', async (state: PlaybackState, method: MethodOf<typeof playbackService>) => {
it.each<[PlaybackState, string, MethodOf<typeof playbackService>]>([
['Stopped', 'Play', 'play'],
['Playing', 'Pause', 'pause'],
['Paused', 'Resume', 'resume']
])('if state is currently "%s", %ss', async (state, name, method) => {
const mock = this.mock(playbackService, method)
const { getByTestId } = this.renderComponent(state)
this.renderComponent(state)
await fireEvent.click(getByTestId('play-control'))
await this.user.click(screen.getByRole('button', { name }))
expect(mock).toHaveBeenCalled()
})

View file

@ -1,14 +1,14 @@
<template>
<div :style="{ backgroundImage: `url(${defaultCover})` }" class="cover">
<img v-koel-hide-broken-icon :alt="song.album_name" :src="song.album_cover" loading="lazy"/>
<a class="control" @click.prevent="changeSongState" data-testid="play-control">
<a :title="title" class="control" role="button" @click.prevent="changeSongState">
<icon :icon="song.playback_state === 'Playing' ? faPause : faPlay" class="text-highlight"/>
</a>
</div>
</template>
<script lang="ts" setup>
import { toRefs } from 'vue'
import { computed, toRefs } from 'vue'
import { faPause, faPlay } from '@fortawesome/free-solid-svg-icons'
import { defaultCover } from '@/utils'
import { playbackService } from '@/services'
@ -22,6 +22,18 @@ const play = () => {
playbackService.play(song.value)
}
const title = computed(() => {
if (song.value.playback_state === 'Playing') {
return 'Pause'
}
if (song.value.playback_state === 'Paused') {
return 'Resume'
}
return 'Play'
})
const changeSongState = () => {
if (song.value.playback_state === 'Stopped') {
play()

View file

@ -1,3 +1,3 @@
// Vitest Snapshot v1
exports[`renders 1`] = `<div class="playing song-item" data-testid="song-item" tabindex="0"><span class="track-number"><i data-v-47e95701=""><span data-v-47e95701=""></span><span data-v-47e95701=""></span><span data-v-47e95701=""></span></i></span><span class="thumbnail"><div style="background-image: url(undefined/resources/assets/img/covers/default.svg);" class="cover" data-v-a2b2e00f=""><img alt="Test Album" src="https://example.com/cover.jpg" loading="lazy" data-v-a2b2e00f=""><a class="control" data-testid="play-control" data-v-a2b2e00f=""><br data-testid="icon" icon="[object Object]" class="text-highlight" data-v-a2b2e00f=""></a></div></span><span class="title-artist"><span class="title text-primary">Test Song</span><span class="artist">Test Artist</span></span><span class="album">Test Album</span><span class="time">16:40</span><span class="extra"><button title="Unlike Test Song by Test Artist" data-testid="like-btn" type="button"><br data-testid="btn-like-liked" icon="[object Object]"></button></span></div>`;
exports[`renders 1`] = `<div class="playing song-item" data-testid="song-item" tabindex="0"><span class="track-number"><i data-v-47e95701=""><span data-v-47e95701=""></span><span data-v-47e95701=""></span><span data-v-47e95701=""></span></i></span><span class="thumbnail"><div style="background-image: url(undefined/resources/assets/img/covers/default.svg);" class="cover" data-v-a2b2e00f=""><img alt="Test Album" src="https://example.com/cover.jpg" loading="lazy" data-v-a2b2e00f=""><a title="Pause" class="control" role="button" data-v-a2b2e00f=""><br data-testid="icon" icon="[object Object]" class="text-highlight" data-v-a2b2e00f=""></a></div></span><span class="title-artist"><span class="title text-primary">Test Song</span><span class="artist">Test Artist</span></span><span class="album">Test Album</span><span class="time">16:40</span><span class="extra"><button title="Unlike Test Song by Test Artist" type="button"><br data-testid="icon" icon="[object Object]"></button></span></div>`;

View file

@ -1,8 +1,8 @@
import { waitFor } from '@testing-library/vue'
import { expect, it } from 'vitest'
import { albumStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import AlbumArtOverlay from './AlbumArtOverlay.vue'
import { waitFor } from '@testing-library/vue'
let albumId: number

View file

@ -2,7 +2,7 @@ import { orderBy } from 'lodash'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { queueStore, songStore } from '@/stores'
import { playbackService } from '@/services'
import Thumbnail from './AlbumArtistThumbnail.vue'
@ -50,9 +50,9 @@ new class extends UnitTestCase {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByRole } = this.renderForAlbum()
this.renderForAlbum()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(album)
@ -64,9 +64,11 @@ new class extends UnitTestCase {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForAlbum').mockResolvedValue(songs)
const queueMock = this.mock(queueStore, 'queue')
const { getByRole } = this.renderForAlbum()
this.renderForAlbum()
await fireEvent.click(getByRole('button'), { altKey: true })
await this.user.keyboard('{Alt>}')
await this.user.click(screen.getByRole('button'))
await this.user.keyboard('{/Alt}')
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(album)
@ -78,9 +80,9 @@ new class extends UnitTestCase {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const playMock = this.mock(playbackService, 'queueAndPlay')
const { getByRole } = this.renderForArtist()
this.renderForArtist()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(artist)
@ -92,9 +94,11 @@ new class extends UnitTestCase {
const songs = factory<Song>('song', 10)
const fetchMock = this.mock(songStore, 'fetchForArtist').mockResolvedValue(songs)
const queueMock = this.mock(queueStore, 'queue')
const { getByRole } = this.renderForArtist()
this.renderForArtist()
await fireEvent.click(getByRole('button'), { altKey: true })
await this.user.keyboard('{Alt>}')
await this.user.click(screen.getByRole('button'))
await this.user.keyboard('{/Alt}')
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(artist)

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import BtnCloseModal from './BtnCloseModal.vue'
@ -8,9 +8,9 @@ new class extends UnitTestCase {
it('renders', () => expect(this.render(BtnCloseModal).html()).toMatchSnapshot())
it('emits the event', async () => {
const { emitted, getByRole } = this.render(BtnCloseModal)
const { emitted } = this.render(BtnCloseModal)
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
expect(emitted().click).toBeTruthy()
})

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { $ } from '@/utils'
import UnitTestCase from '@/__tests__/UnitTestCase'
import BtnScrollToTop from './BtnScrollToTop.vue'
@ -12,9 +12,9 @@ new class extends UnitTestCase {
it('scrolls to top', async () => {
const mock = this.mock($, 'scrollTo')
const { getByTitle } = this.render(BtnScrollToTop)
this.render(BtnScrollToTop)
await fireEvent.click(getByTitle('Scroll to top'))
await this.user.click(screen.getByTitle('Scroll to top'))
expect(mock).toHaveBeenCalled()
})

View file

@ -1,7 +1,7 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { screen } from '@testing-library/vue'
import CheckBox from './CheckBox.vue'
import { fireEvent } from '@testing-library/vue'
new class extends UnitTestCase {
protected test () {
@ -14,9 +14,9 @@ new class extends UnitTestCase {
}).html()).toMatchSnapshot())
it('emits the input event', async () => {
const { getByRole, emitted } = this.render(CheckBox)
const { emitted } = this.render(CheckBox)
await fireEvent.input(getByRole('checkbox'))
await this.user.click(screen.getByRole('checkbox'))
expect(emitted()['update:modelValue']).toBeTruthy()
})

View file

@ -1,30 +1,30 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import ExtraPanelTabHeader from './ExtraPanelTabHeader.vue'
import { commonStore } from '@/stores'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import ExtraPanelTabHeader from './ExtraPanelTabHeader.vue'
new class extends UnitTestCase {
protected test () {
it('renders tab headers', () => {
commonStore.state.use_you_tube = false
const { getByTitle, queryByTitle } = this.render(ExtraPanelTabHeader)
this.render(ExtraPanelTabHeader)
;['Lyrics', 'Artist information', 'Album information'].forEach(title => getByTitle(title))
expect(queryByTitle('Related YouTube videos')).toBeNull()
;['Lyrics', 'Artist information', 'Album information'].forEach(name => screen.getByRole('button', { name }))
expect(screen.queryByRole('button', { name: 'Related YouTube videos' })).toBeNull()
})
it('has a YouTube tab header if using YouTube', () => {
commonStore.state.use_you_tube = true
const { getByTitle } = this.render(ExtraPanelTabHeader)
this.render(ExtraPanelTabHeader)
getByTitle('Related YouTube videos')
screen.getByRole('button', { name: 'Related YouTube videos' })
})
it('emits the selected tab value', async () => {
const { getByTitle, emitted } = this.render(ExtraPanelTabHeader)
const { emitted } = this.render(ExtraPanelTabHeader)
await fireEvent.click(getByTitle('Lyrics'))
await this.user.click(screen.getByRole('button', { name: 'Lyrics' }))
expect(emitted()['update:modelValue']).toEqual([['Lyrics']])
})

View file

@ -3,7 +3,7 @@ import { ref } from 'vue'
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { playbackService } from '@/services'
import { fireEvent, getByRole, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { CurrentSongKey } from '@/symbols'
import { commonStore, favoriteStore, queueStore, recentlyPlayedStore, songStore } from '@/stores'
import FooterPlayButton from './FooterPlayButton.vue'
@ -22,9 +22,9 @@ new class extends UnitTestCase {
protected test () {
it('toggles the playback of current song', async () => {
const toggleMock = this.mock(playbackService, 'toggle')
const { getByRole } = this.renderComponent(factory<Song>('song'))
this.renderComponent(factory<Song>('song'))
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
expect(toggleMock).toHaveBeenCalled()
})
@ -33,7 +33,7 @@ new class extends UnitTestCase {
['Album', 'fetchForAlbum'],
['Artist', 'fetchForArtist'],
['Playlist', 'fetchForPlaylist']
])('initiates playback for %s screen', async (screen, fetchMethod) => {
])('initiates playback for %s screen', async (screenName, fetchMethod) => {
commonStore.state.song_count = 10
const songs = factory<Song>('song', 3)
const fetchMock = this.mock(songStore, fetchMethod).mockResolvedValue(songs)
@ -41,13 +41,13 @@ new class extends UnitTestCase {
const goMock = this.mock(this.router, 'go')
await this.router.activateRoute({
screen,
screen: screenName,
path: '_'
}, { id: '42' })
const { getByRole } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalledWith(42)
expect(playMock).toHaveBeenCalledWith(songs)
@ -58,7 +58,7 @@ new class extends UnitTestCase {
it.each<[ScreenName, object, string]>([
['Favorites', favoriteStore, 'fetch'],
['RecentlyPlayed', recentlyPlayedStore, 'fetch']
])('initiates playback for %s screen', async (screen, store, fetchMethod) => {
])('initiates playback for %s screen', async (screenName, store, fetchMethod) => {
commonStore.state.song_count = 10
const songs = factory<Song>('song', 3)
const fetchMock = this.mock(store, fetchMethod).mockResolvedValue(songs)
@ -66,13 +66,13 @@ new class extends UnitTestCase {
const goMock = this.mock(this.router, 'go')
await this.router.activateRoute({
screen,
screen: screenName,
path: '_'
})
const { getByRole } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalled()
expect(playMock).toHaveBeenCalledWith(songs)
@ -80,7 +80,7 @@ new class extends UnitTestCase {
})
})
it.each<[ScreenName]>([['Queue'], ['Songs'], ['Albums']])('initiates playback %s screen', async (screen) => {
it.each<[ScreenName]>([['Queue'], ['Songs'], ['Albums']])('initiates playback %s screen', async screenName => {
commonStore.state.song_count = 10
const songs = factory<Song>('song', 3)
const fetchMock = this.mock(queueStore, 'fetchRandom').mockResolvedValue(songs)
@ -88,13 +88,13 @@ new class extends UnitTestCase {
const goMock = this.mock(this.router, 'go')
await this.router.activateRoute({
screen,
screen: screenName,
path: '_'
})
const { getByRole } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(fetchMock).toHaveBeenCalled()
expect(playMock).toHaveBeenCalledWith(songs)
@ -113,9 +113,9 @@ new class extends UnitTestCase {
path: '_'
})
const { getByRole } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
await waitFor(() => {
expect(playMock).not.toHaveBeenCalled()
expect(goMock).not.toHaveBeenCalled()

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import factory from '@/__tests__/factory'
import { eventBus } from '@/utils'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import LyricsPane from './LyricsPane.vue'
import Magnifier from '@/components/ui/Magnifier.vue'
@ -25,29 +25,22 @@ new class extends UnitTestCase {
}
protected test () {
it('renders', () => {
expect(this.renderComponent().html()).toMatchSnapshot()
})
it('renders', () => expect(this.renderComponent().html()).toMatchSnapshot())
it('provides a button to add lyrics if current user is admin', async () => {
const song = factory<Song>('song', {
lyrics: null
})
const song = factory<Song>('song', { lyrics: null })
const mock = this.mock(eventBus, 'emit')
const { getByTestId } = this.actingAsAdmin().renderComponent(song)
this.actingAsAdmin().renderComponent(song)
await fireEvent.click(getByTestId('add-lyrics-btn'))
await this.user.click(screen.getByRole('button', { name: 'Click here' }))
expect(mock).toHaveBeenCalledWith('MODAL_SHOW_EDIT_SONG_FORM', song, 'lyrics')
})
it('does not have a button to add lyrics if current user is not an admin', async () => {
const { queryByTestId } = this.actingAs().renderComponent(factory<Song>('song', {
lyrics: null
}))
expect(await queryByTestId('add-lyrics-btn')).toBeNull()
this.actingAs().renderComponent(factory<Song>('song', { lyrics: null }))
expect(screen.queryByRole('button', { name: 'Click here' })).toBeNull()
})
}
}

View file

@ -9,7 +9,7 @@
<p v-if="song.id && !song.lyrics" class="none text-secondary">
<template v-if="isAdmin">
No lyrics found.
<button class="text-highlight" data-testid="add-lyrics-btn" type="button" @click.prevent="showEditSongForm">
<button class="text-highlight" type="button" @click.prevent="showEditSongForm">
Click here
</button>
to add lyrics.

View file

@ -1,17 +1,17 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import Magnifier from './Magnifier.vue'
new class extends UnitTestCase {
protected test () {
it('renders and functions', async () => {
const { getByTitle, html, emitted } = this.render(Magnifier)
const { html, emitted } = this.render(Magnifier)
await fireEvent.click(getByTitle('Zoom in'))
expect(emitted()['in']).toBeTruthy()
await this.user.click(screen.getByRole('button', { name: 'Zoom in' }))
expect(emitted().in).toBeTruthy()
await fireEvent.click(getByTitle('Zoom out'))
await this.user.click(screen.getByRole('button', { name: 'Zoom out' }))
expect(emitted().out).toBeTruthy()
expect(html()).toMatchSnapshot()

View file

@ -1,7 +1,7 @@
import { expect, it, vi } from 'vitest'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import MessageToast from './MessageToast.vue'
import { fireEvent } from '@testing-library/vue'
new class extends UnitTestCase {
private renderComponent () {
@ -21,8 +21,8 @@ new class extends UnitTestCase {
it('renders', () => expect(this.renderComponent().html()).toMatchSnapshot())
it('dismisses upon click', async () => {
const { emitted, getByTitle } = this.renderComponent()
await fireEvent.click(getByTitle('Click to dismiss'))
const { emitted } = this.renderComponent()
await this.user.click(screen.getByTitle('Click to dismiss'))
expect(emitted().dismiss).toBeTruthy()
})

View file

@ -12,14 +12,14 @@ import MessageToast from '@/components/ui/MessageToast.vue'
const messages = ref<ToastMessage[]>([])
const addMessage = (type: 'info' | 'success' | 'warning' | 'danger', content: string, timeout?: number) => {
const addMessage = (type: 'info' | 'success' | 'warning' | 'danger', content: string, timeout = 5) => {
const id = `${Date.now().toString(36)}.${Math.random().toString(36)}`
messages.value.push({
id,
type,
content,
timeout: timeout || 5
timeout
})
}

View file

@ -1,7 +1,7 @@
import { expect, it } from 'vitest'
import { preferenceStore } from '@/stores'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { playbackService } from '@/services'
import RepeatModeSwitch from './RepeatModeSwitch.vue'
@ -10,9 +10,9 @@ new class extends UnitTestCase {
it('changes mode', async () => {
const mock = this.mock(playbackService, 'changeRepeatMode')
preferenceStore.state.repeatMode = 'NO_REPEAT'
const { getByRole } = this.render(RepeatModeSwitch)
this.render(RepeatModeSwitch)
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
expect(mock).toHaveBeenCalledOnce()
})

View file

@ -1,6 +1,6 @@
import isMobile from 'ismobilejs'
import { expect, it } from 'vitest'
import { fireEvent, getByRole } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UnitTestCase from '@/__tests__/UnitTestCase'
import ScreenControlsToggle from './ScreenControlsToggle.vue'
@ -8,9 +8,9 @@ new class extends UnitTestCase {
protected test () {
it('renders and emits an event on mobile', async () => {
isMobile.phone = true
const { emitted, getByRole } = this.render(ScreenControlsToggle)
const { emitted } = this.render(ScreenControlsToggle)
await fireEvent.click(getByRole('checkbox'))
await this.user.click(screen.getByRole('checkbox'))
expect(emitted()['update:modelValue']).toBeTruthy()
})

View file

@ -1,43 +1,43 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { eventBus } from '@/utils'
import SearchForm from './SearchForm.vue'
new class extends UnitTestCase {
protected test () {
it('sets focus into search box when requested', async () => {
const { getByRole } = this.render(SearchForm)
this.render(SearchForm)
eventBus.emit('FOCUS_SEARCH_FIELD')
expect(getByRole('searchbox')).toBe(document.activeElement)
expect(screen.getByRole('searchbox')).toBe(document.activeElement)
})
it('goes to search screen when search box is focused', async () => {
const mock = this.mock(this.router, 'go')
const { getByRole } = this.render(SearchForm)
this.render(SearchForm)
await fireEvent.focus(getByRole('searchbox'))
await this.user.click(screen.getByRole('searchbox'))
expect(mock).toHaveBeenCalledWith('search')
})
it('emits an event when search query is changed', async () => {
const mock = this.mock(eventBus, 'emit')
const { getByRole } = this.render(SearchForm)
this.render(SearchForm)
await fireEvent.update(getByRole('searchbox'), 'hey')
await this.type(screen.getByRole('searchbox'), 'hey')
expect(mock).toHaveBeenCalledWith('SEARCH_KEYWORDS_CHANGED', 'hey')
})
it('goes to the search screen if the form is submitted', async () => {
const goMock = this.mock(this.router, 'go')
const { getByRole } = this.render(SearchForm)
this.render(SearchForm)
await fireEvent.update(getByRole('searchbox'), 'hey')
await fireEvent.submit(getByRole('search'))
await this.type(screen.getByRole('searchbox'), 'hey')
await this.user.click(screen.getByRole('button', { name: 'Search' }))
expect(goMock).toHaveBeenCalledWith('search')
})

View file

@ -1,6 +1,6 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent } from '@testing-library/vue'
import { fireEvent, screen } from '@testing-library/vue'
import { socketService, volumeManager } from '@/services'
import { preferenceStore } from '@/stores'
import Volume from './Volume.vue'
@ -15,15 +15,15 @@ new class extends UnitTestCase {
protected test () {
it('mutes and unmutes', async () => {
const { getByTitle, html } = this.render(Volume)
const { html } = this.render(Volume)
expect(html()).toMatchSnapshot()
expect(volumeManager.volume.value).toEqual(5)
await fireEvent.click(getByTitle('Mute'))
await this.user.click(screen.getByTitle('Mute'))
expect(html()).toMatchSnapshot()
expect(volumeManager.volume.value).toEqual(0)
await fireEvent.click(getByTitle('Unmute'))
await this.user.click(screen.getByTitle('Unmute'))
expect(html()).toMatchSnapshot()
expect(volumeManager.volume.value).toEqual(5)
})
@ -31,10 +31,10 @@ new class extends UnitTestCase {
it('sets and broadcasts volume', async () => {
const setMock = this.mock(volumeManager, 'set')
const broadCastMock = this.mock(socketService, 'broadcast')
const { getByRole } = this.render(Volume)
this.render(Volume)
await fireEvent.update(getByRole('slider'), '4.2')
await fireEvent.change(getByRole('slider'))
await fireEvent.update(screen.getByRole('slider'), '4.2')
await fireEvent.change(screen.getByRole('slider'))
expect(setMock).toHaveBeenCalledWith(4.2)
expect(broadCastMock).toHaveBeenCalledWith('SOCKET_VOLUME_CHANGED', 4.2)

View file

@ -1,5 +1,5 @@
import { expect, it } from 'vitest'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { youTubeService } from '@/services'
import UnitTestCase from '@/__tests__/UnitTestCase'
import YouTubeVideoItem from './YouTubeVideoItem.vue'
@ -34,9 +34,9 @@ new class extends UnitTestCase {
it('plays', async () => {
const mock = this.mock(youTubeService, 'play')
const { getByRole } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByRole('button'))
await this.user.click(screen.getByRole('button'))
expect(mock).toHaveBeenCalledWith(video)
})

View file

@ -2,7 +2,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { youTubeService } from '@/services'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import Btn from '@/components/ui/Btn.vue'
import YouTubeVideo from '@/components/ui/YouTubeVideoItem.vue'
import YouTubeVideoList from './YouTubeVideoList.vue'
@ -20,7 +20,7 @@ new class extends UnitTestCase {
items: factory<YouTubeVideo>('video', 3)
})
const { getAllByTestId, getByRole } = this.render(YouTubeVideoList, {
this.render(YouTubeVideoList, {
props: {
song
},
@ -34,14 +34,14 @@ new class extends UnitTestCase {
await waitFor(() => {
expect(searchMock).toHaveBeenNthCalledWith(1, song, '')
expect(getAllByTestId('youtube-video')).toHaveLength(5)
expect(screen.getAllByRole('listitem')).toHaveLength(5)
})
await fireEvent.click(getByRole('button', { name: 'Load More' }))
await this.user.click(screen.getByRole('button', { name: 'Load More' }))
await waitFor(() => {
expect(searchMock).toHaveBeenNthCalledWith(2, song, 'foo')
expect(getAllByTestId('youtube-video')).toHaveLength(8)
expect(screen.getAllByRole('listitem')).toHaveLength(8)
})
})
}

View file

@ -1,6 +1,6 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import { UploadFile, uploadService, UploadStatus } from '@/services'
import Btn from '@/components/ui/Btn.vue'
import UploadItem from './UploadItem.vue'
@ -33,11 +33,11 @@ new class extends UnitTestCase {
protected test () {
it('renders', () => expect(this.renderComponent('Canceled').html()).toMatchSnapshot())
it.each<[UploadStatus]>([['Canceled'], ['Errored']])('allows retrying when %s', async (status) => {
it.each<[UploadStatus]>([['Canceled'], ['Errored']])('allows retrying when %s', async status => {
const mock = this.mock(uploadService, 'retry')
const { getByTitle } = this.renderComponent(status)
this.renderComponent(status)
await fireEvent.click(getByTitle('Retry'))
await this.user.click(screen.getByRole('button', { name: 'Retry' }))
expect(mock).toHaveBeenCalled()
})
@ -46,11 +46,11 @@ new class extends UnitTestCase {
['Uploaded'],
['Errored'],
['Canceled']]
)('allows removal if not uploading', async (status) => {
)('allows removal if not uploading', async status => {
const mock = this.mock(uploadService, 'remove')
const { getByTitle } = this.renderComponent(status)
this.renderComponent(status)
await fireEvent.click(getByTitle('Remove'))
await this.user.click(screen.getByRole('button', { name: 'Remove' }))
expect(mock).toHaveBeenCalled()
})

View file

@ -1,6 +1,6 @@
import { expect, it } from 'vitest'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { userStore } from '@/stores'
import { MessageToasterStub } from '@/__tests__/stubs'
import AddUserForm from './AddUserForm.vue'
@ -11,13 +11,13 @@ new class extends UnitTestCase {
const storeMock = this.mock(userStore, 'store')
const alertMock = this.mock(MessageToasterStub.value, 'success')
const { getByLabelText, getByRole } = this.render(AddUserForm)
this.render(AddUserForm)
await fireEvent.update(getByLabelText('Name'), 'John Doe')
await fireEvent.update(getByLabelText('Email'), 'john@doe.com')
await fireEvent.update(getByLabelText('Password'), 'secret-password')
await fireEvent.click(getByRole('checkbox'))
await fireEvent.click(getByRole('button', { name: 'Save' }))
await this.type(screen.getByRole('textbox', { name: 'Name' }), 'John Doe')
await this.type(screen.getByRole('textbox', { name: 'Email' }), 'john@doe.com')
await this.type(screen.getByLabelText('Password'), 'secret-password')
await this.user.click(screen.getByRole('checkbox'))
await this.user.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => {
expect(storeMock).toHaveBeenCalledWith({

View file

@ -3,7 +3,7 @@ import { expect, it } from 'vitest'
import factory from '@/__tests__/factory'
import UnitTestCase from '@/__tests__/UnitTestCase'
import { ModalContextKey } from '@/symbols'
import { fireEvent, waitFor } from '@testing-library/vue'
import { screen, waitFor } from '@testing-library/vue'
import { userStore } from '@/stores'
import { MessageToasterStub } from '@/__tests__/stubs'
import EditUserForm from './EditUserForm.vue'
@ -16,7 +16,7 @@ new class extends UnitTestCase {
const user = ref(factory<User>('user', { name: 'John Doe' }))
const { getByLabelText, getByRole } = this.render(EditUserForm, {
this.render(EditUserForm, {
global: {
provide: {
[<symbol>ModalContextKey]: [ref({ user })]
@ -24,9 +24,9 @@ new class extends UnitTestCase {
}
})
await fireEvent.update(getByLabelText('Name'), 'Jane Doe')
await fireEvent.update(getByLabelText('Password'), 'new-password-duck')
await fireEvent.click(getByRole('button', { name: 'Update' }))
await this.type(screen.getByRole('textbox', { name: 'Name' }), 'Jane Doe')
await this.type(screen.getByLabelText('Password'), 'new-password-duck')
await this.user.click(screen.getByRole('button', { name: 'Update' }))
await waitFor(() => {
expect(updateMock).toHaveBeenCalledWith(user.value, {

View file

@ -2,7 +2,7 @@ import UnitTestCase from '@/__tests__/UnitTestCase'
import factory from '@/__tests__/factory'
import { expect, it } from 'vitest'
import { eventBus } from '@/utils'
import { fireEvent } from '@testing-library/vue'
import { screen } from '@testing-library/vue'
import UserBadge from './UserBadge.vue'
new class extends UnitTestCase {
@ -17,9 +17,9 @@ new class extends UnitTestCase {
it('logs out', async () => {
const emitMock = this.mock(eventBus, 'emit')
const { getByTestId } = this.renderComponent()
this.renderComponent()
await fireEvent.click(getByTestId('btn-logout'))
await this.user.click(screen.getByRole('button', { name: 'Log out' }))
expect(emitMock).toHaveBeenCalledWith('LOG_OUT')
})

Some files were not shown because too many files have changed in this diff Show more