test: add FooterPlayerControls component tests

This commit is contained in:
Phan An 2022-05-05 17:30:10 +02:00
parent 4e2351c85e
commit 4ddb6c6a44
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
11 changed files with 68 additions and 24 deletions

View file

@ -91,11 +91,11 @@ context('Queuing', { scrollBehavior: false }, () => {
cy.$shuffleSeveralSongs()
cy.get('#queueWrapper .song-item:nth-child(1)').should('have.class', 'playing')
cy.findByTestId('play-next-btn').click({ force: true })
cy.findByTitle('Play next song').click({ force: true })
cy.get('#queueWrapper .song-item:nth-child(2)').should('have.class', 'playing')
cy.$assertPlaying()
cy.findByTestId('play-prev-btn').click({ force: true })
cy.findByTitle('Play previous song').click({ force: true })
cy.get('#queueWrapper .song-item:nth-child(1)').should('have.class', 'playing')
cy.$assertPlaying()
})

View file

@ -1,8 +1,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 Closure ? K : never; }[keyof T] & (string | symbol);
export const mockHelper = {
backup: new Map(),

View file

@ -0,0 +1,48 @@
import { beforeEach, expect, it } from 'vitest'
import { cleanup, fireEvent } from '@testing-library/vue'
import { mockHelper, render } from '@/__tests__/__helpers__'
import { playbackService } from '@/services'
import factory from '@/__tests__/factory'
import FooterPlayerControls from './FooterPlayerControls.vue'
beforeEach(() => {
cleanup()
mockHelper.restoreAllMocks()
})
declare type PlaybackMethod = {
[K in keyof typeof playbackService]:
typeof playbackService[K] extends Closure ? K : never;
}[keyof typeof playbackService]
it.each<[string, string, PlaybackMethod]>([
['plays next song', 'Play next song', 'playNext'],
['plays previous song', 'Play previous song', 'playPrev'],
['plays/resumes current song', 'Play or resume', 'toggle']
])('%s', async (_: string, title: string, method: PlaybackMethod) => {
const mock = mockHelper.mock(playbackService, method)
const { getByTitle } = render(FooterPlayerControls, {
props: {
song: factory<Song>('song')
}
})
await fireEvent.click(getByTitle(title))
expect(mock).toHaveBeenCalled()
})
it('pauses the current song', async () => {
const mock = mockHelper.mock(playbackService, 'toggle')
const { getByTitle } = render(FooterPlayerControls, {
props: {
song: factory<Song>('song', {
playbackState: 'Playing'
})
}
})
await fireEvent.click(getByTitle('Pause'))
expect(mock).toHaveBeenCalled()
})

View file

@ -2,7 +2,6 @@
<div class="side player-controls">
<i
class="prev fa fa-step-backward control"
data-testid="play-prev-btn"
role="button"
tabindex="0"
title="Play previous song"
@ -14,7 +13,6 @@
<span
v-if="shouldShowPlayButton"
class="play"
data-testid="play-btn"
role="button"
tabindex="0"
title="Play or resume"
@ -37,7 +35,6 @@
<i
class="next fa fa-step-forward control"
data-testid="play-next-btn"
role="button"
tabindex="0"
title="Play next song"

View file

@ -6,7 +6,7 @@ import { alerts, pluralize } from '@/utils'
* Includes the methods trigger-able on a song (context) menu.
* Each component including this mixin must have a `songs` array as either data, prop, or computed.
*/
export const useSongMenuMethods = (songs: Ref<Song[]>, close: TAnyFunction) => {
export const useSongMenuMethods = (songs: Ref<Song[]>, close: Closure) => {
const queueSongsAfterCurrent = () => {
queueStore.queueAfterCurrent(songs.value)
close()

View file

@ -34,7 +34,7 @@ const router = {
playbackService.queueAndPlay([song])
}
})
} as { [path: string]: TAnyFunction },
} as { [path: string]: Closure },
init () {
this.loadState()

View file

@ -35,7 +35,7 @@ export const socketService = {
return this
},
listen (eventName: string, cb: TAnyFunction) {
listen (eventName: string, cb: Closure) {
this.channel && this.channel.bind(`client-${eventName}.${userStore.current.id}`, data => cb(data))
return this
}

View file

@ -17,18 +17,18 @@ declare module '*.svg' {
export default value
}
declare type TAnyFunction = (...args: Array<unknown | any>) => unknown | any
declare type Closure = (...args: Array<unknown | any>) => unknown | any
declare module 'alertify.js' {
function alert (msg: string): void
function confirm (msg: string, okFunc: TAnyFunction, cancelFunc?: TAnyFunction): void
function confirm (msg: string, okFunc: Closure, cancelFunc?: Closure): void
function success (msg: string, cb?: TAnyFunction): void
function success (msg: string, cb?: Closure): void
function error (msg: string, cb?: TAnyFunction): void
function error (msg: string, cb?: Closure): void
function log (msg: string, cb?: TAnyFunction): void
function log (msg: string, cb?: Closure): void
function logPosition (position: string): void
@ -101,7 +101,7 @@ interface Window {
}
interface FileSystemDirectoryReader {
readEntries (successCallback: TAnyFunction, errorCallback?: TAnyFunction): FileSystemEntry[]
readEntries (successCallback: Closure, errorCallback?: Closure): FileSystemEntry[]
}
interface FileSystemEntry {
@ -113,7 +113,7 @@ interface FileSystemEntry {
createReader (): FileSystemDirectoryReader
file (successCallback: TAnyFunction): void
file (successCallback: Closure): void
}
interface AlbumTrack {

View file

@ -6,7 +6,7 @@ export const $ = {
addClass: (el: Element | null, className: string) => el?.classList.add(className),
removeClass: (el: Element | null, className: string) => el?.classList.remove(className),
scrollTo (el: Element, to: number, duration: number, cb?: TAnyFunction) {
scrollTo (el: Element, to: number, duration: number, cb?: Closure) {
if (duration <= 0 || !el) {
return
}

View file

@ -9,21 +9,21 @@ const encodeEntities = (str: string) => str.replace(/&/g, '&amp;')
export const alerts = {
alert: (msg: string) => alertify.alert(encodeEntities(msg)),
confirm: (msg: string, okFunc: TAnyFunction, cancelFunc?: TAnyFunction) => {
confirm: (msg: string, okFunc: Closure, cancelFunc?: Closure) => {
alertify.confirm(msg, okFunc, cancelFunc)
},
log: (msg: string, type: logType = 'log', cb?: TAnyFunction) => {
log: (msg: string, type: logType = 'log', cb?: Closure) => {
alertify.logPosition('top right')
alertify.closeLogOnClick(true)
alertify[type](encodeEntities(msg), cb)
},
success (msg: string, cb?: TAnyFunction) {
success (msg: string, cb?: Closure) {
this.log(msg, 'success', cb)
},
error (msg: string, cb?: TAnyFunction) {
error (msg: string, cb?: Closure) {
this.log(msg, 'error', cb)
}
}

View file

@ -3,7 +3,7 @@ import { EventName } from '@/config'
export const eventBus = {
all: new Map(),
on (name: EventName | EventName[] | Partial<{ [K in EventName]: TAnyFunction }>, callback?: TAnyFunction) {
on (name: EventName | EventName[] | Partial<{ [K in EventName]: Closure }>, callback?: Closure) {
if (Array.isArray(name)) {
name.forEach(k => this.on(k, callback))
return
@ -26,7 +26,7 @@ export const eventBus = {
emit (name: EventName, ...args: any) {
if (this.all.has(name)) {
this.all.get(name).forEach((cb: TAnyFunction) => cb(...args))
this.all.get(name).forEach((cb: Closure) => cb(...args))
} else {
console.warn(`Event ${name} is not listened by any component`)
}