feat: Permettre la définition d'une semaine type récurrente pour l'emploi du temps
Les administrateurs devaient recréer manuellement l'emploi du temps chaque semaine. Cette implémentation introduit un système de récurrence hebdomadaire avec gestion des exceptions par occurrence, permettant de modifier ou annuler un cours spécifique sans affecter les autres semaines. Le ScheduleResolver calcule dynamiquement l'EDT réel en combinant les créneaux récurrents, les exceptions ponctuelles et le calendrier scolaire (vacances/fériés).
This commit is contained in:
@@ -120,11 +120,18 @@ async function waitForScheduleReady(page: import('@playwright/test').Page) {
|
||||
timeout: 15000
|
||||
});
|
||||
// Wait for either the grid or the empty state to appear
|
||||
await expect(page.locator('.schedule-grid, .empty-state, .alert-error')).toBeVisible({
|
||||
await expect(page.locator('.schedule-grid, .empty-state')).toBeVisible({
|
||||
timeout: 15000
|
||||
});
|
||||
}
|
||||
|
||||
async function chooseScopeAndEdit(page: import('@playwright/test').Page) {
|
||||
// Scope modal appears - choose "this occurrence"
|
||||
const scopeModal = page.getByRole('dialog');
|
||||
await expect(scopeModal).toBeVisible({ timeout: 10000 });
|
||||
await scopeModal.getByText('Cette occurrence uniquement').click();
|
||||
}
|
||||
|
||||
async function fillSlotForm(
|
||||
dialog: import('@playwright/test').Locator,
|
||||
options: {
|
||||
@@ -228,7 +235,7 @@ test.describe('Schedule Management - Modification & Conflicts & Calendar (Story
|
||||
// AC3: Slot Modification & Deletion
|
||||
// ==========================================================================
|
||||
test.describe('AC3: Slot Modification & Deletion', () => {
|
||||
test('clicking a slot opens edit modal', async ({ page }) => {
|
||||
test('clicking a slot opens scope modal then edit modal', async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
await page.goto(`${ALPHA_URL}/admin/schedule`);
|
||||
|
||||
@@ -246,11 +253,13 @@ test.describe('Schedule Management - Modification & Conflicts & Calendar (Story
|
||||
await dialog.getByRole('button', { name: /créer/i }).click();
|
||||
await expect(dialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click on the created slot
|
||||
// Click on the created slot — scope modal should appear
|
||||
const slotCard = page.locator('.slot-card').first();
|
||||
await expect(slotCard).toBeVisible({ timeout: 10000 });
|
||||
await slotCard.click();
|
||||
|
||||
await chooseScopeAndEdit(page);
|
||||
|
||||
// Edit modal should appear
|
||||
const editDialog = page.getByRole('dialog');
|
||||
await expect(editDialog).toBeVisible({ timeout: 10000 });
|
||||
@@ -277,17 +286,24 @@ test.describe('Schedule Management - Modification & Conflicts & Calendar (Story
|
||||
await dialog.getByRole('button', { name: /créer/i }).click();
|
||||
await expect(dialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click on the slot to edit
|
||||
// Click on the slot — scope modal then edit modal
|
||||
const slotCard = page.locator('.slot-card').first();
|
||||
await expect(slotCard).toBeVisible({ timeout: 10000 });
|
||||
await slotCard.click();
|
||||
|
||||
await chooseScopeAndEdit(page);
|
||||
|
||||
dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click delete button
|
||||
// Click delete button — opens scope modal again for delete scope
|
||||
await dialog.getByRole('button', { name: /supprimer/i }).click();
|
||||
|
||||
// Scope modal appears for delete action
|
||||
const scopeModal = page.locator('.modal-scope');
|
||||
await expect(scopeModal).toBeVisible({ timeout: 10000 });
|
||||
await scopeModal.getByText('Cette occurrence uniquement').click();
|
||||
|
||||
// Confirmation modal should appear
|
||||
const deleteModal = page.getByRole('alertdialog');
|
||||
await expect(deleteModal).toBeVisible({ timeout: 10000 });
|
||||
@@ -323,9 +339,11 @@ test.describe('Schedule Management - Modification & Conflicts & Calendar (Story
|
||||
await expect(slotCard).toBeVisible({ timeout: 10000 });
|
||||
await expect(slotCard.getByText('A101')).toBeVisible();
|
||||
|
||||
// Click to open edit modal
|
||||
// Click to open scope modal then edit modal
|
||||
await slotCard.click();
|
||||
|
||||
await chooseScopeAndEdit(page);
|
||||
|
||||
dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible({ timeout: 10000 });
|
||||
await expect(
|
||||
@@ -520,5 +538,61 @@ test.describe('Schedule Management - Modification & Conflicts & Calendar (Story
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).not.toBeVisible({ timeout: 3000 });
|
||||
});
|
||||
|
||||
test('recurring slots do not appear on vacation days in next week (AC4)', async ({
|
||||
page
|
||||
}) => {
|
||||
// Compute next week Thursday (use local format to avoid UTC shift)
|
||||
const thisThursday = new Date(getWeekdayInCurrentWeek(4) + 'T00:00:00');
|
||||
const nextThursday = new Date(thisThursday);
|
||||
nextThursday.setDate(thisThursday.getDate() + 7);
|
||||
const y = nextThursday.getFullYear();
|
||||
const m = String(nextThursday.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(nextThursday.getDate()).padStart(2, '0');
|
||||
const nextThursdayStr = `${y}-${m}-${d}`;
|
||||
|
||||
// Clean calendar entries and seed a vacation on next Thursday only
|
||||
cleanupCalendarEntries();
|
||||
seedBlockedDate(nextThursdayStr, 'Vacances AC4', 'vacation');
|
||||
clearCache();
|
||||
|
||||
await loginAsAdmin(page);
|
||||
await page.goto(`${ALPHA_URL}/admin/schedule`);
|
||||
await waitForScheduleReady(page);
|
||||
|
||||
// Create a Thursday slot (if none exists) so we have a recurring slot on Thursday
|
||||
const dayColumns = page.locator('.day-column');
|
||||
// Check if Thursday column (index 3) already has a slot
|
||||
const thursdaySlots = dayColumns.nth(3).locator('.slot-card');
|
||||
const existingCount = await thursdaySlots.count();
|
||||
|
||||
if (existingCount === 0) {
|
||||
// Create a slot on Thursday
|
||||
const thursdayCell = dayColumns.nth(3).locator('.time-cell').first();
|
||||
await thursdayCell.click();
|
||||
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible({ timeout: 10000 });
|
||||
await fillSlotForm(dialog, { dayValue: '4', startTime: '09:00', endTime: '10:00' });
|
||||
await dialog.getByRole('button', { name: /créer/i }).click();
|
||||
await expect(dialog).not.toBeVisible({ timeout: 10000 });
|
||||
await waitForScheduleReady(page);
|
||||
}
|
||||
|
||||
// Verify slot is visible on current week's Thursday
|
||||
await expect(dayColumns.nth(3).locator('.slot-card').first()).toBeVisible({
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
// Navigate to next week
|
||||
await page.getByRole('button', { name: 'Semaine suivante' }).click();
|
||||
await waitForScheduleReady(page);
|
||||
|
||||
// Next week Thursday should be blocked
|
||||
await expect(dayColumns.nth(3)).toHaveClass(/day-blocked/, { timeout: 10000 });
|
||||
|
||||
// No slot card should appear on the blocked Thursday
|
||||
await expect(dayColumns.nth(3).locator('.slot-card')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user