From 4ddb6c6a445310e8a4fa8032798d9480d504826c Mon Sep 17 00:00:00 2001 From: Phan An Date: Thu, 5 May 2022 17:30:10 +0200 Subject: [PATCH] test: add FooterPlayerControls component tests --- cypress/integration/queuing.spec.ts | 4 +- .../assets/js/__tests__/__helpers__/mock.ts | 3 +- .../app-footer/FooterPlayerControl.spec.ts | 48 +++++++++++++++++++ .../app-footer/FooterPlayerControls.vue | 3 -- .../js/composables/useSongMenuMethods.ts | 2 +- resources/assets/js/router.ts | 2 +- resources/assets/js/services/socketService.ts | 2 +- resources/assets/js/types.d.ts | 14 +++--- resources/assets/js/utils/$.ts | 2 +- resources/assets/js/utils/alerts.ts | 8 ++-- resources/assets/js/utils/event.ts | 4 +- 11 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 resources/assets/js/components/layout/app-footer/FooterPlayerControl.spec.ts diff --git a/cypress/integration/queuing.spec.ts b/cypress/integration/queuing.spec.ts index 46663c24..1949255b 100644 --- a/cypress/integration/queuing.spec.ts +++ b/cypress/integration/queuing.spec.ts @@ -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() }) diff --git a/resources/assets/js/__tests__/__helpers__/mock.ts b/resources/assets/js/__tests__/__helpers__/mock.ts index 7906faeb..db9fa7a4 100644 --- a/resources/assets/js/__tests__/__helpers__/mock.ts +++ b/resources/assets/js/__tests__/__helpers__/mock.ts @@ -1,8 +1,7 @@ import { vi } from 'vitest' import { noop } from '@/utils' -declare type Procedure = (...args: any[]) => any; -declare type Methods = { [K in keyof T]: T[K] extends Procedure ? K : never; }[keyof T] & (string | symbol); +declare type Methods = { [K in keyof T]: T[K] extends Closure ? K : never; }[keyof T] & (string | symbol); export const mockHelper = { backup: new Map(), diff --git a/resources/assets/js/components/layout/app-footer/FooterPlayerControl.spec.ts b/resources/assets/js/components/layout/app-footer/FooterPlayerControl.spec.ts new file mode 100644 index 00000000..1d292087 --- /dev/null +++ b/resources/assets/js/components/layout/app-footer/FooterPlayerControl.spec.ts @@ -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') + } + }) + + 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', { + playbackState: 'Playing' + }) + } + }) + + await fireEvent.click(getByTitle('Pause')) + expect(mock).toHaveBeenCalled() +}) diff --git a/resources/assets/js/components/layout/app-footer/FooterPlayerControls.vue b/resources/assets/js/components/layout/app-footer/FooterPlayerControls.vue index 298985ae..ae321d8b 100644 --- a/resources/assets/js/components/layout/app-footer/FooterPlayerControls.vue +++ b/resources/assets/js/components/layout/app-footer/FooterPlayerControls.vue @@ -2,7 +2,6 @@
, close: TAnyFunction) => { +export const useSongMenuMethods = (songs: Ref, close: Closure) => { const queueSongsAfterCurrent = () => { queueStore.queueAfterCurrent(songs.value) close() diff --git a/resources/assets/js/router.ts b/resources/assets/js/router.ts index 94d38572..db52e070 100644 --- a/resources/assets/js/router.ts +++ b/resources/assets/js/router.ts @@ -34,7 +34,7 @@ const router = { playbackService.queueAndPlay([song]) } }) - } as { [path: string]: TAnyFunction }, + } as { [path: string]: Closure }, init () { this.loadState() diff --git a/resources/assets/js/services/socketService.ts b/resources/assets/js/services/socketService.ts index 03e034a3..6d68941e 100644 --- a/resources/assets/js/services/socketService.ts +++ b/resources/assets/js/services/socketService.ts @@ -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 } diff --git a/resources/assets/js/types.d.ts b/resources/assets/js/types.d.ts index 88edebd8..d0d57de4 100644 --- a/resources/assets/js/types.d.ts +++ b/resources/assets/js/types.d.ts @@ -17,18 +17,18 @@ declare module '*.svg' { export default value } -declare type TAnyFunction = (...args: Array) => unknown | any +declare type Closure = (...args: Array) => 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 { diff --git a/resources/assets/js/utils/$.ts b/resources/assets/js/utils/$.ts index 7c065d3c..29af827c 100644 --- a/resources/assets/js/utils/$.ts +++ b/resources/assets/js/utils/$.ts @@ -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 } diff --git a/resources/assets/js/utils/alerts.ts b/resources/assets/js/utils/alerts.ts index 125a53df..15bb86ce 100644 --- a/resources/assets/js/utils/alerts.ts +++ b/resources/assets/js/utils/alerts.ts @@ -9,21 +9,21 @@ const encodeEntities = (str: string) => str.replace(/&/g, '&') 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) } } diff --git a/resources/assets/js/utils/event.ts b/resources/assets/js/utils/event.ts index 17eafa1d..eeab0d44 100644 --- a/resources/assets/js/utils/event.ts +++ b/resources/assets/js/utils/event.ts @@ -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`) }