From ea74ce545e727699ad34bd2bb5cff429dd7a99cd Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Tue, 18 Aug 2026 00:16:50 +0300 Subject: [PATCH] test schedule progress split --- .../website/cypress/e2e/set-scheduling.cy.ts | 201 ++++++++++++++++-- 1 file changed, 185 insertions(+), 16 deletions(-) diff --git a/frontend/website/cypress/e2e/set-scheduling.cy.ts b/frontend/website/cypress/e2e/set-scheduling.cy.ts index 9f83629..2a6984e 100644 --- a/frontend/website/cypress/e2e/set-scheduling.cy.ts +++ b/frontend/website/cypress/e2e/set-scheduling.cy.ts @@ -121,6 +121,55 @@ const completedScheduleDetail = { }, } +const splitScheduleDetail = { + schedule: { + ...scheduleDetail.schedule, + targetDate: '2026-08-13', + assignmentCount: 4, + days: [ + scheduleDetail.schedule.days[0], + scheduleDetail.schedule.days[1], + { + date: '2026-08-12', + assignments: [ + { + id: 2, + completedAt: '2026-08-11T08:30:00+00:00', + element: { + name: 'Chapter 1', + kind: 'Chapter_sections-v2', + path: ['Exodus', 'Shemot', 'Chapter 1'], + }, + }, + { + id: 3, + completedAt: null, + element: { + name: 'Chapter 1', + kind: 'Chapter_sections-v2', + path: ['Leviticus', 'Vayikra', 'Chapter 1'], + }, + }, + ], + }, + { + date: '2026-08-13', + assignments: [ + { + id: 4, + completedAt: '2026-08-11T09:45:00+00:00', + element: { + name: 'Chapter 1', + kind: 'Chapter_sections-v2', + path: ['Numbers', 'Bamidbar', 'Chapter 1'], + }, + }, + ], + }, + ], + }, +} + function interceptAuthenticatedUser(): void { cy.intercept('GET', '**/api/me', { statusCode: 200, @@ -159,9 +208,12 @@ describe('set scheduling', () => { cy.location('pathname').should('equal', '/sets/41/schedules/new') cy.get('h1').should('have.text', 'Schedule Bible') cy.get('#schedule-level option').then(($options) => { - expect([...$options].map((option) => option.textContent?.trim())).to.deep.equal( - ['Choose a level', 'book (2)', 'portion (2)', 'Chapter_sections-v2 (2)'], - ) + expect([...$options].map((option) => option.textContent?.trim())).to.deep.equal([ + 'Choose a level', + 'book (2)', + 'portion (2)', + 'Chapter_sections-v2 (2)', + ]) }) cy.get('#schedule-level').select('3') cy.get('#schedule-start-date').type('2026-08-10') @@ -184,6 +236,14 @@ describe('set scheduling', () => { 'contain.text', 'Exodus / Shemot / Chapter 1', ) + cy.get('[data-assignment-section="completed"]') + .should('not.have.attr', 'open') + .find('summary') + .click() + cy.get('[data-assignment-section="completed"]').should( + 'contain.text', + 'No assignments completed yet.', + ) }) it('validates the schedule form before submitting', () => { @@ -248,6 +308,69 @@ describe('set scheduling', () => { cy.get('h1').should('have.text', 'Schedule not found') }) + it('splits assignments by progress while preserving the schedule timeline', () => { + cy.clock(new Date(2026, 7, 11, 12).getTime(), ['Date']) + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: splitScheduleDetail, + }).as('schedule') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + + cy.get('[data-assignment-section="remaining"]') + .should('have.attr', 'data-assignment-count', '2') + .within(() => { + cy.get('h2').should('have.text', 'Remaining') + cy.get('[data-schedule-day]').then(($days) => { + expect([...$days].map((day) => day.getAttribute('data-schedule-date'))).to.deep.equal([ + '2026-08-10', + '2026-08-11', + '2026-08-12', + ]) + }) + cy.get('[data-schedule-date="2026-08-10"]') + .should('contain.text', 'Overdue') + .and('contain.text', 'Genesis / Creation / Chapter 1') + cy.get('[data-schedule-date="2026-08-11"]') + .should('contain.text', 'Rest day') + .and('not.contain.text', 'Overdue') + cy.get('[data-schedule-date="2026-08-12"]') + .should('contain.text', 'Leviticus / Vayikra / Chapter 1') + .and('not.contain.text', 'Exodus / Shemot / Chapter 1') + cy.get('[data-schedule-date="2026-08-13"]').should('not.exist') + }) + + cy.get('[data-assignment-section="completed"]') + .should('have.attr', 'data-assignment-count', '2') + .and('not.have.attr', 'open') + .within(() => { + cy.get('summary').should('contain.text', 'Completed').click() + cy.get('[data-schedule-day]').then(($days) => { + expect([...$days].map((day) => day.getAttribute('data-schedule-date'))).to.deep.equal([ + '2026-08-12', + '2026-08-13', + ]) + }) + cy.get('[data-schedule-date="2026-08-12"]') + .should('contain.text', 'Exodus / Shemot / Chapter 1') + .and('not.contain.text', 'Leviticus / Vayikra / Chapter 1') + cy.get('[data-schedule-date="2026-08-13"]').should( + 'contain.text', + 'Numbers / Bamidbar / Chapter 1', + ) + cy.contains('Rest day').should('not.exist') + }) + + cy.viewport(375, 667) + cy.document().then((document) => { + expect(document.documentElement.scrollWidth).to.be.at.most( + document.documentElement.clientWidth, + ) + }) + }) + it('completes and reopens assignments from the schedule', () => { const completedAt = '2026-08-15T12:30:00+00:00' cy.intercept('GET', '**/api/schedules/73', { @@ -278,33 +401,79 @@ describe('set scheduling', () => { cy.wait('@me') cy.wait('@schedule') - cy.get('[data-schedule-assignment="2"]').within(() => { + cy.get('[data-assignment-section="completed"] summary').click() + cy.get('[data-assignment-section="completed"] [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.get('[data-assignment-section="remaining"] [data-schedule-assignment="1"]').within(() => { + cy.contains('button', 'Mark complete').click() + cy.contains('button', 'Saving...').should('be.disabled') + }) + cy.wait('@completeAssignment') + cy.get('[data-assignment-section="remaining"]') + .should('have.attr', 'data-assignment-count', '0') + .and('contain.text', 'All assignments are complete.') + .and('contain.text', 'Rest day') + cy.get('[data-assignment-section="completed"]') + .should('have.attr', 'data-assignment-count', '2') + .within(() => { + 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') + }) + }) + cy.get('[data-completion-announcement]').should('have.text', 'Assignment moved to Completed.') + + cy.get('[data-assignment-section="completed"] [data-schedule-assignment="2"]').within(() => { cy.contains('button', 'Reopen').click() }) cy.wait('@reopenAssignment') - cy.get('[data-schedule-assignment="2"]') + cy.get('[data-assignment-section="remaining"]') + .should('have.attr', 'data-assignment-count', '1') + .find('[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-assignment-section="completed"]') + .should('have.attr', 'data-assignment-count', '1') + .find('[data-schedule-assignment="2"]') + .should('not.exist') + cy.get('[data-completion-announcement]').should('have.text', 'Assignment moved to Remaining.') + }) - cy.get('[data-schedule-assignment="1"]').within(() => { - cy.contains('button', 'Mark complete').click() - cy.contains('button', 'Saving...').should('be.disabled') - }) + it('keeps an assignment in place when a completion request fails', () => { + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: scheduleDetail, + }).as('schedule') + cy.intercept('PATCH', '**/api/assignments/1', { + statusCode: 500, + }).as('completeAssignment') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + + cy.get('[data-assignment-section="remaining"] [data-schedule-assignment="1"]') + .contains('button', 'Mark complete') + .click() 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') - }) + + cy.get('[data-assignment-section="remaining"] [data-schedule-assignment="1"]') + .should('be.visible') + .and('contain.text', "We couldn't update this assignment.") + cy.get('[data-assignment-section="completed"]') + .find('[data-schedule-assignment="1"]') + .should('not.exist') }) it('lists the users schedules on the dashboard', () => {