test set layout browsing

This commit is contained in:
Yisroel Baum 2026-08-08 23:30:11 +03:00
parent 936855efab
commit eb7ac7d261
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
8 changed files with 619 additions and 3 deletions

View file

@ -24,6 +24,13 @@ describe('guest authentication pages', () => {
cy.location('search').should('include', 'redirect=/dashboard')
})
it('redirects guests away from a protected set layout', () => {
cy.visit('/sets/41')
cy.location('pathname').should('equal', '/login')
cy.location('search').should('include', 'redirect=/sets/41')
})
it('shows the login form and links to signup', () => {
cy.visit('/login')

View file

@ -0,0 +1,183 @@
const authenticatedUser = {
id: 7,
email: 'user@example.com',
}
const bibleLayout = {
set: { id: 41, name: 'Bible' },
elements: [
{
id: 1,
name: 'Genesis',
kind: 'book',
children: [
{
id: 3,
name: 'Creation',
kind: 'portion',
children: [
{
id: 4,
name: 'Chapter 1',
kind: 'chapter',
children: [],
},
],
},
{ id: 5, name: 'Noah', kind: 'portion', children: [] },
],
},
{ id: 2, name: 'Exodus', kind: 'book', 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' }, 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,
)
})
})
})

View file

@ -15,7 +15,7 @@ describe('sets dashboard', () => {
interceptAuthenticatedUser()
})
it('shows every available set by name', () => {
it('shows every available set as a detail link', () => {
cy.intercept('GET', '**/api/sets', (request) => {
expect(request.headers.accept).to.equal('application/json')
request.reply({
@ -46,8 +46,13 @@ describe('sets dashboard', () => {
.should('not.contain.text', '41')
.and('not.contain.text', '58')
.and('not.contain.text', '92')
.find('a, button')
.should('not.exist')
cy.contains('a', 'Bible').should('have.attr', 'href', '/sets/41')
cy.contains('a', 'Course').should('have.attr', 'href', '/sets/58')
cy.contains('a', 'Fitness Program').should(
'have.attr',
'href',
'/sets/92',
)
})
it('shows loading and empty catalog states', () => {