From 6ab6ed801a2efcdc072b8f65d10d47576d77300a Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 19 Aug 2026 22:48:03 +0300 Subject: [PATCH] test reschedule form --- .../website/cypress/e2e/set-scheduling.cy.ts | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/frontend/website/cypress/e2e/set-scheduling.cy.ts b/frontend/website/cypress/e2e/set-scheduling.cy.ts index b799db0..d138e28 100644 --- a/frontend/website/cypress/e2e/set-scheduling.cy.ts +++ b/frontend/website/cypress/e2e/set-scheduling.cy.ts @@ -184,6 +184,103 @@ const splitScheduleDetail = { }, } +const rescheduleSourceDetail = { + schedule: { + ...scheduleDetail.schedule, + startDate: '2026-08-10', + targetDate: '2026-08-20', + assignmentCount: 4, + days: [ + { + date: '2026-08-10', + assignments: [ + { + ...scheduleDetail.schedule.days[0]?.assignments[0], + completedAt: '2026-08-11T08:30:00+00:00', + }, + ], + }, + { + date: '2026-08-18', + assignments: [ + { + id: 2, + completedAt: null, + element: { + name: 'Chapter 2', + kind: 'Chapter_sections-v2', + path: ['Genesis', 'Creation', 'Chapter 2'], + }, + }, + ], + }, + { + date: '2026-08-19', + assignments: [ + { + id: 3, + completedAt: null, + element: { + name: 'Chapter 3', + kind: 'Chapter_sections-v2', + path: ['Genesis', 'Creation', 'Chapter 3'], + }, + }, + ], + }, + { + date: '2026-08-20', + assignments: [ + { + id: 4, + completedAt: null, + element: { + name: 'Chapter 4', + kind: 'Chapter_sections-v2', + path: ['Genesis', 'Creation', 'Chapter 4'], + }, + }, + ], + }, + ], + }, +} + +const rescheduledDetail = { + schedule: { + ...rescheduleSourceDetail.schedule, + startDate: '2026-08-15', + targetDate: '2026-08-16', + days: [ + rescheduleSourceDetail.schedule.days[0], + { + date: '2026-08-15', + assignments: [rescheduleSourceDetail.schedule.days[1]?.assignments[0]], + }, + { + date: '2026-08-16', + assignments: [ + rescheduleSourceDetail.schedule.days[2]?.assignments[0], + rescheduleSourceDetail.schedule.days[3]?.assignments[0], + ], + }, + ], + }, +} + +const fullyCompletedScheduleDetail = { + schedule: { + ...scheduleDetail.schedule, + days: scheduleDetail.schedule.days.map((day) => ({ + ...day, + assignments: day.assignments.map((assignment) => ({ + ...assignment, + completedAt: '2026-08-15T09:30:00+00:00', + })), + })), + }, +} + function interceptAuthenticatedUser(): void { cy.intercept('GET', '**/api/me', { statusCode: 200, @@ -458,6 +555,99 @@ describe('set scheduling', () => { }) }) + it('reschedules unfinished assignments while preserving completed work', () => { + cy.clock(new Date(2026, 7, 15, 12).getTime(), ['Date']) + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: rescheduleSourceDetail, + }).as('schedule') + cy.intercept('PATCH', '**/api/schedules/73', (request) => { + expect(request.headers.accept).to.equal('application/json') + expect(request.body).to.deep.equal({ + startDate: '2026-08-15', + targetDate: '2026-08-16', + workloadPlacement: 'end', + }) + request.reply({ statusCode: 200, body: rescheduledDetail }) + }).as('reschedule') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + cy.get('[data-reschedule-form]').should('not.exist') + cy.get('[data-reschedule-toggle]').should('have.text', 'Reschedule remaining').click() + + cy.get('#reschedule-start-date').should('have.value', '2026-08-15') + cy.get('#reschedule-target-date').should('have.value', '2026-08-20') + cy.get('[data-workload-placement]').should('not.exist') + cy.get('#reschedule-target-date').clear().type('2026-08-16') + cy.get('[data-workload-placement]').should('be.visible').within(() => { + cy.get('input[value="middle"]').should('be.checked') + cy.get('input[value="end"]').check() + }) + cy.get('[data-reschedule-form]').submit() + cy.wait('@reschedule') + + cy.get('[data-reschedule-form]').should('not.exist') + cy.get('[data-reschedule-announcement]').should( + 'have.text', + '3 remaining assignments rescheduled.', + ) + cy.contains('4 Chapter_sections-v2 assignments across 2 days').should('be.visible') + cy.get('.schedule-range').should('contain.text', 'Aug 15, 2026 to Aug 16, 2026') + cy.get('[data-assignment-section="remaining"] [data-schedule-day]').then(($days) => { + expect([...$days].map((day) => day.getAttribute('data-schedule-date'))).to.deep.equal([ + '2026-08-15', + '2026-08-16', + ]) + }) + cy.get('[data-assignment-section="completed"] summary').click() + cy.get('[data-assignment-section="completed"] [data-schedule-day]') + .should('have.length', 1) + .and('have.attr', 'data-schedule-date', '2026-08-10') + + cy.get('[data-reschedule-toggle]').click() + cy.get('#reschedule-start-date').should('have.value', '2026-08-15') + cy.get('#reschedule-target-date').should('have.value', '2026-08-16') + }) + + it('uses today for an expired target and validates before rescheduling', () => { + cy.clock(new Date(2026, 7, 15, 12).getTime(), ['Date']) + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: scheduleDetail, + }).as('schedule') + cy.intercept('PATCH', '**/api/schedules/73').as('reschedule') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + cy.get('[data-reschedule-toggle]').click() + + cy.get('#reschedule-start-date').should('have.value', '2026-08-15') + cy.get('#reschedule-target-date').should('have.value', '2026-08-15') + cy.get('#reschedule-target-date').clear().type('2026-08-14') + cy.get('[data-reschedule-form]').submit() + cy.get('#reschedule-target-date-error').should( + 'have.text', + 'Target date cannot be before the start date.', + ) + cy.get('@reschedule.all').should('have.length', 0) + }) + + it('does not offer rescheduling after all assignments are complete', () => { + cy.intercept('GET', '**/api/schedules/73', { + statusCode: 200, + body: fullyCompletedScheduleDetail, + }).as('schedule') + + cy.visit('/schedules/73') + cy.wait('@me') + cy.wait('@schedule') + + cy.get('[data-reschedule-toggle]').should('not.exist') + }) + it('completes and reopens assignments from the schedule', () => { const completedAt = '2026-08-15T12:30:00+00:00' cy.intercept('GET', '**/api/schedules/73', {