migration: unit/component tests

This commit is contained in:
Phan An 2022-05-03 18:51:59 +02:00
parent 67b2bae7da
commit 806297fcb1
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
20 changed files with 182 additions and 187 deletions

View file

@ -3,7 +3,7 @@ context('About Koel', () => {
it('displays the About modal', () => {
cy.findByTestId('about-btn').click()
cy.findByTestId('about-modal').should('be.visible').within(() => cy.get('[data-test=close-modal-btn]').click())
cy.findByTestId('about-modal').should('be.visible').within(() => cy.findByTestId('close-modal-btn').click())
cy.findByTestId('about-modal').should('not.exist')
})
})

View file

@ -53,11 +53,11 @@ context('Albums', { scrollBehavior: false }, () => {
cy.findByText('Info').click()
})
cy.get('[data-test=album-info]').should('be.visible').within(() => {
cy.findByTestId('album-info').should('be.visible').within(() => {
cy.findByText('Album full wiki').should('be.visible')
cy.get('.cover').should('be.visible')
cy.get('[data-test=album-info-tracks]').should('be.visible').within(() => {
cy.findByTestId('album-info-tracks').should('be.visible').within(() => {
// out of 4 tracks, 3 are already available in Koel. The last one has a link to iTunes.
cy.get('li').should('have.length', 4)
cy.get('li.available').should('have.length', 3)
@ -65,8 +65,8 @@ context('Albums', { scrollBehavior: false }, () => {
})
})
cy.get('[data-test=close-modal-btn]').click()
cy.get('[data-test=album-info]').should('not.exist')
cy.findByTestId('close-modal-btn').click()
cy.findByTestId('album-info').should('not.exist')
})
})

View file

@ -7,19 +7,19 @@ context('Artists', { scrollBehavior: false }, () => {
it('loads the list of artists', () => {
cy.get('#artistsWrapper').within(() => {
cy.get('.screen-header').should('be.visible').and('contain.text', 'Artists')
cy.get('[data-test=view-mode-thumbnail]').should('be.visible').and('have.class', 'active')
cy.get('[data-test=view-mode-list]').should('be.visible').and('not.have.class', 'active')
cy.get('[data-test=artist-card]').should('have.length', 1)
cy.findByTestId('view-mode-thumbnail').should('be.visible').and('have.class', 'active')
cy.findByTestId('view-mode-list').should('be.visible').and('not.have.class', 'active')
cy.findByTestId('artist-card').should('have.length', 1)
})
})
it('changes display mode', () => {
cy.get('#artistsWrapper').should('be.visible').within(() => {
cy.get('[data-test=artist-card]').should('have.length', 1)
cy.get('[data-test=view-mode-list]').click()
cy.get('[data-test=artist-card].compact').should('have.length', 1)
cy.get('[data-test=view-mode-thumbnail]').click()
cy.get('[data-test=artist-card].full').should('have.length', 1)
cy.findByTestId('artist-card').should('have.length', 1)
cy.findByTestId('view-mode-list').click()
cy.findByTestId('artist-card].compact').should('have.length', 1)
cy.findByTestId('view-mode-thumbnail').click()
cy.$findInTestId('artist-card .full').should('have.length', 1)
})
})
@ -27,7 +27,7 @@ context('Artists', { scrollBehavior: false }, () => {
cy.$mockPlayback()
cy.get('#artistsWrapper').within(() => {
cy.get('[data-test=artist-card]:first-child .control-play')
cy.get('[data-testid=artist-card]:first-child .control-play')
.invoke('show')
.click()
})
@ -42,7 +42,7 @@ context('Artists', { scrollBehavior: false }, () => {
})
cy.get('#artistsWrapper').within(() => {
cy.get('[data-test=artist-card]:first-child .name').click()
cy.get('[data-testid=artist-card]:first-child .name').click()
cy.url().should('contain', '/#!/artist/3')
})
@ -54,13 +54,13 @@ context('Artists', { scrollBehavior: false }, () => {
cy.findByText('Info').click()
})
cy.get('[data-test=artist-info]').should('be.visible').within(() => {
cy.findByTestId('artist-info').should('be.visible').within(() => {
cy.findByText('Artist full bio').should('be.visible')
cy.get('.cover').should('be.visible')
})
cy.get('[data-test=close-modal-btn]').click()
cy.get('[data-test=artist-info]').should('not.exist')
cy.findByTestId('close-modal-btn').click()
cy.findByTestId('artist-info').should('not.exist')
})
})
})

View file

@ -32,7 +32,7 @@ context('Extra Information Panel', () => {
cy.get('#extraPanelArtist').should('be.visible').within(() => {
cy.get('[data-test=artist-info]').should('be.visible')
cy.findByText('Artist summary').should('be.visible')
cy.get('[data-test=more-btn]').click()
cy.findByTestId('more-btn').click()
cy.findByText('Artist summary').should('not.exist')
cy.findByText('Artist full bio').should('be.visible')
})
@ -44,7 +44,7 @@ context('Extra Information Panel', () => {
cy.get('#extraPanelAlbum').should('be.visible').within(() => {
cy.get('[data-test=album-info]').should('be.visible')
cy.findByText('Album summary').should('be.visible')
cy.get('[data-test=more-btn]').click()
cy.findByTestId('more-btn').click()
cy.findByText('Album summary').should('not.exist')
cy.findByText('Album full wiki').should('be.visible')
})

View file

@ -1,2 +1,3 @@
export * from './noop'
export * from './mock'
export * from './render'

View file

@ -2,9 +2,7 @@ import { vi } from 'vitest'
import { noop } from '@/utils'
declare type Procedure = (...args: any[]) => any;
declare type Methods<T> = {
[K in keyof T]: T[K] extends Procedure ? K : never;
}[keyof T] & (string | symbol);
declare type Methods<T> = { [K in keyof T]: T[K] extends Procedure ? K : never; }[keyof T] & (string | symbol);
export const mockHelper = {
backup: new Map(),
@ -25,7 +23,6 @@ export const mockHelper = {
restoreMocks () {
this.backup.forEach((fn, [obj, methodName]) => (obj[methodName] = fn))
this.backup = new Map()
}
}

View file

@ -0,0 +1,14 @@
import { render as baseRender, RenderOptions } from '@testing-library/vue'
import { clickaway, focus, droppable } from '@/directives'
export const render = (component: any, options: RenderOptions = {}) => {
return baseRender(component, Object.assign({
global: {
directives: {
'koel-clickaway': clickaway,
'koel-focus': focus,
'koel-droppable': droppable
}
}
}, options))
}

View file

@ -1,50 +0,0 @@
import Component from '@/components/album/AlbumCard.vue'
import Thumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
import factory from '@/__tests__/factory'
import { playbackService, downloadService } from '@/services'
import { commonStore } from '@/stores'
import { mock } from '@/__tests__/__helpers__'
import { mount, shallow } from '@/__tests__/adapter'
describe('components/album/card', () => {
let album: Album
beforeEach(() => {
album = factory<Album>('album', {
songs: factory<Song>('song', 10)
})
// @ts-ignore
commonStore.state = { allowDownload: true }
})
afterEach(() => {
jest.resetModules()
jest.clearAllMocks()
})
it('renders properly', async () => {
const wrapper = mount(Component, { propsData: { album } })
await wrapper.vm.$nextTick()
expect(wrapper.has(Thumbnail)).toBe(true)
const html = wrapper.html()
expect(html).toMatch(album.name)
expect(html).toMatch('10 songs')
})
it('shuffles', () => {
const wrapper = shallow(Component, { propsData: { album } })
const m = mock(playbackService, 'playAllInAlbum')
wrapper.click('.shuffle-album')
expect(m).toHaveBeenCalledWith(album, true)
})
it('downloads', () => {
const wrapper = shallow(Component, { propsData: { album } })
const m = mock(downloadService, 'fromAlbum')
wrapper.click('.download-album')
expect(m).toHaveBeenCalledWith(album)
})
})

View file

@ -1,45 +0,0 @@
import Component from '@/components/album/AlbumInfo.vue'
import AlbumThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
import factory from '@/__tests__/factory'
import { shallow, mount } from '@/__tests__/adapter'
describe('components/album/info', () => {
it('displays the info as a sidebar by default', () => {
const wrapper = shallow(Component, {
propsData: {
album: factory('album')
}
})
expect(wrapper.findAll('.album-info.sidebar')).toHaveLength(1)
expect(wrapper.findAll('.album-info.full')).toHaveLength(0)
})
it('can display the info in full mode', () => {
const wrapper = shallow(Component, {
propsData: {
album: factory('album'),
mode: 'full'
}
})
expect(wrapper.findAll('.album-info.sidebar')).toHaveLength(0)
expect(wrapper.findAll('.album-info.full')).toHaveLength(1)
})
it('triggers showing full wiki', () => {
const album = factory<Album>('album')
const wrapper = shallow(Component, {
propsData: { album }
})
wrapper.click('.wiki button.more')
expect(wrapper.html()).toMatch(album.info!.wiki!.full)
})
it('shows the album thumbnail', async () => {
const album = factory('album')
const wrapper = mount(Component, {
propsData: { album }
})
await wrapper.vm.$nextTick()
expect(wrapper.has(AlbumThumbnail)).toBe(true)
})
})

View file

@ -1,61 +1,61 @@
import Component from './AlbumCard.vue'
import { cleanup, fireEvent, render } from '@testing-library/vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import AlbumCard from './AlbumCard.vue'
import { cleanup, fireEvent } from '@testing-library/vue'
import { beforeEach, expect, it, vi } from 'vitest'
import { commonStore } from '@/stores'
import { downloadService, playbackService } from '@/services'
import { mockHelper } from '@/__tests__/__helpers__'
import { mockHelper, render } from '@/__tests__/__helpers__'
import factory from '@/__tests__/factory'
describe('AlbumCard', () => {
let album: Album
let album: Album
beforeEach(() => {
vi.restoreAllMocks()
mockHelper.restoreMocks()
cleanup()
beforeEach(() => {
vi.restoreAllMocks()
mockHelper.restoreMocks()
cleanup()
album = factory<Album>('album', {
name: 'IV',
songs: factory<Song>('song', 10)
})
commonStore.state.allowDownload = true
album = factory<Album>('album', {
name: 'IV',
songs: factory<Song>('song', 10)
})
it('renders', () => {
const { getByText, getByTestId } = render(Component, {
props: {
album
}
})
expect(getByTestId('name').innerText).equal('IV')
getByText(/^10 songs • .+ 0 plays$/)
getByTestId('shuffleAlbum')
getByTestId('downloadAlbum')
})
it('downloads', async () => {
const mock = mockHelper.mock(downloadService, 'fromAlbum')
const { getByTestId } = render(Component, {
props: {
album
}
})
await fireEvent.click(getByTestId('downloadAlbum'))
expect(mock).toHaveBeenCalledTimes(1)
})
it('shuffles', async () => {
const mock = mockHelper.mock(playbackService, 'playAllInAlbum')
const { getByTestId } = render(Component, {
props: {
album
}
})
await fireEvent.click(getByTestId('shuffleAlbum'))
expect(mock).toHaveBeenCalled()
})
commonStore.state.allowDownload = true
})
it('renders', () => {
const { getByText, getByTestId } = render(AlbumCard, {
props: {
album
}
})
expect(getByTestId('name').innerText).equal('IV')
getByText(/^10 songs • .+ 0 plays$/)
getByTestId('shuffle-album')
getByTestId('download-album')
})
it('downloads', async () => {
const mock = mockHelper.mock(downloadService, 'fromAlbum')
const { getByTestId } = render(AlbumCard, {
props: {
album
}
})
await fireEvent.click(getByTestId('download-album'))
expect(mock).toHaveBeenCalledTimes(1)
})
it('shuffles', async () => {
const mock = mockHelper.mock(playbackService, 'playAllInAlbum')
const { getByTestId } = render(AlbumCard, {
props: {
album
}
})
await fireEvent.click(getByTestId('shuffle-album'))
expect(mock).toHaveBeenCalled()
})

View file

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

View file

@ -0,0 +1,49 @@
import { beforeEach, expect, it, vi } from 'vitest'
import { mockHelper, render } from '@/__tests__/__helpers__'
import { cleanup, fireEvent } from '@testing-library/vue'
import factory from '@/__tests__/factory'
import AlbumInfo from '@/components/album/AlbumInfo.vue'
import AlbumThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
let album: Album
beforeEach(() => {
vi.restoreAllMocks()
mockHelper.restoreMocks()
cleanup()
album = factory<Album>('album', {
name: 'IV',
songs: factory<Song>('song', 10)
})
})
it.each([['sidebar'], ['full']])('renders in %s mode', async (mode: string) => {
const { getByTestId } = render(AlbumInfo, {
props: {
album,
mode
},
global: {
stubs: {
AlbumThumbnail
}
}
})
getByTestId('album-thumbnail')
const element = getByTestId<HTMLElement>('album-info')
expect(element.classList.contains(mode)).toBe(true)
})
it('triggers showing full wiki', async () => {
const { getByText } = render(AlbumInfo, {
props: {
album
}
})
await fireEvent.click(getByText('Full Wiki'))
getByText(album.info!.wiki!.full)
})

View file

@ -1,8 +1,8 @@
<template>
<article class="album-info" :class="mode" data-test="album-info">
<article :class="mode" class="album-info" data-testid="album-info">
<h1 class="name">
<span>{{ album.name }}</span>
<button :title="`Shuffle all songs in ${album.name}`" @click.prevent="shuffleAll" class="shuffle control">
<button :title="`Shuffle all songs in ${album.name}`" class="shuffle control" @click.prevent="shuffleAll">
<i class="fa fa-random"></i>
</button>
</h1>
@ -12,17 +12,17 @@
<template v-if="album.info">
<div class="wiki" v-if="album.info?.wiki?.summary">
<div class="summary" v-if="showSummary" v-html="album.info?.wiki?.summary"></div>
<div class="full" v-if="showFull" v-html="album.info?.wiki?.full"></div>
<div v-if="showSummary" class="summary" v-html="album.info?.wiki?.summary"></div>
<div v-if="showFull" class="full" v-html="album.info?.wiki?.full"></div>
<button class="more" v-if="showSummary" @click.prevent="showingFullWiki = true" data-test="more-btn">
<button v-if="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullWiki = true">
Full Wiki
</button>
</div>
<TrackList :album="album" v-if="album.info?.tracks?.length" data-test="album-info-tracks"/>
<TrackList v-if="album.info?.tracks?.length" :album="album" data-testid="album-info-tracks"/>
<footer>Data &copy; <a target="_blank" rel="noopener" :href="album.info?.url">Last.fm</a></footer>
<footer>Data &copy; <a :href="album.info?.url" rel="noopener" target="_blank">Last.fm</a></footer>
</template>
</main>
</article>

View file

@ -0,0 +1,23 @@
import { render } from '@/__tests__/__helpers__'
import factory from '@/__tests__/factory'
import { cleanup } from '@testing-library/vue'
import { beforeEach, expect, test } from 'vitest'
import AlbumTrackList from './AlbumTrackList.vue'
import TrackListItem from './AlbumTrackListItem.vue'
beforeEach(() => cleanup())
test('list the correct number of tracks', () => {
const { queryAllByTestId } = render(AlbumTrackList, {
props: {
album: factory<Album>('album')
},
global: {
stubs: {
TrackListItem
}
}
})
expect(queryAllByTestId('album-track-item')).toHaveLength(2)
})

View file

@ -9,6 +9,7 @@
:key="index"
:album="album"
:track="track"
data-testid="album-track-item"
/>
</ul>
</section>

View file

@ -1,5 +1,5 @@
<template>
<article class="artist-info" :class="mode" data-test="artist-info">
<article class="artist-info" :class="mode" data-testid="artist-info">
<h1 class="name">
<span>{{ artist.name }}</span>
<button :title="`Shuffle all songs by ${artist.name}`" class="shuffle control" @click.prevent="shuffleAll">
@ -15,7 +15,7 @@
<div v-if="showSummary" class="summary" v-html="artist.info?.bio?.summary"></div>
<div v-if="showFull" class="full" v-html="artist.info?.bio?.full"></div>
<button v-show="showSummary" class="more" data-test="more-btn" @click.prevent="showingFullBio = true">
<button v-show="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullBio = true">
Full Bio
</button>
</div>

View file

@ -39,7 +39,7 @@
</main>
<footer>
<Btn data-test="close-modal-btn" red rounded @click.prevent="close">Close</Btn>
<Btn data-testid="close-modal-btn" red rounded @click.prevent="close">Close</Btn>
</footer>
</div>
</template>

View file

@ -1,5 +1,10 @@
<template>
<span :class="{ droppable }" :style="{ backgroundImage: `url(${image})` }" class="cover">
<span
:class="{ droppable }"
:style="{ backgroundImage: `url(${image})` }"
class="cover"
data-testid="album-thumbnail"
>
<a
class="control control-play font-size-0"
href

View file

@ -21,7 +21,7 @@
import { nextTick, ref, toRefs } from 'vue'
import { eventBus } from '@/utils'
const props = defineProps<{ extraClass: string }>()
const props = defineProps<{ extraClass?: string }>()
const { extraClass } = toRefs(props)
const el = ref<HTMLElement>()

View file

@ -3,7 +3,7 @@
<label
:class="{ active: value === 'thumbnails' }"
class="thumbnails"
data-test="view-mode-thumbnail"
data-testid="view-mode-thumbnail"
title="View as thumbnails"
>
<input v-model="value" class="hidden" name="view-mode" type="radio" value="thumbnails">
@ -14,7 +14,7 @@
<label
:class="{ active: value === 'list' }"
class="list"
data-test="view-mode-list"
data-testid="view-mode-list"
title="View as list"
>
<input v-model="value" class="hidden" name="view-mode" type="radio" value="list">