mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Add NarratorCard and AuthorCard component tests
This commit is contained in:
parent
d638a328d8
commit
9e1c907591
192
client/components/cards/AuthorCard.cy.js
Normal file
192
client/components/cards/AuthorCard.cy.js
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Import the necessary dependencies
|
||||||
|
import AuthorCard from './AuthorCard.vue'
|
||||||
|
import AuthorImage from '../covers/AuthorImage.vue'
|
||||||
|
import Tooltip from '../ui/Tooltip.vue'
|
||||||
|
import LoadingSpinner from '../widgets/LoadingSpinner.vue'
|
||||||
|
|
||||||
|
describe('AuthorCard', () => {
|
||||||
|
const author = {
|
||||||
|
id: 1,
|
||||||
|
name: 'John Doe',
|
||||||
|
numBooks: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
author,
|
||||||
|
width: 192 * 0.8,
|
||||||
|
height: 192,
|
||||||
|
sizeMultiplier: 1,
|
||||||
|
nameBelow: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$strings: {
|
||||||
|
LabelBooks: 'Books',
|
||||||
|
ButtonQuickMatch: 'Quick Match'
|
||||||
|
},
|
||||||
|
$store : {
|
||||||
|
getters: {
|
||||||
|
'user/getUserCanUpdate': true,
|
||||||
|
'libraries/getLibraryProvider': () => 'audible.us'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
libraries: {
|
||||||
|
currentLibraryId: 'library-123'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$eventBus: {
|
||||||
|
$on: () => {},
|
||||||
|
$off: () => {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const stubs = {
|
||||||
|
'covers-author-image': AuthorImage,
|
||||||
|
'ui-tooltip': Tooltip,
|
||||||
|
'widgets-loading-spinner': LoadingSpinner
|
||||||
|
}
|
||||||
|
|
||||||
|
const mountOptions = { propsData, mocks, stubs }
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
cy.mount(AuthorCard, mountOptions)
|
||||||
|
|
||||||
|
cy.get('#textInline').should('be.visible')
|
||||||
|
cy.get('#match').should('be.hidden')
|
||||||
|
cy.get('#edit').should('be.hidden')
|
||||||
|
cy.get('#nameBelow').should('be.hidden')
|
||||||
|
cy.get('#card').should(($el) => {
|
||||||
|
const width = $el.width()
|
||||||
|
const height = $el.height()
|
||||||
|
expect(width).to.be.closeTo(propsData.width, 0.01)
|
||||||
|
expect(height).to.be.closeTo(propsData.height, 0.01)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component with the author name below', () => {
|
||||||
|
const updatedPropsData = { ...propsData, nameBelow: true }
|
||||||
|
cy.mount(AuthorCard, { ...mountOptions, propsData: updatedPropsData } )
|
||||||
|
|
||||||
|
cy.get('#textInline').should('be.hidden')
|
||||||
|
cy.get('#match').should('be.hidden')
|
||||||
|
cy.get('#edit').should('be.hidden')
|
||||||
|
let nameBelowHeight
|
||||||
|
cy.get('#nameBelow')
|
||||||
|
.should('be.visible')
|
||||||
|
.and('have.text', 'John Doe')
|
||||||
|
.and(($el) => {
|
||||||
|
const height = $el.height()
|
||||||
|
const width = $el.width()
|
||||||
|
const sizeMultiplier = propsData.sizeMultiplier
|
||||||
|
const defaultFontSize = 16
|
||||||
|
const defaultLineHeight = 1.5
|
||||||
|
const fontSizeMultiplier = 0.75
|
||||||
|
const px2 = 16
|
||||||
|
expect(height).to.be.closeTo(defaultFontSize * fontSizeMultiplier * sizeMultiplier * defaultLineHeight, 0.01)
|
||||||
|
nameBelowHeight = height
|
||||||
|
expect(width).to.be.closeTo(propsData.width - px2, 0.01)
|
||||||
|
})
|
||||||
|
cy.get('#card').should(($el) => {
|
||||||
|
const width = $el.width()
|
||||||
|
const height = $el.height()
|
||||||
|
const py1 = 8
|
||||||
|
expect(width).to.be.closeTo(propsData.width, 0.01)
|
||||||
|
expect(height).to.be.closeTo(propsData.height + nameBelowHeight + py1, 0.01)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders quick-match and edit buttons on mouse hover', () => {
|
||||||
|
cy.mount(AuthorCard, mountOptions )
|
||||||
|
|
||||||
|
// before mouseover
|
||||||
|
cy.get('#match').should('be.hidden')
|
||||||
|
cy.get('#edit').should('be.hidden')
|
||||||
|
// after mouseover
|
||||||
|
cy.get('#card').trigger('mouseover')
|
||||||
|
cy.get('#match').should('be.visible')
|
||||||
|
cy.get('#edit').should('be.visible')
|
||||||
|
// after mouseleave
|
||||||
|
cy.get('#card').trigger('mouseleave')
|
||||||
|
cy.get('#match').should('be.hidden')
|
||||||
|
cy.get('#edit').should('be.hidden')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component with spinner while searching', () => {
|
||||||
|
const data = () => { return { searching: true, isHovering: false } }
|
||||||
|
cy.mount(AuthorCard, { ...mountOptions, data } )
|
||||||
|
|
||||||
|
cy.get('#textInline').should('be.hidden')
|
||||||
|
cy.get('#match').should('be.hidden')
|
||||||
|
cy.get('#edit').should('be.hidden')
|
||||||
|
cy.get('#spinner').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('toasts after quick match with no updates', () => {
|
||||||
|
const updatedMocks = {
|
||||||
|
...mocks,
|
||||||
|
$axios: {
|
||||||
|
$post: cy.stub().resolves({ updated: false, author: { name: 'John Doe' } })
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
success: cy.spy().as('success'),
|
||||||
|
error: cy.spy().as('error'),
|
||||||
|
info: cy.spy().as('info')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
|
||||||
|
cy.get('#card').trigger('mouseover')
|
||||||
|
cy.get('#match').click()
|
||||||
|
|
||||||
|
cy.get("#spinner").should('be.hidden')
|
||||||
|
cy.get('@success').should('not.have.been.called')
|
||||||
|
cy.get('@error').should('not.have.been.called')
|
||||||
|
cy.get('@info').should('have.been.called')
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('toasts after quick match with updates and no image', () => {
|
||||||
|
const updatedMocks = {
|
||||||
|
...mocks,
|
||||||
|
$axios: {
|
||||||
|
$post: cy.stub().resolves({ updated: true, author: { name: 'John Doe' } })
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
success: cy.stub().withArgs('Author John Doe was updated (no image found)').as('success'),
|
||||||
|
error: cy.spy().as('error'),
|
||||||
|
info: cy.spy().as('info')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
|
||||||
|
cy.get('#card').trigger('mouseover')
|
||||||
|
cy.get('#match').click()
|
||||||
|
|
||||||
|
cy.get("#spinner").should('be.hidden')
|
||||||
|
cy.get('@success').should('have.been.called')
|
||||||
|
cy.get('@error').should('not.have.been.called')
|
||||||
|
cy.get('@info').should('not.have.been.called')
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('toasts after quick match with updates including image', () => {
|
||||||
|
const updatedMocks = {
|
||||||
|
...mocks,
|
||||||
|
$axios: {
|
||||||
|
$post: cy.stub().resolves({ updated: true, author: { name: 'John Doe', imagePath: "path/to/image" } })
|
||||||
|
},
|
||||||
|
$toast: {
|
||||||
|
success: cy.stub().withArgs('Author John Doe was updated').as('success'),
|
||||||
|
error: cy.spy().as('error'),
|
||||||
|
info: cy.spy().as('info')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
|
||||||
|
cy.get('#card').trigger('mouseover')
|
||||||
|
cy.get('#match').click()
|
||||||
|
|
||||||
|
cy.get("#spinner").should('be.hidden')
|
||||||
|
cy.get('@success').should('have.been.called')
|
||||||
|
cy.get('@error').should('not.have.been.called')
|
||||||
|
cy.get('@info').should('not.have.been.called')
|
||||||
|
})
|
||||||
|
})
|
@ -1,35 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<nuxt-link :to="`/author/${author.id}`">
|
<nuxt-link :to="`/author/${author?.id}`">
|
||||||
<div @mouseover="mouseover" @mouseleave="mouseleave">
|
<div id="card" :style="{ width: width + 'px'}" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
<div id="imageArea" :style="{ height: height + 'px' }" class=" bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||||
<!-- Image or placeholder -->
|
<!-- Image or placeholder -->
|
||||||
<covers-author-image :author="author" />
|
<covers-author-image :author="author"/>
|
||||||
|
|
||||||
<!-- Author name & num books overlay -->
|
<!-- Author name & num books overlay -->
|
||||||
<div v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
<div id="textInline" v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
||||||
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} {{ $strings.LabelBooks }}</p>
|
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} {{ $strings.LabelBooks }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search icon btn -->
|
<!-- Search icon btn -->
|
||||||
<div v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
<div id="match" v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
||||||
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
|
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
|
||||||
<span class="material-icons text-lg">search</span>
|
<span class="material-icons text-lg">search</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
<div id="edit" v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
||||||
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
|
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
|
||||||
<span class="material-icons text-lg">edit</span>
|
<span class="material-icons text-lg">edit</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading spinner -->
|
<!-- Loading spinner -->
|
||||||
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
<div id="spinner" v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||||
<widgets-loading-spinner size="" />
|
<widgets-loading-spinner size="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="nameBelow" class="w-full py-1 px-2">
|
<div id="nameBelow" v-show="nameBelow" class="w-full py-1 px-2">
|
||||||
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
86
client/components/cards/NarratorCard.cy.js
Normal file
86
client/components/cards/NarratorCard.cy.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import NarratorCard from './NarratorCard.vue'
|
||||||
|
|
||||||
|
describe('<NarratorCard />', () => {
|
||||||
|
const narrator = {
|
||||||
|
name: 'John Doe',
|
||||||
|
numBooks: 5
|
||||||
|
}
|
||||||
|
const propsData = {
|
||||||
|
narrator,
|
||||||
|
width: 200,
|
||||||
|
height: 150,
|
||||||
|
sizeMultiplier: 1.2
|
||||||
|
}
|
||||||
|
const mocks = {
|
||||||
|
$store: {
|
||||||
|
getters: {
|
||||||
|
'user/getUserCanUpdate': true
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
libraries: {
|
||||||
|
currentLibraryId: 'library-123'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$encode: (value) => value
|
||||||
|
}
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
// see: https://on.cypress.io/mounting-vue
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the narrator name correctly', () => {
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
|
||||||
|
cy.get('#name').should('have.text', 'John Doe')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the number of books correctly', () => {
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
|
||||||
|
cy.get('#numbooks').should('have.text', '5 Books')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders 1 book correctly', () => {
|
||||||
|
let propsData = { narrator: { name: 'John Doe', numBooks: 1 }, width: 200, height: 150, sizeMultiplier: 1.2 }
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
|
||||||
|
cy.get('#numbooks').should('have.text', '1 Book')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the default name and num-books when narrator is not provided', () => {
|
||||||
|
let propsData = { width: 200, height: 150, sizeMultiplier: 1.2 }
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
cy.get('#name').should('have.text', '')
|
||||||
|
cy.get('#numbooks').should('have.text', '0 Books')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the correct width and height', () => {
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
cy.get('#card').should('have.css', 'width', '200px')
|
||||||
|
cy.get('#card').should('have.css', 'height', '150px')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has the correct width and height when not provided', () => {
|
||||||
|
let propsData = { narrator, sizeMultiplier: 1.2 }
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
cy.get('#card').should('have.css', 'width', '150px')
|
||||||
|
cy.get('#card').should('have.css', 'height', '100px')
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('has the correct font sizes', () => {
|
||||||
|
let mountOptions = { propsData, mocks }
|
||||||
|
cy.mount(NarratorCard, mountOptions)
|
||||||
|
cy.get('#name').should('have.css', 'font-size', '14.4px') // 0.75 * 1.2 * 16
|
||||||
|
cy.get('#numbooks').should('have.css', 'font-size', '12.48px') // 0.65 * 1.2 * 16
|
||||||
|
})
|
||||||
|
})
|
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`">
|
||||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
<div id="card" :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||||
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
|
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
|
||||||
<span class="material-icons-outlined text-[10rem]">record_voice_over</span>
|
<span class="material-icons-outlined text-[10rem]">record_voice_over</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Narrator name & num books overlay -->
|
<!-- Narrator name & num books overlay -->
|
||||||
<div class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
<div class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
||||||
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
<p id="name" class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
<p id="numbooks" class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
@ -21,8 +21,14 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
width: Number,
|
width: {
|
||||||
height: Number,
|
type: Number,
|
||||||
|
default: 150
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
sizeMultiplier: {
|
sizeMultiplier: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
|
Loading…
Reference in New Issue
Block a user