mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 17:05:48 +00:00
* feat(task-repeat-cfg): use schedule dialog picker for recurring task start date * refactor(calendar): extract shared DateTimePickerComponent and use in schedule/deadline dialogs * style(calendar): make primary button gray when disabled and stronger green when enabled * style(calendar): upgrade date picker calendar visuals and remove outlines * fix(task-repeat-cfg): prevent premature duration formatting during typing * fix: resolve merge conflict and fix lint errors in dialog components * test(e2e): update recurring task tests to match new schedule dialog UI * test(e2e): fix regressions and ambiguous locators in recurring task tests Resolves strict mode violations for 'Schedule' button and updates locators to handle the new separate schedule dialog robustly using the datetime-picker filter. * fix: hide unschedule button in schedule dialog when opened from recurring task cfg * fix(datetime-picker): restore calendar first-week weekday alignment by using visibility:hidden instead of display:none for body-label cell * fix(i18n): use active UI language for date locale fallback, show full month names, and translate hardcoded Time label * test: fix DateTimeFormatService unit tests by mocking TranslateService * fix(task-repeat): add default reminders to recurring tasks * test(sync): increase lock reentry timeout to prevent flakiness * style(ui): align calendar cell shapes and make hover color darker * style(ui): style inputs and action buttons in schedule and deadline dialogs * feat(task-repeat): enable quick access scheduling for recurring tasks * feat(ui): separate month and year into distinct header buttons in datetime picker * style(ui): show dropdown arrow next to the year only * fix(locale): map UI fallbacks to parse-safe locales and sync DateAdapter * fix(task-repeat): fix option caching, DST normalization, and schedule dialog time preservation * test(task-repeat): mock formatTime in repeat dialog spec * fix(ui): improve datetime-picker month/year picking, arrow direction, transitions, and highlighter sync * fix(ui): defer year selection view transition and avoid year picker pagination updating highlighter * style(ui): restore focus outline and default material look * style(ui): drop custom hover circle and pill shape, and revert month-grid names to default short format * fix(ui): gate schedule warnings on isSelectDueOnly * style(ui): remove custom button color overrides * style(ui): restore default Material hover and focus behavior * fix(ui): add calendar header aria-labels and mirror arrows in RTL * test(e2e): fix recurring task start date specs for new datetime picker * revert(date-time): restore master's browser-region-first fallback in DateTimeFormatService * fix(ui): make quick-access tooltips configurable on datetime-picker and preserve deadline labels * fix(ui): exclude disabled previous/next calendar buttons from custom hover style * fix(ui): align period header button aria-labels with actual view transitions * style(ui): refactor recurring start date button styling with logical CSS and remove any type * fix(ui): preserve selected year when navigating in year picker and toggling back * style(ui): nudge default calendar cell hover with a shared token * fix(datetime-picker): sync hover selection with keyboard navigation and focus on open * fix(deadline): grey out past days in deadline calendar * style(datetime-picker): use shared calendar hover background for header buttons * style(calendar): use primary mixed color globally for calendar hover background * style(datetime-picker): hide mouse on arrow key nav and unify hover/focus colors * style(datetime-picker): resolve selected cell highlight bug and update hover outlines * style(datetime-picker): style current date/month/year selectors with accent color * fix(task-repeat-cfg): guard repeat schedule opening with isValidSplitTime * fix(task-repeat-cfg): only persist remindAt when a valid time is present * fix(ui): add disabled guards for calendar cells and improve scheduling result * fix(ui): improve focus management and navigation in datetime-picker * fix(ui): prevent invalid 24:00 time in datetime-picker and remove debug log * refactor(ui): cleanup datetime-picker and improve calendar header logic * feat(ui): enable year navigation and consistent month selection in datetime-picker * fix(ui): align multi-year boundary logic with Angular Material anchoring * fix(repeat): restore past start-date floor for existing repeat configs * test(e2e): fix setRecurStartDate to correctly navigate month and year * test(e2e): remove duplicate helper declarations causing SyntaxError * refactor(calendar): de-risk calendar by reverting brittle hover sync and custom header * test(calendar): update unit tests for datetime-picker de-risking * fix(calendar): restore standard year-to-month drill-down navigation * test(e2e): fix recurring task and planner task compatibility - Update setRecurStartDate helper to use standard Material calendar drill-down navigation, fixing compatibility with the new UI. - Update TaskPage helper to support both 'task' and 'planner-task' elements in board and planner views. * test(e2e): fix recurring task date selection and calendar navigation - Update setRecurStartDate helper to use correct click sequence for the new date selector header. - Remove minDate restriction in recurring task edit dialog to allow moving start dates earlier (#7423). * style(ui): remove stale calendar overrides and localize header labels * test(e2e): fix failing task detail date test due to calendar i18n label change * fix(ui): remove redundant global locale mutation from DateTimePickerComponent * fix(ui): prevent calendar navigation reset when date value hasn't changed * fix(planner): wire showQuickAccess and disable auto-submit in select-due-only mode * fix(repeat): allow past dates when editing repeat configurations * fix(repeat): use DateService for logical today check * fix(test): add coverage for navigation guard and interactive flows in DateTimePickerComponent * fix(test): restore time handling coverage for select-due-only mode in DialogScheduleTaskComponent * fix(tasks): implement robust reminder quantization using mid-points and add tests * feat(planner): add stable data-test-id locators for schedule dialog buttons * fix(e2e): replace fragile translated 'Schedule' locators with data-test-id * fix(ui): improve calendar styling and i18n consistency * refactor(ui): cleanup unused code in DateTimePickerComponent * fix(planner): restore auto-submit on quick-access in DialogScheduleTaskComponent * fix(repeat): refine minDate strategy for recurring configurations * chore(i18n): restore de.json to master
155 lines
5.5 KiB
TypeScript
155 lines
5.5 KiB
TypeScript
import { expect, Locator, Page } from '@playwright/test';
|
|
|
|
/**
|
|
* Shared helpers for the recurring-task start-date E2E specs
|
|
* (e2e/tests/recurring/*).
|
|
*
|
|
* These flows were duplicated — at varying levels of robustness — across the
|
|
* start-date specs. Centralising them keeps the flaky-edge fixes (datepicker
|
|
* text entry, SPA hash-route drops) in one place so one hardening pass covers
|
|
* every spec that uses them.
|
|
*/
|
|
|
|
const DIALOG_CONTAINER = 'mat-dialog-container';
|
|
|
|
/** Open the recurring-config dialog from the task detail panel's repeat row. */
|
|
export const openRecurDialog = async (page: Page): Promise<Locator> => {
|
|
const recurItem = page
|
|
.locator('task-detail-item')
|
|
.filter({ has: page.locator('mat-icon', { hasText: /^repeat$/ }) });
|
|
await expect(recurItem).toBeVisible({ timeout: 5000 });
|
|
await recurItem.click();
|
|
const dialog = page.locator(DIALOG_CONTAINER);
|
|
await dialog.waitFor({ state: 'visible', timeout: 10000 });
|
|
return dialog;
|
|
};
|
|
|
|
/**
|
|
* Open the recurring-config dialog by clicking a transparent planner
|
|
* projection. After the live instance is deleted, the repeat config can only be
|
|
* reached this way.
|
|
*/
|
|
export const openRecurDialogFromProjection = async (
|
|
page: Page,
|
|
taskTitle: string,
|
|
): Promise<Locator> => {
|
|
const projection = page
|
|
.locator('planner-repeat-projection')
|
|
.filter({ hasText: taskTitle })
|
|
.first();
|
|
await expect(projection).toBeVisible({ timeout: 15000 });
|
|
await projection.click();
|
|
const dialog = page.locator(DIALOG_CONTAINER);
|
|
await dialog.waitFor({ state: 'visible', timeout: 10000 });
|
|
return dialog;
|
|
};
|
|
|
|
/**
|
|
* Set the recurring "Start date" by using the calendar datepicker.
|
|
*/
|
|
export const setRecurStartDate = async (page: Page, ddmmyyyy: string): Promise<void> => {
|
|
const dialog = page.locator(DIALOG_CONTAINER).first();
|
|
const scheduleBtn = dialog.locator('.planned-start-date-btn');
|
|
await expect(scheduleBtn).toBeVisible({ timeout: 5000 });
|
|
await scheduleBtn.click();
|
|
|
|
const scheduleDialog = page
|
|
.locator(DIALOG_CONTAINER)
|
|
.filter({ has: page.locator('datetime-picker') });
|
|
await scheduleDialog.waitFor({ state: 'visible', timeout: 5000 });
|
|
|
|
const calendar = scheduleDialog.locator('mat-calendar');
|
|
await expect(calendar).toBeVisible({ timeout: 5000 });
|
|
|
|
const [dayStr, monthStr, yearStr] = ddmmyyyy.split('/');
|
|
const day = parseInt(dayStr, 10);
|
|
const month = parseInt(monthStr, 10) - 1; // 0-indexed
|
|
const year = parseInt(yearStr, 10);
|
|
|
|
// Navigate to correct year
|
|
await scheduleDialog.locator('.mat-calendar-period-button').click();
|
|
const yearCell = scheduleDialog
|
|
.locator('.mat-calendar-body-cell')
|
|
.filter({ hasText: new RegExp(`^\\s*${year}\\s*$`) })
|
|
.first();
|
|
await expect(yearCell).toBeVisible({ timeout: 5000 });
|
|
await yearCell.click();
|
|
|
|
// Navigate to correct month
|
|
const monthCell = scheduleDialog
|
|
.locator('.mat-calendar-body-cell')
|
|
.filter({
|
|
hasText: new RegExp(
|
|
`^\\s*${new Intl.DateTimeFormat('en-US', { month: 'short' }).format(new Date(year, month, 1))}\\s*$`,
|
|
'i',
|
|
),
|
|
})
|
|
.first();
|
|
await expect(monthCell).toBeVisible({ timeout: 5000 });
|
|
await monthCell.click();
|
|
|
|
// Select day
|
|
const dayCell = scheduleDialog
|
|
.locator('.mat-calendar-body-cell')
|
|
.filter({ hasText: new RegExp(`^\\s*${day}\\s*$`) })
|
|
.first();
|
|
await expect(dayCell).toBeVisible({ timeout: 5000 });
|
|
await dayCell.click();
|
|
|
|
// Click Schedule button
|
|
const scheduleSubmitBtn = scheduleDialog.locator(
|
|
'[data-test-id="schedule-submit-btn"]',
|
|
);
|
|
await scheduleSubmitBtn.click();
|
|
await scheduleDialog.waitFor({ state: 'hidden', timeout: 5000 });
|
|
};
|
|
|
|
/** Switch the recurring-config quick-setting select (e.g. Daily → Mon-Fri). */
|
|
export const setRecurQuickSetting = async (
|
|
page: Page,
|
|
optionLabel: RegExp,
|
|
): Promise<void> => {
|
|
const dialog = page.locator(DIALOG_CONTAINER);
|
|
await dialog.locator('mat-select').first().click();
|
|
const option = page.locator('mat-option').filter({ hasText: optionLabel });
|
|
await expect(option).toBeVisible({ timeout: 5000 });
|
|
await option.click();
|
|
};
|
|
|
|
/** Click Save in the recurring-config dialog and wait for it to close. */
|
|
export const saveRecurDialog = async (page: Page): Promise<void> => {
|
|
const dialog = page.locator(DIALOG_CONTAINER);
|
|
const saveBtn = dialog.getByRole('button', { name: /Save/i });
|
|
await expect(saveBtn).toBeEnabled({ timeout: 5000 });
|
|
await saveBtn.click();
|
|
await dialog.waitFor({ state: 'hidden', timeout: 10000 });
|
|
};
|
|
|
|
/**
|
|
* Navigate to a SPA hash route reliably. Playwright's page.goto only mutates the
|
|
* URL fragment for hash routes, and Angular's router occasionally drops that
|
|
* hashchange when goto lands mid-bootstrap — leaving the previous view mounted
|
|
* (e.g. the work-view stays on "Today" instead of switching to the Inbox
|
|
* project) and sometimes rewriting the fragment back to the old route. When the
|
|
* expected marker doesn't render, hop through about:blank so the next goto is a
|
|
* cross-document load that bootstraps the app fresh on the target URL and reads
|
|
* the fragment on init.
|
|
*/
|
|
export const gotoHashRoute = async (
|
|
page: Page,
|
|
route: string,
|
|
marker: Locator,
|
|
): Promise<void> => {
|
|
await page.goto(route);
|
|
await page.waitForLoadState('networkidle');
|
|
const landed = await marker
|
|
.waitFor({ state: 'visible', timeout: 5000 })
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
if (!landed) {
|
|
await page.goto('about:blank');
|
|
await page.goto(route);
|
|
await page.waitForLoadState('networkidle');
|
|
await expect(marker).toBeVisible({ timeout: 15000 });
|
|
}
|
|
};
|