const authenticatedUser = { id: 7, email: 'user@example.com', } const bibleLayout = { set: { id: 41, name: 'Bible' }, levels: [ { id: 1, kind: 'book', depth: 0, elementCount: 2 }, { id: 2, kind: 'portion', depth: 1, elementCount: 2 }, { id: 3, kind: 'chapter', depth: 2, elementCount: 1 }, ], elements: [ { id: 1, name: 'Genesis', kind: 'book', levelId: 1, children: [ { id: 3, name: 'Creation', kind: 'portion', levelId: 2, children: [ { id: 4, name: 'Chapter 1', kind: 'chapter', levelId: 3, children: [], }, ], }, { id: 5, name: 'Noah', kind: 'portion', levelId: 2, children: [] }, ], }, { id: 2, name: 'Exodus', kind: 'book', levelId: 1, children: [] }, ], } function interceptAuthenticatedUser(): void { cy.intercept('GET', '**/api/me', { statusCode: 200, body: { user: authenticatedUser }, }).as('me') } describe('set element layout', () => { beforeEach(() => { interceptAuthenticatedUser() }) it('opens a set from the dashboard and shows its full hierarchy', () => { cy.intercept('GET', '**/api/sets', { statusCode: 200, body: { sets: [{ id: 41, name: 'Bible' }] }, }).as('sets') cy.intercept('GET', '**/api/sets/41', (request) => { expect(request.headers.accept).to.equal('application/json') request.reply({ statusCode: 200, body: bibleLayout }) }).as('layout') cy.visit('/dashboard') cy.wait('@me') cy.wait('@sets') cy.contains('a', 'Bible').click() cy.wait('@layout') cy.location('pathname').should('equal', '/sets/41') cy.get('h1').should('have.text', 'Bible') cy.contains('a', 'Back to sets').should('have.attr', 'href', '/dashboard') cy.get('ol[aria-label="Bible element layout"] > li').then(($nodes) => { expect([...$nodes].map((node) => node.dataset.elementId)).to.deep.equal([ '1', '2', ]) }) cy.get('[data-element-id="1"] > .element-node__card') .should('contain.text', 'Genesis') .and('contain.text', 'book') cy.get('[data-element-id="1"] > ol > li').then(($nodes) => { expect([...$nodes].map((node) => node.dataset.elementId)).to.deep.equal([ '3', '5', ]) }) cy.get('[data-element-id="3"] > ol > li') .should('have.length', 1) .and('have.attr', 'data-element-id', '4') cy.contains('.element-node__card', 'Chapter 1') .should('be.visible') .and('contain.text', 'chapter') }) it('shows loading and empty layout states', () => { cy.intercept('GET', '**/api/sets/41', { delay: 500, statusCode: 200, body: { set: { id: 41, name: 'Empty set' }, levels: [], elements: [] }, }).as('layout') cy.visit('/sets/41') cy.wait('@me') cy.get('[role="status"]').should('have.text', 'Loading set layout...') cy.wait('@layout') cy.get('h1').should('have.text', 'Empty set') cy.get('[role="status"]').should( 'have.text', 'This set does not have any elements yet.', ) }) it('retries after a layout request fails', () => { let requestCount = 0 cy.intercept('GET', '**/api/sets/41', (request) => { requestCount += 1 request.alias = `layout${requestCount}` if (requestCount === 1) { request.reply({ statusCode: 500 }) return } request.reply({ statusCode: 200, body: bibleLayout }) }) cy.visit('/sets/41') cy.wait('@me') cy.wait('@layout1') cy.get('[role="alert"]').should( 'contain.text', "We couldn't load this set's layout.", ) cy.contains('button', 'Try again').click() cy.wait('@layout2') cy.get('h1').should('have.text', 'Bible') }) it('distinguishes missing sets from malformed responses', () => { cy.intercept('GET', '**/api/sets/41', { statusCode: 404, body: { error: 'set not found' }, }).as('missing') cy.visit('/sets/41') cy.wait('@me') cy.wait('@missing') cy.get('h1').should('have.text', 'Set not found') cy.get('[role="alert"]').should( 'contain.text', "The set you're looking for is not available.", ) cy.intercept('GET', '**/api/sets/42', { statusCode: 200, body: { set: { id: 42, name: 12 }, elements: [] }, }).as('malformed') cy.visit('/sets/42') cy.wait('@malformed') cy.get('[role="alert"]').should( 'contain.text', "We couldn't load this set's layout.", ) cy.contains('button', 'Try again').should('be.visible') }) it('keeps a deeply nested layout within a mobile viewport', () => { cy.viewport(390, 844) cy.intercept('GET', '**/api/sets/41', { statusCode: 200, body: bibleLayout, }).as('layout') cy.visit('/sets/41') cy.wait('@me') cy.wait('@layout') cy.contains('.element-node__card', 'Chapter 1').should('be.visible') cy.document().then((document) => { expect(document.documentElement.scrollWidth).to.be.at.most( document.documentElement.clientWidth, ) }) }) })