diff --git a/client/components/cards/AuthorCard.cy.js b/client/components/cards/AuthorCard.cy.js new file mode 100644 index 00000000..0f78229e --- /dev/null +++ b/client/components/cards/AuthorCard.cy.js @@ -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') + }) +}) \ No newline at end of file diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index fc3bc4b2..e685e942 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -1,35 +1,35 @@