replace id attribute (which has to be unique across a document) with cy-id (which doesn't)

This commit is contained in:
mikiher 2024-04-29 08:30:14 +03:00
parent ec83eb0a27
commit e5fe31fe26
6 changed files with 124 additions and 124 deletions

View File

@ -52,11 +52,11 @@ describe('AuthorCard', () => {
it('renders the component', () => { it('renders the component', () => {
cy.mount(AuthorCard, mountOptions) cy.mount(AuthorCard, mountOptions)
cy.get('#textInline').should('be.visible') cy.get('&textInline').should('be.visible')
cy.get('#match').should('be.hidden') cy.get('&match').should('be.hidden')
cy.get('#edit').should('be.hidden') cy.get('&edit').should('be.hidden')
cy.get('#nameBelow').should('be.hidden') cy.get('&nameBelow').should('be.hidden')
cy.get('#card').should(($el) => { cy.get('&card').should(($el) => {
const width = $el.width() const width = $el.width()
const height = $el.height() const height = $el.height()
expect(width).to.be.closeTo(propsData.width, 0.01) expect(width).to.be.closeTo(propsData.width, 0.01)
@ -68,11 +68,11 @@ describe('AuthorCard', () => {
const updatedPropsData = { ...propsData, nameBelow: true } const updatedPropsData = { ...propsData, nameBelow: true }
cy.mount(AuthorCard, { ...mountOptions, propsData: updatedPropsData } ) cy.mount(AuthorCard, { ...mountOptions, propsData: updatedPropsData } )
cy.get('#textInline').should('be.hidden') cy.get('&textInline').should('be.hidden')
cy.get('#match').should('be.hidden') cy.get('&match').should('be.hidden')
cy.get('#edit').should('be.hidden') cy.get('&edit').should('be.hidden')
let nameBelowHeight let nameBelowHeight
cy.get('#nameBelow') cy.get('&nameBelow')
.should('be.visible') .should('be.visible')
.and('have.text', 'John Doe') .and('have.text', 'John Doe')
.and(($el) => { .and(($el) => {
@ -87,7 +87,7 @@ describe('AuthorCard', () => {
nameBelowHeight = height nameBelowHeight = height
expect(width).to.be.closeTo(propsData.width - px2, 0.01) expect(width).to.be.closeTo(propsData.width - px2, 0.01)
}) })
cy.get('#card').should(($el) => { cy.get('&card').should(($el) => {
const width = $el.width() const width = $el.width()
const height = $el.height() const height = $el.height()
const py1 = 8 const py1 = 8
@ -101,16 +101,16 @@ describe('AuthorCard', () => {
cy.mount(AuthorCard, mountOptions ) cy.mount(AuthorCard, mountOptions )
// before mouseover // before mouseover
cy.get('#match').should('be.hidden') cy.get('&match').should('be.hidden')
cy.get('#edit').should('be.hidden') cy.get('&edit').should('be.hidden')
// after mouseover // after mouseover
cy.get('#card').trigger('mouseover') cy.get('&card').trigger('mouseover')
cy.get('#match').should('be.visible') cy.get('&match').should('be.visible')
cy.get('#edit').should('be.visible') cy.get('&edit').should('be.visible')
// after mouseleave // after mouseleave
cy.get('#card').trigger('mouseleave') cy.get('&card').trigger('mouseleave')
cy.get('#match').should('be.hidden') cy.get('&match').should('be.hidden')
cy.get('#edit').should('be.hidden') cy.get('&edit').should('be.hidden')
}) })
@ -118,10 +118,10 @@ describe('AuthorCard', () => {
const data = () => { return { searching: true, isHovering: false } } const data = () => { return { searching: true, isHovering: false } }
cy.mount(AuthorCard, { ...mountOptions, data } ) cy.mount(AuthorCard, { ...mountOptions, data } )
cy.get('#textInline').should('be.hidden') cy.get('&textInline').should('be.hidden')
cy.get('#match').should('be.hidden') cy.get('&match').should('be.hidden')
cy.get('#edit').should('be.hidden') cy.get('&edit').should('be.hidden')
cy.get('#spinner').should('be.visible') cy.get('&spinner').should('be.visible')
}) })
it ('toasts after quick match with no updates', () => { it ('toasts after quick match with no updates', () => {
@ -137,10 +137,10 @@ describe('AuthorCard', () => {
} }
} }
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } ) cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
cy.get('#card').trigger('mouseover') cy.get('&card').trigger('mouseover')
cy.get('#match').click() cy.get('&match').click()
cy.get("#spinner").should('be.hidden') cy.get('&spinner').should('be.hidden')
cy.get('@success').should('not.have.been.called') cy.get('@success').should('not.have.been.called')
cy.get('@error').should('not.have.been.called') cy.get('@error').should('not.have.been.called')
cy.get('@info').should('have.been.called') cy.get('@info').should('have.been.called')
@ -159,10 +159,10 @@ describe('AuthorCard', () => {
} }
} }
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } ) cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
cy.get('#card').trigger('mouseover') cy.get('&card').trigger('mouseover')
cy.get('#match').click() cy.get('&match').click()
cy.get("#spinner").should('be.hidden') cy.get('&spinner').should('be.hidden')
cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated (no image found)') cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated (no image found)')
cy.get('@error').should('not.have.been.called') cy.get('@error').should('not.have.been.called')
cy.get('@info').should('not.have.been.called') cy.get('@info').should('not.have.been.called')
@ -181,10 +181,10 @@ describe('AuthorCard', () => {
} }
} }
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } ) cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks } )
cy.get('#card').trigger('mouseover') cy.get('&card').trigger('mouseover')
cy.get('#match').click() cy.get('&match').click()
cy.get("#spinner").should('be.hidden') cy.get('&spinner').should('be.hidden')
cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated') cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated')
cy.get('@error').should('not.have.been.called') cy.get('@error').should('not.have.been.called')
cy.get('@info').should('not.have.been.called') cy.get('@info').should('not.have.been.called')

View File

@ -1,34 +1,34 @@
<template> <template>
<nuxt-link :to="`/author/${author?.id}`"> <nuxt-link :to="`/author/${author?.id}`">
<div id="card" :style="{ width: width + 'px'}" @mouseover="mouseover" @mouseleave="mouseleave"> <div cy-id="card" :style="{ width: width + 'px'}" @mouseover="mouseover" @mouseleave="mouseleave">
<div id="imageArea" :style="{ height: height + 'px' }" class=" bg-primary box-shadow-book rounded-md relative overflow-hidden"> <div cy-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 id="textInline" v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2"> <div cy-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 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"> <div cy-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 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)"> <div cy-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 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"> <div cy-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 id="nameBelow" v-show="nameBelow" class="w-full py-1 px-2"> <div cy-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>

View File

@ -1,19 +1,19 @@
import LazySeriesCard from "./LazySeriesCard.vue" import LazySeriesCard from './LazySeriesCard.vue'
import GroupCover from "../covers/GroupCover.vue" import GroupCover from '../covers/GroupCover.vue'
describe("LazySeriesCard", () => { describe('LazySeriesCard', () => {
const series = { const series = {
id: 1, id: 1,
name: "The Lord of the Rings", name: 'The Lord of the Rings',
nameIgnorePrefix: "Lord of the Rings", nameIgnorePrefix: 'Lord of the Rings',
books: [ books: [
{ id: 1, updatedAt: /* 04/14/2024 */ 1713099600000, addedAt: 1713099600000, media: { coverPath: "cover1.jpg" }, title: "The Fellowship of the Ring" }, { id: 1, updatedAt: /* 04/14/2024 */ 1713099600000, addedAt: 1713099600000, media: { coverPath: 'cover1.jpg' }, title: 'The Fellowship of the Ring' },
{ id: 2, updatedAt: /* 04/15/2024 */ 1713186000000, addedAt: 1713186000000, media: { coverPath: "cover2.jpg" }, title: "The Two Towers" }, { id: 2, updatedAt: /* 04/15/2024 */ 1713186000000, addedAt: 1713186000000, media: { coverPath: 'cover2.jpg' }, title: 'The Two Towers' },
{ id: 3, updatedAt: /* 04/16/2024 */ 1713272400000, addedAt: 1713272400000, media: { coverPath: "cover3.jpg" }, title: "The Return of the King" } { id: 3, updatedAt: /* 04/16/2024 */ 1713272400000, addedAt: 1713272400000, media: { coverPath: 'cover3.jpg' }, title: 'The Return of the King' }
], ],
addedAt: /* 04/17/2024 */ 1713358800000, addedAt: /* 04/17/2024 */ 1713358800000,
totalDuration: /* 7h 30m */ 3600 * 7 + 60 * 30, totalDuration: /* 7h 30m */ 3600 * 7 + 60 * 30,
rssFeed: "https://example.com/feed.rss" rssFeed: 'https://example.com/feed.rss'
} }
const propsData = { const propsData = {
@ -26,84 +26,84 @@ describe("LazySeriesCard", () => {
isCategorized: false, isCategorized: false,
seriesMount: series, seriesMount: series,
sortingIgnorePrefix: false, sortingIgnorePrefix: false,
orderBy: "addedAt", orderBy: 'addedAt',
} }
const stubs = { const stubs = {
"covers-group-cover": GroupCover 'covers-group-cover': GroupCover
} }
const mocks = { const mocks = {
$store: { $store: {
getters: { getters: {
"user/getUserCanUpdate": true, 'user/getUserCanUpdate': true,
"user/getUserMediaProgress": (id) => null, 'user/getUserMediaProgress': (id) => null,
"libraries/getLibraryProvider": () => "audible.us", 'libraries/getLibraryProvider': () => 'audible.us',
"globals/getLibraryItemCoverSrc": () => "/book_placeholder.jpg" 'globals/getLibraryItemCoverSrc': () => '/book_placeholder.jpg'
}, },
state: { state: {
libraries: { libraries: {
currentLibraryId: "library-123" currentLibraryId: 'library-123'
}, },
serverSettings: { serverSettings: {
dateFormat: "MM/dd/yyyy" dateFormat: 'MM/dd/yyyy'
} }
} }
} }
} }
before(() => { before(() => {
cy.intercept("GET", "/book_placeholder.jpg", { fixture: "images/book_placeholder.jpg" }) cy.intercept('GET', '/book_placeholder.jpg', { fixture: 'images/book_placeholder.jpg' })
}) })
it("renders the component", () => { it('renders the component', () => {
cy.mount(LazySeriesCard, { propsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData, stubs, mocks })
cy.get('#series-card-0').should(($el) => { cy.get('&card').should(($el) => {
const width = $el.width() const width = $el.width()
const height = $el.height() const height = $el.height()
expect(width).to.be.closeTo(propsData.width, 0.01) expect(width).to.be.closeTo(propsData.width, 0.01)
expect(height).to.be.closeTo(propsData.height, 0.01) expect(height).to.be.closeTo(propsData.height, 0.01)
}) })
cy.get("#seriesLengthMarker").should("be.visible").and("have.text", propsData.seriesMount.books.length) cy.get('&seriesLengthMarker').should('be.visible').and('have.text', propsData.seriesMount.books.length)
cy.get("#seriesProgressBar").should("not.exist") cy.get('&seriesProgressBar').should('not.exist')
cy.get("#hoveringDisplayTitle").should("be.hidden") cy.get('&hoveringDisplayTitle').should('be.hidden')
cy.get("#rssFeedMarker").should("be.visible") cy.get('&rssFeedMarker').should('be.visible')
cy.get("#standardBottomDisplayTitle").should("not.exist") cy.get('&standardBottomDisplayTitle').should('not.exist')
cy.get("#detailBottomDisplayTitle").should("be.visible") cy.get('&detailBottomDisplayTitle').should('be.visible')
cy.get("#detailBottomDisplayTitle").should("have.text", "The Lord of the Rings") cy.get('&detailBottomDisplayTitle').should('have.text', 'The Lord of the Rings')
cy.get("#detailBottomSortLine").should("have.text", "Added 04/17/2024") cy.get('&detailBottomSortLine').should('have.text', 'Added 04/17/2024')
}) })
it("shows series name and hides rss feed marker on mouseover", () => { it('shows series name and hides rss feed marker on mouseover', () => {
cy.mount(LazySeriesCard, { propsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData, stubs, mocks })
cy.get("#series-card-0").trigger("mouseover") cy.get('&card').trigger('mouseover')
cy.get("#hoveringDisplayTitle").should("be.visible").should("have.text", "The Lord of the Rings") cy.get('&hoveringDisplayTitle').should('be.visible').should('have.text', 'The Lord of the Rings')
cy.get("#rssFeedMarker").should("not.exist") cy.get('&rssFeedMarker').should('not.exist')
}) })
it("routes properly when clicked", () => { it('routes properly when clicked', () => {
const updatedMocks = { const updatedMocks = {
...mocks, ...mocks,
$router: { $router: {
push: cy.stub().as("routerPush") push: cy.stub().as('routerPush')
} }
} }
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks}) cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks})
cy.get("#series-card-0").click() cy.get('&card').click()
cy.get("@routerPush").should("have.been.calledOnceWithExactly", "/library/library-123/series/1") cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/library/library-123/series/1')
}) })
it("shows progress bar when progress is available", () => { it('shows progress bar when progress is available', () => {
const updatedMocks = { const updatedMocks = {
...mocks, ...mocks,
$store: { $store: {
...mocks.$store, ...mocks.$store,
getters: { getters: {
...mocks.$store.getters, ...mocks.$store.getters,
"user/getUserMediaProgress": (id) => { 'user/getUserMediaProgress': (id) => {
switch (id) { switch (id) {
case 1: case 1:
return { isFinished: true } return { isFinished: true }
@ -118,96 +118,96 @@ describe("LazySeriesCard", () => {
} }
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks }) cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks })
cy.get("#seriesProgressBar") cy.get('&seriesProgressBar')
.should("be.visible") .should('be.visible')
.and("have.class", "bg-yellow-400") .and('have.class', 'bg-yellow-400')
.and(($el) => { .and(($el) => {
const width = $el.width() const width = $el.width()
expect(width).to.be.closeTo((2/3) * propsData.width, 0.01) expect(width).to.be.closeTo((2/3) * propsData.width, 0.01)
}) })
}) })
it("shows full green progress bar when all books are finished", () => { it('shows full green progress bar when all books are finished', () => {
const updatedMocks = { const updatedMocks = {
...mocks, ...mocks,
$store: { $store: {
...mocks.$store, ...mocks.$store,
getters: { getters: {
...mocks.$store.getters, ...mocks.$store.getters,
"user/getUserMediaProgress": (id) => { return { isFinished: true } } 'user/getUserMediaProgress': (id) => { return { isFinished: true } }
} }
} }
} }
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks }) cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks })
cy.get("#seriesProgressBar") cy.get('&seriesProgressBar')
.should("be.visible") .should('be.visible')
.and("have.class", "bg-success") .and('have.class', 'bg-success')
.and(($el) => { .and(($el) => {
const width = $el.width() const width = $el.width()
expect(width).to.equal(propsData.width) expect(width).to.equal(propsData.width)
}) })
}) })
it("hides the rss feed marker when there is no rss feed", () => { it('hides the rss feed marker when there is no rss feed', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
seriesMount: { ...series, rssFeed: null } seriesMount: { ...series, rssFeed: null }
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#rssFeedMarker").should("not.exist") cy.get('&rssFeedMarker').should('not.exist')
}) })
it("shows the standard bottom display when bookshelf view is 0", () => { it('shows the standard bottom display when bookshelf view is 0', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
bookshelfView: 0 bookshelfView: 0
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#standardBottomDisplayTitle").should("be.visible") cy.get('&standardBottomDisplayTitle').should('be.visible')
cy.get("#detailBottomDisplayTitle").should("not.exist") cy.get('&detailBottomDisplayTitle').should('not.exist')
}) })
it("shows total duration in sort line when orderBy is totalDuration", () => { it('shows total duration in sort line when orderBy is totalDuration', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
orderBy: "totalDuration" orderBy: 'totalDuration'
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#detailBottomSortLine").should("have.text", "Duration 7h 30m") cy.get('&detailBottomSortLine').should('have.text', 'Duration 7h 30m')
}) })
it("shows last book updated date in sort line when orderBy is lastBookUpdated", () => { it('shows last book updated date in sort line when orderBy is lastBookUpdated', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
orderBy: "lastBookUpdated" orderBy: 'lastBookUpdated'
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#detailBottomSortLine").should("have.text", "Last Book Updated 04/16/2024") cy.get('&detailBottomSortLine').should('have.text', 'Last Book Updated 04/16/2024')
}) })
it("shows last book added date in sort line when orderBy is lastBookAdded", () => { it('shows last book added date in sort line when orderBy is lastBookAdded', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
orderBy: "lastBookAdded" orderBy: 'lastBookAdded'
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#detailBottomSortLine").should("have.text", "Last Book Added 04/16/2024") cy.get('&detailBottomSortLine').should('have.text', 'Last Book Added 04/16/2024')
}) })
it("shows nameIgnorePrefix when sortingIgnorePrefix is true", () => { it('shows nameIgnorePrefix when sortingIgnorePrefix is true', () => {
const updatedPropsData = { const updatedPropsData = {
...propsData, ...propsData,
sortingIgnorePrefix: true sortingIgnorePrefix: true
} }
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks }) cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
cy.get("#detailBottomDisplayTitle").should("have.text", "Lord of the Rings") cy.get('&detailBottomDisplayTitle').should('have.text', 'Lord of the Rings')
}) })
}) })

View File

@ -1,28 +1,28 @@
<template> <template>
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard"> <div cy-id="card" ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" /> <div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0"> <div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="displayTitle" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="displayTitle" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div> </div>
<div id="seriesLengthMarker" class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ books.length }}</div> <div cy-id="seriesLengthMarker" class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ books.length }}</div>
<div id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" /> <div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
<div id="hoveringDisplayTitle" v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }"> <div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p> <p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
</div> </div>
<span id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span> <span cy-id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
<div id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }"> <div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }"> <div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
<p id="standardBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p> <p cy-id="standardBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
</div> </div>
</div> </div>
<div id="detailBottomText" v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center"> <div cy-id="detailBottomText" v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
<p id="detailBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p> <p cy-id="detailBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
<p id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p> <p cy-id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
</div> </div>
</div> </div>
</template> </template>

View File

@ -35,14 +35,14 @@ describe('<NarratorCard />', () => {
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#name').should('have.text', 'John Doe') cy.get('&name').should('have.text', 'John Doe')
}) })
it('renders the number of books correctly', () => { it('renders the number of books correctly', () => {
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#numBooks').should('have.text', '5 Books') cy.get('&numBooks').should('have.text', '5 Books')
}) })
it('renders 1 book correctly', () => { it('renders 1 book correctly', () => {
@ -50,7 +50,7 @@ describe('<NarratorCard />', () => {
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#numBooks').should('have.text', '1 Book') cy.get('&numBooks').should('have.text', '1 Book')
}) })
@ -58,29 +58,29 @@ describe('<NarratorCard />', () => {
let propsData = { width: 200, height: 150, sizeMultiplier: 1.2 } let propsData = { width: 200, height: 150, sizeMultiplier: 1.2 }
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#name').should('have.text', '') cy.get('&name').should('have.text', '')
cy.get('#numBooks').should('have.text', '0 Books') cy.get('&numBooks').should('have.text', '0 Books')
}) })
it('has the correct width and height', () => { it('has the correct width and height', () => {
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#card').should('have.css', 'width', '200px') cy.get('&card').should('have.css', 'width', '200px')
cy.get('#card').should('have.css', 'height', '150px') cy.get('&card').should('have.css', 'height', '150px')
}) })
it('has the correct width and height when not provided', () => { it('has the correct width and height when not provided', () => {
let propsData = { narrator, sizeMultiplier: 1.2 } let propsData = { narrator, sizeMultiplier: 1.2 }
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#card').should('have.css', 'width', '150px') cy.get('&card').should('have.css', 'width', '150px')
cy.get('#card').should('have.css', 'height', '100px') cy.get('&card').should('have.css', 'height', '100px')
}) })
it ('has the correct font sizes', () => { it ('has the correct font sizes', () => {
let mountOptions = { propsData, mocks } let mountOptions = { propsData, mocks }
cy.mount(NarratorCard, mountOptions) cy.mount(NarratorCard, mountOptions)
cy.get('#name').should('have.css', 'font-size', '14.4px') // 0.75 * 1.2 * 16 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 cy.get('&numBooks').should('have.css', 'font-size', '12.48px') // 0.65 * 1.2 * 16
}) })
}) })

View File

@ -1,14 +1,14 @@
<template> <template>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`"> <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`">
<div id="card" :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden"> <div cy-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 id="name" class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p> <p cy-id="name" class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
<p id="numBooks" class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p> <p cy-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>