From 0d9e2fa71bf772e0690397d3decbc3780cc0f1a6 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sat, 15 Aug 2026 22:47:49 +0300 Subject: [PATCH] test assignment completion ui --- .../website/cypress/e2e/set-scheduling.cy.ts | 87 +++++++++++++++++++ .../cypress/e2e/today-assignments.cy.ts | 68 +++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/frontend/website/cypress/e2e/set-scheduling.cy.ts b/frontend/website/cypress/e2e/set-scheduling.cy.ts index a12f672..9f83629 100644 --- a/frontend/website/cypress/e2e/set-scheduling.cy.ts +++ b/frontend/website/cypress/e2e/set-scheduling.cy.ts @@ -74,6 +74,7 @@ const scheduleDetail = { assignments: [ { id: 1, + completedAt: null, element: { name: 'Chapter 1', kind: 'Chapter_sections-v2', @@ -88,6 +89,7 @@ const scheduleDetail = { assignments: [ { id: 2, + completedAt: null, element: { name: 'Chapter 1', kind: 'Chapter_sections-v2', @@ -100,6 +102,25 @@ const scheduleDetail = { }, } +const completedScheduleDetail = { + schedule: { + ...scheduleDetail.schedule, + days: [ + scheduleDetail.schedule.days[0], + scheduleDetail.schedule.days[1], + { + ...scheduleDetail.schedule.days[2], + assignments: [ + { + ...scheduleDetail.schedule.days[2]?.assignments[0], + completedAt: '2026-08-15T09:30:00+00:00', + }, + ], + }, + ], + }, +} + function interceptAuthenticatedUser(): void { cy.intercept('GET', '**/api/me', { statusCode: 200, @@ -227,6 +248,65 @@ describe('set scheduling', () => { cy.get('h1').should('have.text', 'Schedule not found') }) + it('completes and reopens assignments from the schedule', () => { + const completedAt = '2026-08-15T12:30:00+00:00' + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: completedScheduleDetail, + }).as('schedule') + cy.intercept('PATCH', '**/api/assignments/1', (request) => { + expect(request.body).to.deep.equal({ completed: true }) + request.reply({ + delay: 400, + statusCode: 200, + body: { + assignment: { id: 1, completedAt }, + }, + }) + }).as('completeAssignment') + cy.intercept('PATCH', '**/api/assignments/2', (request) => { + expect(request.body).to.deep.equal({ completed: false }) + request.reply({ + statusCode: 200, + body: { + assignment: { id: 2, completedAt: null }, + }, + }) + }).as('reopenAssignment') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + + cy.get('[data-schedule-assignment="2"]').within(() => { + cy.contains('Completed').should('be.visible') + cy.get('time') + .should('have.attr', 'datetime', '2026-08-15T09:30:00+00:00') + .and('have.text', formatCompletionTime('2026-08-15T09:30:00+00:00')) + cy.contains('button', 'Reopen').click() + }) + cy.wait('@reopenAssignment') + cy.get('[data-schedule-assignment="2"]') + .should('contain.text', 'Not completed') + .and('not.contain.text', 'Completed') + .within(() => { + cy.contains('button', 'Mark complete').should('be.enabled') + }) + + cy.get('[data-schedule-assignment="1"]').within(() => { + cy.contains('button', 'Mark complete').click() + cy.contains('button', 'Saving...').should('be.disabled') + }) + cy.wait('@completeAssignment') + cy.get('[data-schedule-assignment="1"]').within(() => { + cy.contains('Completed').should('be.visible') + cy.get('time') + .should('have.attr', 'datetime', completedAt) + .and('have.text', formatCompletionTime(completedAt)) + cy.contains('button', 'Reopen').should('be.enabled') + }) + }) + it('lists the users schedules on the dashboard', () => { cy.intercept('GET', '**/api/sets', { statusCode: 200, @@ -272,3 +352,10 @@ describe('set scheduling', () => { cy.contains('a', 'Bible').should('have.attr', 'href', '/schedules/73') }) }) + +function formatCompletionTime(value: string): string { + return new Intl.DateTimeFormat('en', { + dateStyle: 'medium', + timeStyle: 'short', + }).format(new Date(value)) +} diff --git a/frontend/website/cypress/e2e/today-assignments.cy.ts b/frontend/website/cypress/e2e/today-assignments.cy.ts index 9d31dde..77d2ba5 100644 --- a/frontend/website/cypress/e2e/today-assignments.cy.ts +++ b/frontend/website/cypress/e2e/today-assignments.cy.ts @@ -113,6 +113,74 @@ describe("today's assignments", () => { ) }) + it('completes an assignment and keeps failures retryable', () => { + cy.intercept('GET', `**/api/assignments?date=${browserToday}`, { + statusCode: 200, + body: { + date: browserToday, + assignments: [ + { + id: 12, + schedule: { + id: 73, + set: { name: 'Bible' }, + }, + element: { + name: 'Chapter 1', + kind: 'chapter', + path: ['Genesis', 'Chapter 1'], + }, + }, + ], + }, + }).as('todayAssignments') + let requestCount = 0 + cy.intercept('PATCH', '**/api/assignments/12', (request) => { + requestCount += 1 + request.alias = `completion${requestCount}` + expect(request.body).to.deep.equal({ completed: true }) + + if (requestCount === 1) { + request.reply({ delay: 400, statusCode: 500 }) + return + } + + request.reply({ + delay: 400, + statusCode: 200, + body: { + assignment: { + id: 12, + completedAt: '2026-08-15T12:30:00+00:00', + }, + }, + }) + }) + + cy.visit('/dashboard') + cy.wait('@me') + cy.wait('@todayAssignments') + + cy.get('[data-today-assignment="12"]').within(() => { + cy.contains('button', 'Mark complete').click() + cy.contains('button', 'Completing...').should('be.disabled') + }) + cy.wait('@completion1') + cy.get('[data-today-assignment="12"]') + .should('contain.text', "We couldn't update this assignment.") + .within(() => { + cy.contains('button', 'Mark complete').click() + cy.contains('button', 'Completing...').should('be.disabled') + }) + cy.wait('@completion2') + + cy.get('[data-today-assignment="12"]').should('not.exist') + cy.get('.today-assignments [role="status"]').should( + 'contain.text', + 'Nothing is assigned for today.', + ) + }) + it('retries independently after the request fails', () => { let requestCount = 0 cy.intercept(