feat(test): add AppHeader component tests

This commit is contained in:
Phan An 2022-05-07 10:12:16 +02:00
parent 93c02a6b58
commit 93073814ca
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
9 changed files with 84 additions and 37 deletions

View file

@ -5,7 +5,7 @@ import album from './album'
import song from './song'
import video from './video'
import playlist from './playlist'
import user from './user'
import user, { states as userStates } from './user'
factory
.define('artist', (faker: Faker): Artist => artist(faker))
@ -13,6 +13,6 @@ factory
.define('song', (faker: Faker): Song => song(faker))
.define('video', (faker: Faker): YouTubeVideo => video(faker))
.define('playlist', (faker: Faker): Playlist => playlist(faker))
.define('user', (faker: Faker): User => user(faker))
.define('user', (faker: Faker): User => user(faker), userStates)
export default factory

View file

@ -9,3 +9,9 @@ export default (faker: Faker): User => ({
avatar: 'https://gravatar.com/foo',
preferences: {}
})
export const states = {
admin: {
is_admin: true
}
}

View file

@ -0,0 +1,61 @@
import { beforeEach, expect, it } from 'vitest'
import { mockHelper, render } from '@/__tests__/__helpers__'
import { cleanup, fireEvent, queryAllByTestId } from '@testing-library/vue'
import { eventBus } from '@/utils'
import { nextTick } from 'vue'
import isMobile from 'ismobilejs'
import AppHeader from './AppHeader.vue'
import SearchForm from '@/components/ui/SearchForm.vue'
import compareVersions from 'compare-versions'
import { userStore } from '@/stores'
import factory from '@/__tests__/factory'
beforeEach(() => {
cleanup()
mockHelper.restoreAllMocks()
isMobile.any = false
})
it('toggles sidebar (mobile only)', async () => {
isMobile.any = true
const { getByTitle } = render(AppHeader)
const mock = mockHelper.mock(eventBus, 'emit')
await fireEvent.click(getByTitle('Show or hide the sidebar'))
expect(mock).toHaveBeenCalledWith('TOGGLE_SIDEBAR')
})
it('toggles search form (mobile only)', async () => {
isMobile.any = true
const { getByTitle, getByTestId, queryByTestId } = render(AppHeader, {
global: {
stubs: {
SearchForm
}
}
})
expect(await queryByTestId('search-form')).toBe(null)
await fireEvent.click(getByTitle('Show or hide the search form'))
await nextTick()
getByTestId('search-form')
})
it.each([[true, true, true], [false, true, false], [true, false, false], [false, false, false]])(
'announces a new version if applicable',
async (hasNewVersion, isAdmin, announcing) => {
mockHelper.mock(compareVersions, 'compare', hasNewVersion)
userStore.state.current = factory<User>('user', {
is_admin: isAdmin
})
const { queryAllByTestId } = render(AppHeader)
expect(await queryAllByTestId('new-version')).toHaveLength(announcing ? 1 : 0)
}
)

View file

@ -1,15 +1,15 @@
<template>
<header id="mainHeader">
<h1 class="brand" v-once>{{ appConfig.name }}</h1>
<h1 class="brand">Koel</h1>
<span class="hamburger" role="button" title="Show or hide the sidebar" @click="toggleSidebar">
<i class="fa fa-bars"></i>
</span>
<span class="magnifier" role="button" title="Show or hide the search form" @click="toggleSearchForm">
<i class="fa fa-search"></i>
</span>
<search-form/>
<SearchForm v-if="showSearchForm"/>
<div class="header-right">
<user-badge/>
<UserBadge/>
<button
class="about control"
data-testid="about-btn"
@ -17,33 +17,29 @@
type="button"
@click.prevent="showAboutDialog"
>
<span v-if="shouldNotifyNewVersion" class="new-version" data-test="new-version-available">
<span v-if="shouldNotifyNewVersion" class="new-version" data-testid="new-version">
{{ latestVersion }} available!
</span>
<i v-else class="fa fa-info-circle"></i>
</button>
</div>
</header>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, toRef } from 'vue'
import { defineAsyncComponent, ref } from 'vue'
import { eventBus } from '@/utils'
import { app as appConfig } from '@/config'
import { commonStore, userStore } from '@/stores'
import { useNewVersionNotification } from '@/composables'
import isMobile from 'ismobilejs'
const SearchForm = defineAsyncComponent(() => import('@/components/ui/SearchForm.vue'))
const UserBadge = defineAsyncComponent(() => import('@/components/user/UserBadge.vue'))
const user = toRef(userStore.state, 'current')
const state = commonStore.state
const showSearchForm = ref(!isMobile.any)
const { shouldNotifyNewVersion, latestVersion } = useNewVersionNotification()
const toggleSidebar = () => eventBus.emit('TOGGLE_SIDEBAR')
const toggleSearchForm = () => eventBus.emit('TOGGLE_SEARCH_FORM')
const toggleSearchForm = () => (showSearchForm.value = !showSearchForm.value)
const showAboutDialog = () => eventBus.emit('MODAL_SHOW_ABOUT_KOEL')
</script>
@ -89,7 +85,7 @@ const showAboutDialog = () => eventBus.emit('MODAL_SHOW_ABOUT_KOEL')
@media only screen and (max-width: 667px) {
display: flex;
align-content: stretch;
justify-content: flext-start;
justify-content: flex-start;
.hamburger, .magnifier {
display: inline-block;

View file

@ -1,5 +1,5 @@
<template>
<div class="side search" id="searchForm" :class="{ showing }" role="search">
<div id="searchForm" class="side search" data-testid="search-form" role="search">
<input
ref="input"
v-model="q"
@ -16,16 +16,13 @@
</template>
<script lang="ts" setup>
import isMobile from 'ismobilejs'
import { ref } from 'vue'
import { debounce } from 'lodash'
import { eventBus } from '@/utils'
import router from '@/router'
const input = ref<HTMLInputElement>()
const q = ref('')
const showing = ref(!isMobile.phone)
const onInput = debounce(() => {
const _q = q.value.trim()
@ -35,8 +32,6 @@ const onInput = debounce(() => {
const goToSearchScreen = () => router.go('/search')
eventBus.on({
'TOGGLE_SEARCH_FORM': () => (showing.value = !showing.value),
FOCUS_SEARCH_FIELD () {
input.value?.focus()
input.value?.select()
@ -56,19 +51,14 @@ eventBus.on({
}
@media only screen and (max-width: 667px) {
z-index: -1;
z-index: 100;
position: absolute;
left: 0;
background: var(--color-bg-primary);
width: 100%;
padding: 12px;
top: 0;
&.showing {
top: var(--header-height);
border-bottom: 1px solid rgba(255, 255, 255, .1);
z-index: 100;
}
top: var(--header-height);
border-bottom: 1px solid rgba(255, 255, 255, .1);
input[type="search"] {
width: 100%;

View file

@ -1,3 +0,0 @@
export const app = {
name: 'Koel'
}

View file

@ -4,7 +4,6 @@ export type EventName =
| 'LOAD_MAIN_CONTENT'
| 'LOG_OUT'
| 'TOGGLE_SIDEBAR'
| 'TOGGLE_SEARCH_FORM'
| 'SHOW_OVERLAY'
| 'HIDE_OVERLAY'
| 'FOCUS_SEARCH_FIELD'

View file

@ -1,4 +1,3 @@
export * from './app'
export * from './events'
export * from './upload.types'
export * from './acceptedMediaTypes'

View file

@ -15,7 +15,6 @@ import {
} from '@/stores'
import { audioService, socketService } from '@/services'
import { app } from '@/config'
import router from '@/router'
/**
@ -156,7 +155,7 @@ export const playbackService = {
return
}
document.title = `${song.title}${app.name}`
document.title = `${song.title}Koel`
this.player!.media.setAttribute('title', `${song.artist.name} - ${song.title}`)
if (queueStore.current) {
@ -332,7 +331,7 @@ export const playbackService = {
},
stop () {
document.title = app.name
document.title = 'Koel'
this.getPlayer().pause()
this.getPlayer().seek(0)