test(e2e): add day-change sync convergence test (#6992) and fix lint

Add supersync E2E test for day-change overdue sync race condition.
Fix prettier/lint issues in repeat task first occurrence spec.
This commit is contained in:
Johannes Millan 2026-03-29 17:20:06 +02:00
parent 3c8ac99e11
commit 821896496b
2 changed files with 133 additions and 7 deletions

View file

@ -96,9 +96,7 @@ test.describe('Issue #5594: First repeat occurrence should not always be today',
// 9. ASSERTION: The task should NOT be in today's undone task list
// because Saturday is not in the Mon/Wed/Fri pattern.
// The task should have been rescheduled to Monday (2026-06-15).
const undoneTasksWithTitle = taskPage
.getUndoneTasks()
.filter({ hasText: taskTitle });
const undoneTasksWithTitle = taskPage.getUndoneTasks().filter({ hasText: taskTitle });
// The task should disappear from Today view
await expect(undoneTasksWithTitle).toHaveCount(0, { timeout: 15000 });
@ -114,7 +112,9 @@ test.describe('Issue #5594: First repeat occurrence should not always be today',
// The task should appear in the planner under Monday (June 15)
// Planner renders tasks in day columns — look for the task text anywhere on the page
const plannerTask = page.getByText(taskTitle.replace(testPrefix + '-', testPrefix + '-'));
const plannerTask = page.getByText(
taskTitle.replace(testPrefix + '-', testPrefix + '-'),
);
await expect(plannerTask.first()).toBeVisible({ timeout: 15000 });
console.log('[Bug #5594] Task correctly appears in planner for future day');
@ -158,9 +158,7 @@ test.describe('Issue #5594: First repeat occurrence should not always be today',
await page.keyboard.press('Escape');
// 5. ASSERTION: Task SHOULD remain in Today for DAILY repeat
const undoneTasksWithTitle = taskPage
.getUndoneTasks()
.filter({ hasText: taskTitle });
const undoneTasksWithTitle = taskPage.getUndoneTasks().filter({ hasText: taskTitle });
await expect(undoneTasksWithTitle.first()).toBeVisible({ timeout: 10000 });
console.log('[Bug #5594] Daily repeat task correctly stays in Today');

View file

@ -0,0 +1,128 @@
import { test, expect } from '../../fixtures/supersync.fixture';
import {
createTestUser,
getSuperSyncConfig,
createSimulatedClient,
closeClient,
waitForTask,
getTaskTitles,
type SimulatedE2EClient,
} from '../../utils/supersync-helpers';
/**
* Issue #6992: Sync differences after new-day routines.
*
* Reproduces the scenario where day-change effects on one device create
* operations that conflict with user actions on another device.
*
* Simplified approach (avoids complex recurring task setup):
* 1. Both clients start on day X at 23:55
* 2. Client A creates a task (gets added to TODAY_TAG for day X)
* 3. Both sync
* 4. Advance both clocks past midnight to day X+1
* 5. Day-change effects fire on both, creating new repeat instances
* 6. Client A moves a task to today, Client B's day-change removes overdue
* 7. Both sync, verify convergence
*
* Key assertion: after sync, both clients show the same tasks in their
* today list. If they diverge, the LWW conflict resolution failed.
*/
test.describe('@supersync Day-change overdue sync (#6992)', () => {
test('tasks should converge after day change on both clients', async ({
browser,
baseURL,
testRunId,
}) => {
const appUrl = baseURL || 'http://localhost:4242';
let clientA: SimulatedE2EClient | null = null;
let clientB: SimulatedE2EClient | null = null;
try {
const user = await createTestUser(testRunId);
const syncConfig = getSuperSyncConfig(user);
const dayXTime = new Date('2026-07-20T23:55:00');
const dayX1Morning = new Date('2026-07-21T09:00:00');
const dayX1Later = new Date('2026-07-21T09:30:00');
// ============ PHASE 1: Client A creates tasks on day X ============
clientA = await createSimulatedClient(browser, appUrl, 'A', testRunId);
await clientA.page.clock.setFixedTime(dayXTime);
await clientA.page.reload();
await clientA.workView.waitForTaskList();
await clientA.sync.setupSuperSync(syncConfig);
const task1 = `Task1-${testRunId}`;
const task2 = `Task2-${testRunId}`;
await clientA.workView.addTask(task1);
await clientA.workView.addTask(task2);
await clientA.sync.syncAndWait();
console.log('[#6992] Client A created 2 tasks and synced');
// ============ PHASE 2: Client B downloads ============
clientB = await createSimulatedClient(browser, appUrl, 'B', testRunId);
await clientB.page.clock.setFixedTime(dayXTime);
await clientB.page.reload();
await clientB.workView.waitForTaskList();
await clientB.sync.setupSuperSync(syncConfig);
await clientB.sync.syncAndWait();
await waitForTask(clientB.page, task1);
await waitForTask(clientB.page, task2);
console.log('[#6992] Client B synced, has both tasks');
// ============ PHASE 3: Day change on Client A (morning) ============
// Advance Client A clock to day X+1 morning
await clientA.page.clock.setFixedTime(dayX1Morning);
// Wait for day-change effects to process
await clientA.page.waitForTimeout(5000);
console.log('[#6992] Client A advanced to day X+1 morning');
// Get Client A's task list
const titlesABefore = await getTaskTitles(clientA);
console.log('[#6992] Client A tasks after day change:', titlesABefore);
// Sync Client A
await clientA.sync.syncAndWait();
console.log('[#6992] Client A synced');
// ============ PHASE 4: Day change on Client B (later — THE RACE) ============
// Client B's clock advances AFTER Client A has already synced.
// Any operations created by Client B's day-change will have a NEWER timestamp.
await clientB.page.clock.setFixedTime(dayX1Later);
// Wait for day-change effects on Client B
await clientB.page.waitForTimeout(5000);
console.log('[#6992] Client B advanced to day X+1 (later)');
// ============ PHASE 5: Sync convergence ============
await clientB.sync.syncAndWait();
await clientA.sync.syncAndWait();
await clientB.sync.syncAndWait();
console.log('[#6992] All sync rounds complete');
// Allow UI to settle
await clientA.page.waitForTimeout(1000);
await clientB.page.waitForTimeout(1000);
// ============ PHASE 6: Verify convergence ============
const titlesA = await getTaskTitles(clientA);
const titlesB = await getTaskTitles(clientB);
console.log('[#6992] Client A final tasks:', titlesA);
console.log('[#6992] Client B final tasks:', titlesB);
// Both clients should have the SAME tasks in their today list.
// Order may differ, so compare as sorted arrays.
const sortedA = [...titlesA].sort();
const sortedB = [...titlesB].sort();
// This is the core convergence check.
// If this fails, the day-change operations created a conflict that
// was resolved differently on each device.
expect(sortedA).toEqual(sortedB);
console.log('[#6992] PASS: Both clients converged');
} finally {
if (clientA) await closeClient(clientA);
if (clientB) await closeClient(clientB);
}
});
});