From 20c036ae0f010895915f1ab61b7cb81d0db02ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 08:00:14 +0200 Subject: [PATCH 1/7] ci: test angular19 against the matching frontend branch PR CI checks out the frontend (and sibling plugins) at the branch the PR targets instead of always stable. Corepack is enabled before every yarn install, since the frontend's angular19 branch pins yarn@4; the Docker build uses yarn for the frontend build as well. Dead WDIO config copies are dropped (the frontend no longer has WDIO). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/dotnet-core-master.yml | 4 ++++ .github/workflows/dotnet-core-pr.yml | 10 +++++++--- Dockerfile | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet-core-master.yml b/.github/workflows/dotnet-core-master.yml index 6f8f1089b..eb94374fb 100644 --- a/.github/workflows/dotnet-core-master.yml +++ b/.github/workflows/dotnet-core-master.yml @@ -76,6 +76,8 @@ jobs: cp -av eform-angular-timeplanning-plugin/eform-client/playwright/helpers eform-angular-frontend/eform-client/playwright/helpers cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh + - name: Enable Corepack + run: corepack enable - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Run Angular unit tests @@ -148,6 +150,8 @@ jobs: cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts cp -av eform-angular-timeplanning-plugin/eform-client/cypress.config.ts eform-angular-frontend/eform-client/cypress.config.ts cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh + - name: Enable Corepack + run: corepack enable - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Install Playwright browsers diff --git a/.github/workflows/dotnet-core-pr.yml b/.github/workflows/dotnet-core-pr.yml index 0e2f0651f..615f5805a 100644 --- a/.github/workflows/dotnet-core-pr.yml +++ b/.github/workflows/dotnet-core-pr.yml @@ -18,7 +18,7 @@ jobs: with: fetch-depth: 0 repository: microting/eform-angular-frontend - ref: stable + ref: ${{ github.base_ref }} path: eform-angular-frontend - name: Copy dependencies run: | @@ -53,7 +53,7 @@ jobs: with: fetch-depth: 0 repository: microting/eform-angular-frontend - ref: stable + ref: ${{ github.base_ref }} path: eform-angular-frontend - name: Use Node.js uses: actions/setup-node@v3 @@ -69,6 +69,8 @@ jobs: cp -av eform-angular-timeplanning-plugin/eform-client/playwright/helpers eform-angular-frontend/eform-client/playwright/helpers cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh + - name: Enable Corepack + run: corepack enable - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Run Angular unit tests @@ -124,7 +126,7 @@ jobs: uses: actions/checkout@v3 with: repository: microting/eform-angular-frontend - ref: stable + ref: ${{ github.base_ref }} path: eform-angular-frontend - name: Copy dependencies run: | @@ -137,6 +139,8 @@ jobs: cp -av eform-angular-timeplanning-plugin/eform-client/playwright.config.ts eform-angular-frontend/eform-client/playwright.config.ts cp -av eform-angular-timeplanning-plugin/eform-client/cypress.config.ts eform-angular-frontend/eform-client/cypress.config.ts cd eform-angular-frontend/eform-client && ../../eform-angular-timeplanning-plugin/testinginstallpn.sh + - name: Enable Corepack + run: corepack enable - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Install Playwright browsers diff --git a/Dockerfile b/Dockerfile index 35167cb4c..5b89a9766 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ARG DISABLE_SENTRY ENV PATH /app/node_modules/.bin:$PATH ENV DISABLE_SENTRY=${DISABLE_SENTRY} COPY eform-angular-frontend/eform-client ./ +RUN corepack enable RUN yarn install RUN yarn build From f395861d844038a94bd756338ece7a7892f5c700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 08:56:02 +0200 Subject: [PATCH 2/7] refactor: convert time-planning-pn to standalone components for Angular 21 Co-Authored-By: Claude Opus 5.5 --- .../c/time-planning-glsa-3f-pay-rules.spec.ts | 4 +- .../assigned-site-dialog.component.ts | 20 +++-- .../download-excel-dialog.component.ts | 12 ++- .../payroll-export-dialog.component.ts | 11 ++- .../version-history-modal.component.ts | 7 +- .../workday-entity-dialog.component.ts | 22 +++-- .../time-plannings-container.component.ts | 19 +++- .../time-plannings-table.component.ts | 13 ++- .../time-planning-settings.component.ts | 13 ++- .../help-hint/help-hint.component.ts | 4 +- .../help-icon/help-icon.component.ts | 7 +- .../help-panel/help-panel.component.ts | 6 +- .../help-tour/help-tour.component.ts | 3 +- .../time-planning-pn-layout.component.ts | 3 +- .../absence-requests.module.ts | 31 ------- ...bsence-requests-approve-modal.component.ts | 10 ++- ...absence-requests-reject-modal.component.ts | 10 ++- .../absence-requests-container.component.ts | 6 +- .../absence-requests-table.component.ts | 10 ++- .../break-policies/break-policies.module.ts | 33 ------- .../break-policies-container.component.ts | 4 +- .../break-policies-create-modal.component.ts | 12 ++- .../break-policies-delete-modal.component.ts | 5 +- .../break-policies-edit-modal.component.ts | 12 ++- .../break-policies-table.component.html | 2 +- .../break-policies-table.component.ts | 9 +- .../break-policy-rule-dialog.component.ts | 6 +- .../break-policy-rule-form.component.ts | 10 ++- .../break-policy-rules-list.component.ts | 8 +- ...mment-office-all-update-modal.component.ts | 9 +- ...s-comment-office-update-modal.component.ts | 9 +- .../time-flexes-container.component.ts | 7 +- .../time-flexes-table.component.html | 12 +-- .../time-flexes-table.component.ts | 7 +- .../modules/flexes/flex.module.ts | 25 ------ .../day-type-rule-dialog.component.ts | 18 +++- .../day-type-rule-list.component.ts | 9 +- .../pay-day-rule-dialog.component.ts | 6 +- .../pay-day-rule-form.component.ts | 17 +++- .../pay-day-rule-list.component.ts | 9 +- .../pay-rule-sets-container.component.ts | 5 +- .../pay-rule-sets-create-modal.component.ts | 15 +++- .../pay-rule-sets-delete-modal.component.ts | 6 +- .../pay-rule-sets-edit-modal.component.ts | 14 ++- .../pay-rule-sets-table.component.html | 2 +- .../pay-rule-sets-table.component.ts | 11 ++- .../directives/hh-mm-seconds.directive.ts | 1 - .../pay-rule-sets/pay-rule-sets.module.ts | 35 -------- ...stration-devices-create-modal.component.ts | 8 +- ...stration-devices-delete-modal.component.ts | 5 +- ...gistration-devices-edit-modal.component.ts | 8 +- ...registration-devices-otp-code.component.ts | 5 +- ...egistration-devices-container.component.ts | 5 +- .../registration-devices-table.component.html | 2 +- .../registration-devices-table.component.ts | 11 ++- .../registration-devices.module.ts | 33 ------- .../request-history-page.component.ts | 15 +++- .../request-history/request-history.module.ts | 25 ------ .../mobile-working-hours.component.ts | 5 +- ...mment-office-all-update-modal.component.ts | 10 ++- ...s-comment-office-update-modal.component.ts | 10 ++- .../working-hours-upload-modal.component.ts | 9 +- .../working-hours-container.component.ts | 4 +- .../working-hours-header.component.ts | 12 ++- .../working-hours-table.component.html | 2 +- .../working-hours-table.component.ts | 12 ++- .../working-hours/working-hours.module.ts | 43 +-------- .../time-planning-pn.module.ts | 87 +------------------ 68 files changed, 385 insertions(+), 455 deletions(-) diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts index 24e6b03ad..8437a1f81 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts @@ -467,7 +467,7 @@ test.describe('GLS-A / 3F Pay Rule Set Full Pipeline E2E', () => { await expect(row.first()).toBeVisible({ timeout: 10000 }); // Open the row-action menu and assert Edit + Delete are disabled - await row.first().locator('#actionMenu').click(); + await row.first().locator('[id^="actionMenu"]').click(); const editBtn = page.locator('button[id^="editPayRuleSetBtn-"]'); const deleteBtn = page.locator('button[id^="deletePayRuleSetBtn-"]'); await expect(editBtn).toBeVisible({ timeout: 5000 }); @@ -480,7 +480,7 @@ test.describe('GLS-A / 3F Pay Rule Set Full Pipeline E2E', () => { // the modal itself must render the locked summary view (no editable // name field, no Update button). // Force-click the disabled edit button via JS to verify modal behavior. - await row.first().locator('#actionMenu').click(); + await row.first().locator('[id^="actionMenu"]').click(); await page.evaluate(() => { const btn = document.querySelector('button[id^="editPayRuleSetBtn-"]') as HTMLButtonElement | null; if (btn) { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.ts index 0e3448d17..322548955 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.ts @@ -1,10 +1,7 @@ import {Component, DoCheck, OnInit, inject } from '@angular/core'; -import { - MAT_DIALOG_DATA, - MatDialog -} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; import {AssignedSiteModel, CommonTagModel, GlobalAutoBreakSettingsModel, PayRuleSetSimpleModel} from '../../../../models'; import {PayRuleSetsViewModalComponent} from '../../../../modules/pay-rule-sets/components/pay-rule-sets-view-modal/pay-rule-sets-view-modal.component'; import {selectCurrentUserIsAdmin, selectCurrentUserIsFirstUser} from 'src/app/state'; @@ -19,13 +16,26 @@ import { ReactiveFormsModule, FormControl, } from '@angular/forms'; +import {NgIf, NgFor, AsyncPipe} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatFormField, MatLabel, MatError, MatHint, MatSuffix} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from '@angular/material/datepicker'; +import {MatCheckbox} from '@angular/material/checkbox'; +import {MatRadioGroup, MatRadioButton} from '@angular/material/radio'; +import {MatTabGroup, MatTab} from '@angular/material/tabs'; +import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; @Component({ selector: 'app-assigned-site-dialog', + imports: [NgIf, NgFor, AsyncPipe, ReactiveFormsModule, TranslatePipe, MatIcon, MatIconButton, MatFormField, MatLabel, MatError, MatHint, MatSuffix, MatInput, MtxSelect, MatTooltip, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDatepicker, MatDatepickerInput, MatDatepickerToggle, MatCheckbox, MatRadioGroup, MatRadioButton, MatTabGroup, MatTab, NgxMaterialTimepickerModule], templateUrl: './assigned-site-dialog.component.html', styleUrls: ['./assigned-site-dialog.component.scss'], - standalone: false }) export class AssignedSiteDialogComponent implements DoCheck, OnInit { private fb = inject(FormBuilder); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.ts index b53b05d6f..536169a41 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.ts @@ -2,7 +2,7 @@ import {Component, OnDestroy, OnInit, inject } from '@angular/core'; import {EMPTY, Subscription} from 'rxjs'; -import {MatDatepickerInputEvent} from '@angular/material/datepicker'; +import {MatDatepickerInputEvent, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate} from '@angular/material/datepicker'; import {SiteDto} from 'src/app/common/models'; import { CommonTagModel, @@ -18,7 +18,13 @@ import { TimePlanningPnWorkingHoursService } from 'src/app/plugins/modules/time-planning-pn/services'; import {ToastrService} from 'ngx-toastr'; -import {MAT_DIALOG_DATA} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; +import {NgIf} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel, MatPrefix} from '@angular/material/form-field'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatTooltip} from '@angular/material/tooltip'; /** * The filters the planning page has on screen when the dialog is opened. The export @@ -36,9 +42,9 @@ export interface DownloadExcelDialogData { @Component({ selector: 'app-download-excel-dialog', + imports: [NgIf, FormsModule, TranslatePipe, MatFormField, MatLabel, MatPrefix, MtxSelect, MatTooltip, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate], templateUrl: './download-excel-dialog.component.html', styleUrls: ['./download-excel-dialog.component.scss'], - standalone: false, }) export class DownloadExcelDialogComponent implements OnInit, OnDestroy { private data = inject(MAT_DIALOG_DATA); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.ts index 4a358fc30..4531c81fb 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.ts @@ -1,9 +1,16 @@ import { Component, Inject, OnInit, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { TimePlanningPnPayrollExportService } from '../../../../services'; import { ToastrService } from 'ngx-toastr'; import { saveAs } from 'file-saver'; import { format } from 'date-fns'; +import {NgIf} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatLabel, MatSuffix} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from '@angular/material/datepicker'; export interface PayrollExportDialogData { cutoffDay: number; @@ -12,8 +19,8 @@ export interface PayrollExportDialogData { @Component({ selector: 'app-payroll-export-dialog', + imports: [NgIf, FormsModule, TranslatePipe, MatIcon, MatFormField, MatLabel, MatSuffix, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions, MatDatepicker, MatDatepickerInput, MatDatepickerToggle], templateUrl: './payroll-export-dialog.component.html', - standalone: false, }) export class PayrollExportDialogComponent implements OnInit { private payrollExportService = inject(TimePlanningPnPayrollExportService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/version-history-modal/version-history-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/version-history-modal/version-history-modal.component.ts index f1b9aa680..0c9fed2dd 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/version-history-modal/version-history-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/version-history-modal/version-history-modal.component.ts @@ -1,11 +1,12 @@ import {Component, OnInit, inject, OnDestroy} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent} from '@angular/material/dialog'; import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; import {TimePlanningPnPlanningsService} from '../../../../services'; import {PlanRegistrationVersionHistoryModel, FieldChangeModel} from '../../../../models'; import {TemplateFilesService} from 'src/app/common/services'; import {Subscription} from 'rxjs'; -import {TranslateService} from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; +import {NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common'; const GOOGLE_MAPS_EMBED_URL = 'https://www.google.com/maps?q={lat},{lng}&output=embed'; const PICTURE_SNAPSHOT_API_URL = '/api/template-files/get-image/'; @@ -32,9 +33,9 @@ interface TimelineDay { @Component({ selector: 'app-version-history-modal', + imports: [NgIf, NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault, TranslatePipe, MatDialogTitle, MatDialogContent], templateUrl: './version-history-modal.component.html', styleUrls: ['./version-history-modal.component.scss'], - standalone: false }) export class VersionHistoryModalComponent implements OnInit, OnDestroy { versionHistory: PlanRegistrationVersionHistoryModel; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.ts index be3b444c1..6d2342fae 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.ts @@ -2,11 +2,11 @@ import {Component, ElementRef, OnInit, TemplateRef, ViewChild, inject, OnDestroy } from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog} from '@angular/material/dialog'; -import {TranslateService} from '@ngx-translate/core'; -import {DatePipe} from '@angular/common'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; +import {DatePipe, NgIf, NgFor, NgTemplateOutlet} from '@angular/common'; import {TimePlanningMessagesEnum} from '../../../../enums'; import {AssignedSiteModel, PlanningPrDayModel, GpsCoordinateModel, PictureSnapshotModel} from '../../../../models'; -import {MtxGridColumn} from '@ng-matero/extensions/grid'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; import {TimePlanningPnPlanningsService, TimePlanningPnGpsCoordinatesService, TimePlanningPnPictureSnapshotsService} from '../../../../services'; import {VersionHistoryModalComponent} from '../version-history-modal/version-history-modal.component'; import {Store} from '@ngrx/store'; @@ -17,7 +17,7 @@ import {TemplateFilesService} from 'src/app/common/services'; import {OperationResult, SharedTagModel} from 'src/app/common/models'; import {Observable, Subscription} from 'rxjs'; import {ToastrService} from 'ngx-toastr'; -import { MatDialogRef } from '@angular/material/dialog'; +import {MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; import {HelpEntryId} from '../../../../help/help.model'; import {HelpPanelService} from '../../../../help/services/help-panel.service'; import {HelpTourService} from '../../../../help/services/help-tour.service'; @@ -34,12 +34,24 @@ import { ReactiveFormsModule, FormArray, } from '@angular/forms'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatCheckbox} from '@angular/material/checkbox'; +import {CdkScrollable} from '@angular/cdk/scrolling'; +import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; +import {EformTagComponent} from 'src/app/common/modules/eform-shared/components'; +import {HelpHintComponent} from '../../../../help/components/help-hint/help-hint.component'; +import {HelpTourComponent} from '../../../../help/components/help-tour/help-tour.component'; +import {HelpIconComponent} from '../../../../help/components/help-icon/help-icon.component'; @Component({ selector: 'app-workday-entity-dialog', + imports: [NgIf, NgFor, NgTemplateOutlet, ReactiveFormsModule, TranslatePipe, MatIcon, MatIconButton, MatFormField, MatLabel, MatError, MatInput, MtxGrid, MatTooltip, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatCheckbox, CdkScrollable, NgxMaterialTimepickerModule, EformTagComponent, HelpHintComponent, HelpTourComponent, HelpIconComponent], templateUrl: './workday-entity-dialog.component.html', styleUrls: ['./workday-entity-dialog.component.scss'], - standalone: false }) export class WorkdayEntityDialogComponent implements OnInit, OnDestroy { private fb = inject(FormBuilder); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.ts index aa0651c2d..53cc0f44f 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.ts @@ -13,7 +13,7 @@ import { } from '../../../services'; import {startOfWeek, endOfWeek, format, startOfDay, subDays} from 'date-fns'; import {ToastrService} from 'ngx-toastr'; -import {TranslateService} from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { assertLockOutcomeHandled, buildReconcilePreview, ReconcilePreview, sendLockRequest, } from '../day-lock.util'; @@ -24,19 +24,32 @@ import {MatDialog} from '@angular/material/dialog'; import { DownloadExcelDialogComponent, DownloadExcelDialogData, PayrollExportDialogComponent } from 'src/app/plugins/modules/time-planning-pn/components'; -import {MatDatepickerInputEvent} from '@angular/material/datepicker'; +import {MatDatepickerInputEvent, MatDatepicker, MatDatepickerInput, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate} from '@angular/material/datepicker'; import {HelpAudience, HelpEntryId, HelpTourName, HelpUiStrings} from '../../../help/help.model'; import {HelpContentService} from '../../../help/services/help-content.service'; import {HelpPanelService} from '../../../help/services/help-panel.service'; import {HelpTourService} from '../../../help/services/help-tour.service'; import {HelpVisibilityService} from '../../../help/services/help-visibility.service'; +import {NgIf, AsyncPipe} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatLabel, MatPrefix} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatSlideToggle} from '@angular/material/slide-toggle'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; +import {HelpPanelComponent} from '../../../help/components/help-panel/help-panel.component'; +import {HelpTourComponent} from '../../../help/components/help-tour/help-tour.component'; +import {HelpIconComponent} from '../../../help/components/help-icon/help-icon.component'; +import {TimePlanningsTableComponent} from '../time-plannings-table/time-plannings-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-time-plannings-container', + imports: [NgIf, AsyncPipe, FormsModule, TranslatePipe, MatIcon, MatFormField, MatLabel, MatPrefix, MatInput, MtxSelect, MatTooltip, MatDatepicker, MatDatepickerInput, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate, MatSlideToggle, EformNewSubheaderComponent, HelpPanelComponent, HelpTourComponent, HelpIconComponent, TimePlanningsTableComponent], templateUrl: './time-plannings-container.component.html', styleUrls: ['./time-plannings-container.component.scss'], - standalone: false }) export class TimePlanningsContainerComponent implements OnInit, OnDestroy { private store = inject(Store); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.ts index c488441ba..1a0d91013 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.ts @@ -5,12 +5,12 @@ import { } from '@angular/core'; import {Subscription, take} from 'rxjs'; import {AssignedSiteModel, TimePlanningModel} from '../../../models'; -import {MtxGridColumn} from '@ng-matero/extensions/grid'; -import {TranslateService} from '@ngx-translate/core'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import {TimePlanningPnPlanningsService, TimePlanningPnSettingsService} from '../../../services'; import {MatDialog} from '@angular/material/dialog'; import {AssignedSiteDialogComponent, WorkdayEntityDialogComponent} from '../'; -import {DatePipe} from '@angular/common'; +import {DatePipe, NgIf, AsyncPipe} from '@angular/common'; import * as R from 'ramda'; import {TimePlanningMessagesEnum} from '../../../enums'; import {Store} from '@ngrx/store'; @@ -21,13 +21,18 @@ import {HelpPanelService} from '../../../help/services/help-panel.service'; import { dayAt, dayKey, dayLockState, formatReconciledProvenance, isDayLocked, isDaySealed, ReconcilePreview, } from '../day-lock.util'; +import {MatIcon} from '@angular/material/icon'; +import {MatTooltip} from '@angular/material/tooltip'; +import {EformTagComponent} from 'src/app/common/modules/eform-shared/components'; +import {HelpHintComponent} from '../../../help/components/help-hint/help-hint.component'; +import {HelpIconComponent} from '../../../help/components/help-icon/help-icon.component'; @Component({ selector: 'app-time-plannings-table', + imports: [NgIf, AsyncPipe, TranslatePipe, MatIcon, MtxGrid, MatTooltip, EformTagComponent, HelpHintComponent, HelpIconComponent], templateUrl: './time-plannings-table.component.html', styleUrls: ['./time-plannings-table.component.scss'], encapsulation: ViewEncapsulation.None, - standalone: false }) export class TimePlanningsTableComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/settings/time-planning-settings/time-planning-settings.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/settings/time-planning-settings/time-planning-settings.component.ts index 35b9e3a7c..f8d8b4f13 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/settings/time-planning-settings/time-planning-settings.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/settings/time-planning-settings/time-planning-settings.component.ts @@ -6,12 +6,23 @@ import {TimePlanningPnSettingsService} from '../../../services'; import {TimePlanningSettingsModel} from '../../../models'; import {selectCurrentUserIsFirstUser} from 'src/app/state'; import {Store} from '@ngrx/store'; +import {NgIf, NgFor} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatButton} from '@angular/material/button'; +import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatSlideToggle} from '@angular/material/slide-toggle'; +import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; @Component({ selector: 'app-time-planning-settings', + imports: [NgIf, NgFor, FormsModule, TranslatePipe, MatButton, MatCard, MatCardContent, MatCardHeader, MatFormField, MatLabel, MatInput, MtxSelect, MatSlideToggle, NgxMaterialTimepickerModule, EformNewSubheaderComponent], templateUrl: './time-planning-settings.component.html', styleUrls: ['./time-planning-settings.component.scss'], - standalone: false }) export class TimePlanningSettingsComponent implements OnInit, OnDestroy { private timePlanningPnSettingsService = inject(TimePlanningPnSettingsService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.ts index 10afb835b..4e538e885 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.ts @@ -1,11 +1,13 @@ import { Component, Input } from '@angular/core'; import { HelpEntryChromeBase } from '../help-chrome.base'; +import {NgIf} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; @Component({ selector: 'tp-help-hint', + imports: [NgIf, MatIcon], templateUrl: './help-hint.component.html', styleUrls: ['./help-hint.component.scss'], - standalone: false, }) export class HelpHintComponent extends HelpEntryChromeBase { @Input() tone: 'info' | 'warn' = 'info'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.ts index e53e74277..962c3a17b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.ts @@ -1,13 +1,16 @@ import { Component, EventEmitter, Output } from '@angular/core'; -import { ConnectedPosition, Overlay, ScrollStrategy } from '@angular/cdk/overlay'; +import {ConnectedPosition, Overlay, ScrollStrategy, CdkConnectedOverlay, CdkOverlayOrigin} from '@angular/cdk/overlay'; import { HelpEntryId } from '../../help.model'; import { HelpEntryChromeBase } from '../help-chrome.base'; +import {NgIf} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; @Component({ selector: 'tp-help-icon', + imports: [NgIf, MatIcon, MatIconButton, CdkConnectedOverlay, CdkOverlayOrigin], templateUrl: './help-icon.component.html', styleUrls: ['./help-icon.component.scss'], - standalone: false, }) export class HelpIconComponent extends HelpEntryChromeBase { @Output() openInPanel = new EventEmitter(); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.ts index f5068bf89..d80442094 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.ts @@ -10,6 +10,10 @@ import { import { HelpPanelService } from '../../services/help-panel.service'; import { HelpSearchResult, HelpSearchService } from '../../services/help-search.service'; import { HelpChromeBase } from '../help-chrome.base'; +import {NgIf, NgFor, NgTemplateOutlet} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; export interface PanelSection { section: HelpSection; @@ -21,9 +25,9 @@ const SECTION_ORDER: HelpSection[] = ['task', 'toolbar', 'grid', 'dayCell', 'fle @Component({ selector: 'tp-help-panel', + imports: [NgIf, NgFor, NgTemplateOutlet, FormsModule, MatIcon, MatIconButton], templateUrl: './help-panel.component.html', styleUrls: ['./help-panel.component.scss'], - standalone: false, }) export class HelpPanelComponent extends HelpChromeBase implements OnInit, OnChanges, AfterViewChecked, OnDestroy { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.ts index 95d6964db..f2c039876 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.ts @@ -13,15 +13,16 @@ import { Subscription } from 'rxjs'; import { HelpProse, HelpTourName } from '../../help.model'; import { HelpTourService, HelpTourState } from '../../services/help-tour.service'; import { HelpChromeBase } from '../help-chrome.base'; +import {NgIf} from '@angular/common'; /** Distinguishes the aria-labelledby target of one mounted tour from another's. */ let nextTourCardId = 0; @Component({ selector: 'tp-help-tour', + imports: [NgIf, CdkConnectedOverlay], templateUrl: './help-tour.component.html', styleUrls: ['./help-tour.component.scss'], - standalone: false, }) export class HelpTourComponent extends HelpChromeBase implements OnInit, DoCheck, AfterViewChecked, OnDestroy { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/layouts/time-planning-pn-layout.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/layouts/time-planning-pn-layout.component.ts index 45492509a..3f656762e 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/layouts/time-planning-pn-layout.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/layouts/time-planning-pn-layout.component.ts @@ -6,12 +6,13 @@ import {translates} from './../i18n/translates'; import {Store} from '@ngrx/store'; import {addPluginToVisited, selectPluginsVisitedPlugins} from 'src/app/state'; import {take} from 'rxjs'; +import {RouterOutlet} from '@angular/router'; @Component({ selector: 'app-time-planning-pn-layout', + imports: [RouterOutlet], template: ` `, - standalone: false }) export class TimePlanningPnLayoutComponent implements AfterContentInit, OnInit { private translateService = inject(TranslateService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts index a43436a3e..9be5a2825 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts @@ -1,16 +1,5 @@ import { NgModule } from '@angular/core'; -import { CommonModule, NgIf, AsyncPipe, DatePipe } from '@angular/common'; -import { EformSharedModule } from 'src/app/common/modules/eform-shared/eform-shared.module'; -import { MatButton, MatIconButton } from '@angular/material/button'; -import { TranslateModule } from '@ngx-translate/core'; import { AbsenceRequestsRouting } from './absence-requests.routing'; -import { MtxGrid } from '@ng-matero/extensions/grid'; -import { MatDialogActions, MatDialogContent, MatDialogTitle } from '@angular/material/dialog'; -import { MatIcon } from '@angular/material/icon'; -import { MatTooltip } from '@angular/material/tooltip'; -import { FormsModule } from '@angular/forms'; -import { MatFormField, MatLabel } from '@angular/material/form-field'; -import { MatInput } from '@angular/material/input'; import { AbsenceRequestsContainerComponent, AbsenceRequestsTableComponent, @@ -20,27 +9,7 @@ import { @NgModule({ imports: [ - CommonModule, - AsyncPipe, - DatePipe, - NgIf, - EformSharedModule, - MatButton, - TranslateModule, AbsenceRequestsRouting, - MtxGrid, - MatDialogTitle, - MatDialogContent, - MatDialogActions, - MatIcon, - MatIconButton, - MatTooltip, - FormsModule, - MatFormField, - MatInput, - MatLabel - ], - declarations: [ AbsenceRequestsContainerComponent, AbsenceRequestsTableComponent, AbsenceRequestsApproveModalComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-approve-modal/absence-requests-approve-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-approve-modal/absence-requests-approve-modal.component.ts index 7cf97a3cd..a171149cc 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-approve-modal/absence-requests-approve-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-approve-modal/absence-requests-approve-modal.component.ts @@ -1,17 +1,21 @@ import { Component, OnInit, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { TimePlanningPnAbsenceRequestsService } from 'src/app/plugins/modules/time-planning-pn/services'; import { AbsenceRequestModel, AbsenceRequestDecisionModel } from 'src/app/plugins/modules/time-planning-pn/models'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { selectAuthUser } from 'src/app/state'; import { take } from 'rxjs'; +import {NgIf, DatePipe} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-absence-requests-approve-modal', + imports: [NgIf, DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './absence-requests-approve-modal.component.html', - standalone: false }) export class AbsenceRequestsApproveModalComponent implements OnInit { private absenceRequestsService = inject(TimePlanningPnAbsenceRequestsService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-reject-modal/absence-requests-reject-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-reject-modal/absence-requests-reject-modal.component.ts index 36fbb4d6c..7f32bf862 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-reject-modal/absence-requests-reject-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-actions/absence-requests-reject-modal/absence-requests-reject-modal.component.ts @@ -1,17 +1,21 @@ import { Component, OnInit, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { TimePlanningPnAbsenceRequestsService } from 'src/app/plugins/modules/time-planning-pn/services'; import { AbsenceRequestModel, AbsenceRequestDecisionModel } from 'src/app/plugins/modules/time-planning-pn/models'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { selectAuthUser } from 'src/app/state'; import { take } from 'rxjs'; +import {NgIf, DatePipe} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-absence-requests-reject-modal', + imports: [NgIf, DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './absence-requests-reject-modal.component.html', - standalone: false }) export class AbsenceRequestsRejectModalComponent implements OnInit { private absenceRequestsService = inject(TimePlanningPnAbsenceRequestsService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-container/absence-requests-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-container/absence-requests-container.component.ts index 6246b51f2..b20c725c9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-container/absence-requests-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-container/absence-requests-container.component.ts @@ -6,16 +6,18 @@ import { MatDialog } from '@angular/material/dialog'; import { Overlay } from '@angular/cdk/overlay'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { selectAuthUser } from 'src/app/state'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; +import {AbsenceRequestsTableComponent} from '../absence-requests-table/absence-requests-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-absence-requests-container', + imports: [TranslatePipe, EformNewSubheaderComponent, AbsenceRequestsTableComponent], templateUrl: './absence-requests-container.component.html', styleUrls: ['./absence-requests-container.component.scss'], - standalone: false }) export class AbsenceRequestsContainerComponent implements OnInit, OnDestroy { public dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-table/absence-requests-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-table/absence-requests-table.component.ts index cb4e931b3..c87317571 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-table/absence-requests-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/components/absence-requests-table/absence-requests-table.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, OnDestroy, Output, inject } from '@angular/core'; -import { MtxGridColumn } from '@ng-matero/extensions/grid'; -import { TranslateService } from '@ngx-translate/core'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { AbsenceRequestModel } from '../../../../models'; import { Subscription } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; @@ -11,12 +11,16 @@ import { AbsenceRequestsApproveModalComponent, AbsenceRequestsRejectModalComponent } from '../absence-requests-actions'; +import {NgIf, NgClass} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; @AutoUnsubscribe() @Component({ selector: 'app-absence-requests-table', + imports: [NgIf, NgClass, TranslatePipe, MatIcon, MatIconButton, MtxGrid, MatTooltip], templateUrl: './absence-requests-table.component.html', - standalone: false }) export class AbsenceRequestsTableComponent implements OnInit, OnDestroy { private dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/break-policies.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/break-policies.module.ts index a72ee710e..b298b54e9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/break-policies.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/break-policies.module.ts @@ -1,9 +1,4 @@ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {TranslateModule} from '@ngx-translate/core'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; import {BreakPoliciesRouting} from './break-policies.routing'; import { BreakPoliciesContainerComponent, @@ -16,38 +11,12 @@ import { BreakPolicyRuleDialogComponent, } from './components'; import {TimePlanningPnBreakPoliciesService} from '../../services'; -import {MtxGridModule} from '@ng-matero/extensions/grid'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MatButtonModule} from '@angular/material/button'; -import {MatIconModule} from '@angular/material/icon'; import {MatDialogModule} from '@angular/material/dialog'; -import {MatTooltipModule} from '@angular/material/tooltip'; -import {MatSelectModule} from '@angular/material/select'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatTableModule} from '@angular/material/table'; @NgModule({ imports: [ - CommonModule, - TranslateModule, - FormsModule, - EformSharedModule, - RouterModule, - ReactiveFormsModule, BreakPoliciesRouting, - MtxGridModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatIconModule, MatDialogModule, - MatTooltipModule, - MatSelectModule, - MatMenuModule, - MatTableModule, - ], - declarations: [ BreakPoliciesContainerComponent, BreakPoliciesTableComponent, BreakPoliciesCreateModalComponent, @@ -62,5 +31,3 @@ import {MatTableModule} from '@angular/material/table'; ], }) export class BreakPoliciesModule {} - - diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-container/break-policies-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-container/break-policies-container.component.ts index c8f798db5..f888170cd 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-container/break-policies-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-container/break-policies-container.component.ts @@ -10,13 +10,15 @@ import { TimePlanningPnBreakPoliciesService } from '../../../../services'; import { BreakPoliciesCreateModalComponent } from '../break-policies-create-modal/break-policies-create-modal.component'; import { BreakPoliciesEditModalComponent } from '../break-policies-edit-modal/break-policies-edit-modal.component'; import { BreakPoliciesDeleteModalComponent } from '../break-policies-delete-modal/break-policies-delete-modal.component'; +import {TranslatePipe} from '@ngx-translate/core'; +import {BreakPoliciesTableComponent} from '../break-policies-table/break-policies-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-break-policies-container', + imports: [TranslatePipe, BreakPoliciesTableComponent], templateUrl: './break-policies-container.component.html', styleUrls: ['./break-policies-container.component.scss'], - standalone: false }) export class BreakPoliciesContainerComponent implements OnInit, OnDestroy { private breakPoliciesService = inject(TimePlanningPnBreakPoliciesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-create-modal/break-policies-create-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-create-modal/break-policies-create-modal.component.ts index c88c14edc..289ecdc86 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-create-modal/break-policies-create-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-create-modal/break-policies-create-modal.component.ts @@ -1,17 +1,21 @@ import { Component, OnInit, inject } from '@angular/core'; -import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import {FormBuilder, FormGroup, FormArray, Validators, ReactiveFormsModule} from '@angular/forms'; +import {MatDialog, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; import { BreakPolicyCreateModel } from '../../../../models'; import { TimePlanningPnBreakPoliciesService } from '../../../../services'; import { BreakPolicyRuleDialogComponent, BreakPolicyRuleDialogData } from '../break-policy-rule-dialog/break-policy-rule-dialog.component'; -import { BreakPolicyRuleFormValue } from '../break-policy-rules-list/break-policy-rules-list.component'; +import {BreakPolicyRuleFormValue, BreakPolicyRulesListComponent} from '../break-policy-rules-list/break-policy-rules-list.component'; +import {NgIf} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-break-policies-create-modal', + imports: [NgIf, ReactiveFormsModule, TranslatePipe, MatFormField, MatLabel, MatError, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions, BreakPolicyRulesListComponent], templateUrl: './break-policies-create-modal.component.html', styleUrls: ['./break-policies-create-modal.component.scss'], - standalone: false }) export class BreakPoliciesCreateModalComponent implements OnInit { private breakPoliciesService = inject(TimePlanningPnBreakPoliciesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-delete-modal/break-policies-delete-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-delete-modal/break-policies-delete-modal.component.ts index 67f447675..140ab1d8b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-delete-modal/break-policies-delete-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-delete-modal/break-policies-delete-modal.component.ts @@ -1,14 +1,15 @@ import { Component, OnInit, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; import { BreakPolicyModel } from '../../../../models'; import { TimePlanningPnBreakPoliciesService } from '../../../../services'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-break-policies-delete-modal', + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './break-policies-delete-modal.component.html', styleUrls: ['./break-policies-delete-modal.component.scss'], - standalone: false }) export class BreakPoliciesDeleteModalComponent implements OnInit { private breakPoliciesService = inject(TimePlanningPnBreakPoliciesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-edit-modal/break-policies-edit-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-edit-modal/break-policies-edit-modal.component.ts index 325cedec4..4113fa2bb 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-edit-modal/break-policies-edit-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-edit-modal/break-policies-edit-modal.component.ts @@ -1,17 +1,21 @@ import { Component, OnInit, inject } from '@angular/core'; -import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import {FormBuilder, FormGroup, FormArray, Validators, ReactiveFormsModule} from '@angular/forms'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; import { BreakPolicyModel, BreakPolicyUpdateModel } from '../../../../models'; import { TimePlanningPnBreakPoliciesService } from '../../../../services'; import { BreakPolicyRuleDialogComponent, BreakPolicyRuleDialogData } from '../break-policy-rule-dialog/break-policy-rule-dialog.component'; -import { BreakPolicyRuleFormValue } from '../break-policy-rules-list/break-policy-rules-list.component'; +import {BreakPolicyRuleFormValue, BreakPolicyRulesListComponent} from '../break-policy-rules-list/break-policy-rules-list.component'; +import {NgIf} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-break-policies-edit-modal', + imports: [NgIf, ReactiveFormsModule, TranslatePipe, MatFormField, MatLabel, MatError, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions, BreakPolicyRulesListComponent], templateUrl: './break-policies-edit-modal.component.html', styleUrls: ['./break-policies-edit-modal.component.scss'], - standalone: false }) export class BreakPoliciesEditModalComponent implements OnInit { private breakPoliciesService = inject(TimePlanningPnBreakPoliciesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.html b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.html index 799ac1b3b..95b8f726b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.html +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.html @@ -20,7 +20,7 @@
- diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.ts index 0bcb82946..6355880b1 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policies-table/break-policies-table.component.ts @@ -1,13 +1,18 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { MtxGridColumn } from '@ng-matero/extensions/grid'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; import { BreakPolicySimpleModel } from '../../../../models'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton, MatButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; @Component({ selector: 'app-break-policies-table', + imports: [TranslatePipe, MatIcon, MatIconButton, MatButton, MtxGrid, MatTooltip, MatMenu, MatMenuItem, MatMenuTrigger], templateUrl: './break-policies-table.component.html', styleUrls: ['./break-policies-table.component.scss'], - standalone: false }) export class BreakPoliciesTableComponent { private dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-dialog/break-policy-rule-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-dialog/break-policy-rule-dialog.component.ts index 2d1371fda..43a8b3817 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-dialog/break-policy-rule-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-dialog/break-policy-rule-dialog.component.ts @@ -1,7 +1,9 @@ import {Component, Inject, OnInit} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {FormGroup, FormControl, Validators} from '@angular/forms'; import {BreakPolicyRuleFormValue} from '../break-policy-rules-list/break-policy-rules-list.component'; +import {TranslatePipe} from '@ngx-translate/core'; +import {BreakPolicyRuleFormComponent} from '../break-policy-rule-form/break-policy-rule-form.component'; export interface BreakPolicyRuleDialogData { mode: 'create' | 'edit'; @@ -10,7 +12,7 @@ export interface BreakPolicyRuleDialogData { @Component({ selector: 'app-break-policy-rule-dialog', - standalone: false, + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions, BreakPolicyRuleFormComponent], templateUrl: './break-policy-rule-dialog.component.html', styleUrls: ['./break-policy-rule-dialog.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-form/break-policy-rule-form.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-form/break-policy-rule-form.component.ts index 0af47b333..6165d3535 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-form/break-policy-rule-form.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rule-form/break-policy-rule-form.component.ts @@ -1,9 +1,15 @@ import {Component, Input, OnInit} from '@angular/core'; -import {FormGroup, FormControl, Validators} from '@angular/forms'; +import {FormGroup, FormControl, Validators, ReactiveFormsModule} from '@angular/forms'; +import {NgIf, NgFor} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel, MatError, MatHint} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MatSelect} from '@angular/material/select'; +import {MatOption} from '@angular/material/core'; @Component({ selector: 'app-break-policy-rule-form', - standalone: false, + imports: [NgIf, NgFor, ReactiveFormsModule, TranslatePipe, MatFormField, MatLabel, MatError, MatHint, MatInput, MatSelect, MatOption], templateUrl: './break-policy-rule-form.component.html', styleUrls: ['./break-policy-rule-form.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rules-list/break-policy-rules-list.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rules-list/break-policy-rules-list.component.ts index aa3de9002..452be4eb6 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rules-list/break-policy-rules-list.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/break-policies/components/break-policy-rules-list/break-policy-rules-list.component.ts @@ -1,5 +1,11 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; import {FormArray, FormGroup} from '@angular/forms'; +import {NgIf} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton, MatButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatTableModule} from '@angular/material/table'; export interface BreakPolicyRuleFormValue { id?: number | null; @@ -10,7 +16,7 @@ export interface BreakPolicyRuleFormValue { @Component({ selector: 'app-break-policy-rules-list', - standalone: false, + imports: [NgIf, TranslatePipe, MatIcon, MatIconButton, MatButton, MatTooltip, MatTableModule], templateUrl: './break-policy-rules-list.component.html', styleUrls: ['./break-policy-rules-list.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-all-update-modal/time-flexes-comment-office-all-update-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-all-update-modal/time-flexes-comment-office-all-update-modal.component.ts index 118b63d68..1462429ff 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-all-update-modal/time-flexes-comment-office-all-update-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-all-update-modal/time-flexes-comment-office-all-update-modal.component.ts @@ -6,15 +6,20 @@ import { } from '@angular/core'; import {AutoUnsubscribe} from 'ngx-auto-unsubscribe'; import {TimeFlexesModel} from '../../../../../models'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; +import {DatePipe} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @AutoUnsubscribe() @Component({ selector: 'app-time-flexes-comment-office-all-update-modal', + imports: [DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './time-flexes-comment-office-all-update-modal.component.html', styleUrls: ['./time-flexes-comment-office-all-update-modal.component.scss', ], - standalone: false }) export class TimeFlexesCommentOfficeAllUpdateModalComponent implements OnInit, OnDestroy { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-update-modal/time-flexes-comment-office-update-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-update-modal/time-flexes-comment-office-update-modal.component.ts index e13cb77fa..5b400040a 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-update-modal/time-flexes-comment-office-update-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-actions/comment-office-update-modal/time-flexes-comment-office-update-modal.component.ts @@ -6,14 +6,19 @@ import { } from '@angular/core'; import {AutoUnsubscribe} from 'ngx-auto-unsubscribe'; import {TimeFlexesModel} from '../../../../../models'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; +import {DatePipe} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @AutoUnsubscribe() @Component({ selector: 'app-time-flexes-comment-office-update-modal', + imports: [DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './time-flexes-comment-office-update-modal.component.html', styleUrls: ['./time-flexes-comment-office-update-modal.component.scss'], - standalone: false }) export class TimeFlexesCommentOfficeUpdateModalComponent implements OnInit, OnDestroy { public dialogRef = inject(MatDialogRef); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-container/time-flexes-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-container/time-flexes-container.component.ts index 9c3724317..2298690a9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-container/time-flexes-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-container/time-flexes-container.component.ts @@ -10,13 +10,18 @@ import { TimeFlexesUpdateModel, } from '../../../../models'; import { TimePlanningPnFlexesService } from '../../../../services'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatTooltip} from '@angular/material/tooltip'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; +import {TimeFlexesTableComponent} from '../time-flexes-table/time-flexes-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-time-flexes-container', + imports: [TranslatePipe, MatIcon, MatTooltip, EformNewSubheaderComponent, TimeFlexesTableComponent], templateUrl: './time-flexes-container.component.html', styleUrls: ['./time-flexes-container.component.scss'], - standalone: false }) export class TimeFlexesContainerComponent implements OnInit, OnDestroy { private planningsService = inject(TimePlanningPnFlexesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.html b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.html index 9df9510c4..d2c7bea62 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.html +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.html @@ -9,24 +9,24 @@ [showColumnMenuButton]="false"> - - + + - - + + diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.ts index 16dc198ec..7f097a633 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/components/time-flexes-table/time-flexes-table.component.ts @@ -13,19 +13,22 @@ import { TimeFlexesCommentOfficeUpdateModalComponent, } from '../'; import {TranslateService} from '@ngx-translate/core'; -import {MtxGridColumn} from '@ng-matero/extensions/grid'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; import {MatDialog} from '@angular/material/dialog'; import {Overlay} from '@angular/cdk/overlay'; import {dialogConfigHelper} from 'src/app/common/helpers'; import {Subscription} from 'rxjs'; import {AutoUnsubscribe} from 'ngx-auto-unsubscribe'; +import {FormsModule} from '@angular/forms'; +import {MatFormField} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @AutoUnsubscribe() @Component({ selector: 'app-time-flexes-table', + imports: [FormsModule, MatFormField, MatInput, MtxGrid], templateUrl: './time-flexes-table.component.html', styleUrls: ['./time-flexes-table.component.scss'], - standalone: false }) export class TimeFlexesTableComponent implements OnInit, OnDestroy { private translateService = inject(TranslateService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/flex.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/flex.module.ts index 4ae2f8783..3606e96f9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/flex.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/flexes/flex.module.ts @@ -1,9 +1,4 @@ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {TranslateModule} from '@ngx-translate/core'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; import {FlexRouting} from './flex.routing'; import { TimeFlexesCommentOfficeAllUpdateModalComponent, @@ -11,32 +6,12 @@ import { TimeFlexesContainerComponent, TimeFlexesTableComponent, } from './components'; -import {MtxGridModule} from '@ng-matero/extensions/grid'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MatButtonModule} from '@angular/material/button'; -import {MatIconModule} from '@angular/material/icon'; import {MatDialogModule} from '@angular/material/dialog'; -import {MatTooltipModule} from '@angular/material/tooltip'; @NgModule({ imports: [ - CommonModule, - TranslateModule, - FormsModule, - EformSharedModule, - RouterModule, - ReactiveFormsModule, FlexRouting, - MtxGridModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatIconModule, MatDialogModule, - MatTooltipModule, - ], - declarations: [ TimeFlexesCommentOfficeAllUpdateModalComponent, TimeFlexesCommentOfficeUpdateModalComponent, TimeFlexesContainerComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-dialog/day-type-rule-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-dialog/day-type-rule-dialog.component.ts index 904b1e85a..6d89142fc 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-dialog/day-type-rule-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-dialog/day-type-rule-dialog.component.ts @@ -1,7 +1,17 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { MatTableDataSource } from '@angular/material/table'; +import {AbstractControl, FormArray, FormBuilder, FormControl, FormGroup, Validators, ReactiveFormsModule} from '@angular/forms'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; +import {MatTableDataSource, MatTableModule} from '@angular/material/table'; +import {NgIf, NgFor} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatSelect} from '@angular/material/select'; +import {MatOption} from '@angular/material/core'; +import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; export interface DayTypeRuleDialogData { mode: 'create' | 'edit'; @@ -10,7 +20,7 @@ export interface DayTypeRuleDialogData { @Component({ selector: 'app-day-type-rule-dialog', - standalone: false, + imports: [NgIf, NgFor, ReactiveFormsModule, TranslatePipe, MatIcon, MatIconButton, MatFormField, MatLabel, MatError, MatInput, MatTooltip, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, MatSelect, MatOption, MatTableModule, NgxMaterialTimepickerModule], templateUrl: './day-type-rule-dialog.component.html', styleUrls: ['./day-type-rule-dialog.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-list/day-type-rule-list.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-list/day-type-rule-list.component.ts index b91ae9ff1..5b1740afb 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-list/day-type-rule-list.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/day-type-rule-list/day-type-rule-list.component.ts @@ -1,10 +1,15 @@ import { Component, DoCheck, EventEmitter, Input, Output } from '@angular/core'; import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; -import { MatTableDataSource } from '@angular/material/table'; +import {MatTableDataSource, MatTableModule} from '@angular/material/table'; +import {NgIf} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; @Component({ selector: 'app-day-type-rule-list', - standalone: false, + imports: [NgIf, TranslatePipe, MatIcon, MatIconButton, MatTooltip, MatTableModule], templateUrl: './day-type-rule-list.component.html', styleUrls: ['./day-type-rule-list.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-dialog/pay-day-rule-dialog.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-dialog/pay-day-rule-dialog.component.ts index 513e218a2..2f7ac734c 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-dialog/pay-day-rule-dialog.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-dialog/pay-day-rule-dialog.component.ts @@ -1,6 +1,8 @@ import { Component, Inject, OnInit } from '@angular/core'; import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; +import {TranslatePipe} from '@ngx-translate/core'; +import {PayDayRuleFormComponent} from '../pay-day-rule-form/pay-day-rule-form.component'; export interface PayDayRuleDialogData { mode: 'create' | 'edit'; @@ -9,7 +11,7 @@ export interface PayDayRuleDialogData { @Component({ selector: 'app-pay-day-rule-dialog', - standalone: false, + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, PayDayRuleFormComponent], templateUrl: './pay-day-rule-dialog.component.html', styleUrls: ['./pay-day-rule-dialog.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-form/pay-day-rule-form.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-form/pay-day-rule-form.component.ts index 1686b7c6b..7b0658e9d 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-form/pay-day-rule-form.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-form/pay-day-rule-form.component.ts @@ -1,12 +1,21 @@ import {Component, Input, OnInit} from '@angular/core'; -import {FormGroup, FormControl, Validators, FormArray, FormBuilder, AbstractControl, ValidationErrors} from '@angular/forms'; -import {MatTableDataSource} from '@angular/material/table'; -import {TranslateService} from '@ngx-translate/core'; +import {FormGroup, FormControl, Validators, FormArray, FormBuilder, AbstractControl, ValidationErrors, ReactiveFormsModule} from '@angular/forms'; +import {MatTableDataSource, MatTableModule} from '@angular/material/table'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import {secondsToHM} from '../../pay-rule-format.util'; +import {NgIf, NgFor} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatFormField, MatLabel, MatError, MatHint} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatSelect} from '@angular/material/select'; +import {MatOption} from '@angular/material/core'; +import {HhMmSecondsDirective} from '../../directives/hh-mm-seconds.directive'; @Component({ selector: 'app-pay-day-rule-form', - standalone: false, + imports: [NgIf, NgFor, ReactiveFormsModule, TranslatePipe, MatIcon, MatIconButton, MatFormField, MatLabel, MatError, MatHint, MatInput, MatTooltip, MatSelect, MatOption, MatTableModule, HhMmSecondsDirective], templateUrl: './pay-day-rule-form.component.html', styleUrls: ['./pay-day-rule-form.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-list/pay-day-rule-list.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-list/pay-day-rule-list.component.ts index 739c7c9d2..4d34eeb5d 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-list/pay-day-rule-list.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-day-rule-list/pay-day-rule-list.component.ts @@ -1,11 +1,16 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormArray, FormGroup } from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { secondsToHM } from '../../pay-rule-format.util'; +import {NgIf} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatTableModule} from '@angular/material/table'; @Component({ selector: 'app-pay-day-rule-list', - standalone: false, + imports: [NgIf, TranslatePipe, MatIcon, MatIconButton, MatTooltip, MatTableModule], templateUrl: './pay-day-rule-list.component.html', styleUrls: ['./pay-day-rule-list.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-container/pay-rule-sets-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-container/pay-rule-sets-container.component.ts index a8de5e493..b94728ef0 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-container/pay-rule-sets-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-container/pay-rule-sets-container.component.ts @@ -10,14 +10,15 @@ import {PayRuleSetsViewModalComponent} from '../pay-rule-sets-view-modal/pay-rul import {TimePlanningPnPayRuleSetsService} from '../../../../services'; import {Subscription} from 'rxjs'; import {ToastrService} from 'ngx-toastr'; -import {TranslateService} from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; +import {PayRuleSetsTableComponent} from '../pay-rule-sets-table/pay-rule-sets-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-pay-rule-sets-container', + imports: [TranslatePipe, PayRuleSetsTableComponent], templateUrl: './pay-rule-sets-container.component.html', styleUrls: ['./pay-rule-sets-container.component.scss'], - standalone: false, }) export class PayRuleSetsContainerComponent implements OnInit, OnDestroy { payRuleSets: PayRuleSetSimpleModel[] = []; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.ts index ce665269d..105621e95 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-create-modal/pay-rule-sets-create-modal.component.ts @@ -1,18 +1,25 @@ import { Component, Inject, OnInit, Optional } from '@angular/core'; -import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; +import {FormBuilder, FormGroup, Validators, FormArray, FormsModule, ReactiveFormsModule} from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatDialog } from '@angular/material/dialog'; +import {MatDialog, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { TimePlanningPnPayRuleSetsService } from '../../../../services'; import { PayRuleSetCreateModel, PAY_RULE_SET_PRESETS, PayRuleSetPreset } from '../../../../models'; import { PayDayRuleDialogComponent, PayDayRuleDialogData } from '../pay-day-rule-dialog/pay-day-rule-dialog.component'; import { DayTypeRuleDialogComponent, DayTypeRuleDialogData } from '../day-type-rule-dialog/day-type-rule-dialog.component'; import { formatTierChain, formatTimeBands } from '../../pay-rule-format.util'; +import {NgIf, NgFor} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {PayDayRuleListComponent} from '../pay-day-rule-list/pay-day-rule-list.component'; +import {DayTypeRuleListComponent} from '../day-type-rule-list/day-type-rule-list.component'; @Component({ selector: 'app-pay-rule-sets-create-modal', - standalone: false, + imports: [NgIf, NgFor, FormsModule, ReactiveFormsModule, TranslatePipe, MatIcon, MatFormField, MatLabel, MatError, MatInput, MtxSelect, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, PayDayRuleListComponent, DayTypeRuleListComponent], templateUrl: './pay-rule-sets-create-modal.component.html', styleUrls: ['./pay-rule-sets-create-modal.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-delete-modal/pay-rule-sets-delete-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-delete-modal/pay-rule-sets-delete-modal.component.ts index 401dd55a1..c4045220d 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-delete-modal/pay-rule-sets-delete-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-delete-modal/pay-rule-sets-delete-modal.component.ts @@ -1,15 +1,15 @@ import { Component, OnInit, inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { PayRuleSetModel } from '../../../../models'; import { TimePlanningPnPayRuleSetsService } from '../../../../services'; @Component({ selector: 'app-pay-rule-sets-delete-modal', + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './pay-rule-sets-delete-modal.component.html', styleUrls: ['./pay-rule-sets-delete-modal.component.scss'], - standalone: false }) export class PayRuleSetsDeleteModalComponent implements OnInit { private payRuleSetsService = inject(TimePlanningPnPayRuleSetsService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-edit-modal/pay-rule-sets-edit-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-edit-modal/pay-rule-sets-edit-modal.component.ts index c0acafed1..0627eb4e2 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-edit-modal/pay-rule-sets-edit-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-edit-modal/pay-rule-sets-edit-modal.component.ts @@ -1,15 +1,21 @@ import { Component, OnInit, Inject } from '@angular/core'; -import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; +import {FormBuilder, FormGroup, Validators, FormArray, ReactiveFormsModule} from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatDialog } from '@angular/material/dialog'; +import {MatDialog, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose} from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; -import { TranslateService } from '@ngx-translate/core'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { TimePlanningPnPayRuleSetsService } from '../../../../services'; import { PayRuleSetUpdateModel, PayRuleSetModel, PAY_RULE_SET_PRESETS, PayRuleSetPreset } from '../../../../models'; import { PayDayRuleDialogComponent, PayDayRuleDialogData } from '../pay-day-rule-dialog/pay-day-rule-dialog.component'; import { DayTypeRuleDialogComponent, DayTypeRuleDialogData } from '../day-type-rule-dialog/day-type-rule-dialog.component'; import { formatTierChain, formatTimeBands } from '../../pay-rule-format.util'; import { isLockedPresetName, normalizePayRuleSetName } from '../../pay-rule-lock.util'; +import {NgIf, NgFor} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField, MatLabel, MatError} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {PayDayRuleListComponent} from '../pay-day-rule-list/pay-day-rule-list.component'; +import {DayTypeRuleListComponent} from '../day-type-rule-list/day-type-rule-list.component'; export interface PayRuleSetsEditModalData { payRuleSetId: number; @@ -17,7 +23,7 @@ export interface PayRuleSetsEditModalData { @Component({ selector: 'app-pay-rule-sets-edit-modal', - standalone: false, + imports: [NgIf, NgFor, ReactiveFormsModule, TranslatePipe, MatIcon, MatFormField, MatLabel, MatError, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose, PayDayRuleListComponent, DayTypeRuleListComponent], templateUrl: './pay-rule-sets-edit-modal.component.html', styleUrls: ['./pay-rule-sets-edit-modal.component.scss'] }) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.html b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.html index 85fb3bb8a..294cb6b4a 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.html +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.html @@ -23,7 +23,7 @@
- diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.ts index 5935796d8..18fe72117 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/components/pay-rule-sets-table/pay-rule-sets-table.component.ts @@ -1,17 +1,22 @@ import { Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { MtxGridColumn } from '@ng-matero/extensions/grid'; -import { TranslateService } from '@ngx-translate/core'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { selectCurrentUserIsAdmin } from 'src/app/state'; import { PayRuleSetSimpleModel } from '../../../../models'; import { isLockedPresetName } from '../../pay-rule-lock.util'; +import {NgIf, AsyncPipe} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; @Component({ selector: 'app-pay-rule-sets-table', + imports: [NgIf, AsyncPipe, TranslatePipe, MatIcon, MatIconButton, MtxGrid, MatTooltip, MatMenu, MatMenuItem, MatMenuTrigger], templateUrl: './pay-rule-sets-table.component.html', styleUrls: ['./pay-rule-sets-table.component.scss'], - standalone: false }) export class PayRuleSetsTableComponent implements OnInit { private dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/directives/hh-mm-seconds.directive.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/directives/hh-mm-seconds.directive.ts index db01f51a9..b1f18ccf0 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/directives/hh-mm-seconds.directive.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/directives/hh-mm-seconds.directive.ts @@ -23,7 +23,6 @@ import {parseHhMmToSeconds, secondsToHhMmInput} from '../pay-rule-format.util'; */ @Directive({ selector: 'input[appHhMmSeconds]', - standalone: false, providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/pay-rule-sets.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/pay-rule-sets.module.ts index 3acbd6809..d981767f8 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/pay-rule-sets.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/pay-rule-sets/pay-rule-sets.module.ts @@ -1,9 +1,4 @@ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {TranslateModule} from '@ngx-translate/core'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; import {PayRuleSetsRouting} from './pay-rule-sets.routing'; import { PayRuleSetsContainerComponent, @@ -19,42 +14,12 @@ import { } from './components'; import {HhMmSecondsDirective} from './directives/hh-mm-seconds.directive'; import {TimePlanningPnPayRuleSetsService} from '../../services'; -import {MtxGridModule} from '@ng-matero/extensions/grid'; -import {MtxSelectModule} from '@ng-matero/extensions/select'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MatButtonModule} from '@angular/material/button'; -import {MatIconModule} from '@angular/material/icon'; import {MatDialogModule} from '@angular/material/dialog'; -import {MatTooltipModule} from '@angular/material/tooltip'; -import {MatSelectModule} from '@angular/material/select'; -import {MatMenuModule} from '@angular/material/menu'; -import {MatTableModule} from '@angular/material/table'; -import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; @NgModule({ imports: [ - CommonModule, - TranslateModule, - FormsModule, - EformSharedModule, - RouterModule, - ReactiveFormsModule, PayRuleSetsRouting, - MtxGridModule, - MtxSelectModule, - MatFormFieldModule, - MatInputModule, - MatButtonModule, - MatIconModule, MatDialogModule, - MatTooltipModule, - MatSelectModule, - MatMenuModule, - MatTableModule, - NgxMaterialTimepickerModule, - ], - declarations: [ PayRuleSetsContainerComponent, PayRuleSetsTableComponent, PayRuleSetsDeleteModalComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-create-modal/registration-devices-create-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-create-modal/registration-devices-create-modal.component.ts index 9be83cb3b..cb7dce104 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-create-modal/registration-devices-create-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-create-modal/registration-devices-create-modal.component.ts @@ -1,16 +1,20 @@ import {Component, OnInit, inject } from '@angular/core'; -import {MatDialogRef} from '@angular/material/dialog'; +import {MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {TimePlanningPnRegistrationDevicesService} from '../../../../../services/time-planning-pn-registration-devices.service'; import {TimePlanningRegistrationDeviceModel} from '../../../../../../../modules/time-planning-pn/models'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-registration-devices-create-modal', + imports: [FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './registration-devices-create-modal.component.html' // styleUrls: ['./registration-devices-create.component.scss'] , - standalone: false }) export class RegistrationDevicesCreateModalComponent implements OnInit { private registrationDevicesService = inject(TimePlanningPnRegistrationDevicesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-delete-modal/registration-devices-delete-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-delete-modal/registration-devices-delete-modal.component.ts index 66a7f1444..77bcfaeaa 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-delete-modal/registration-devices-delete-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-delete-modal/registration-devices-delete-modal.component.ts @@ -1,16 +1,17 @@ import {Component, OnInit, inject } from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {TimePlanningPnRegistrationDevicesService} from 'src/app/plugins/modules/time-planning-pn/services'; import {TimePlanningRegistrationDeviceModel} from 'src/app/plugins/modules/time-planning-pn/models'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-registration-devices-delete-modal', + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './registration-devices-delete-modal.component.html' // styleUrls: ['./registration-devices-delete.component.scss'] , - standalone: false }) export class RegistrationDevicesDeleteModalComponent implements OnInit { private registrationDevicesService = inject(TimePlanningPnRegistrationDevicesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-edit-modal/registration-devices-edit-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-edit-modal/registration-devices-edit-modal.component.ts index 17342d5f5..003667cdd 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-edit-modal/registration-devices-edit-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-edit-modal/registration-devices-edit-modal.component.ts @@ -1,16 +1,20 @@ import {Component, OnInit, inject } from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {TimePlanningPnRegistrationDevicesService} from 'src/app/plugins/modules/time-planning-pn/services'; import {TimePlanningRegistrationDeviceModel} from 'src/app/plugins/modules/time-planning-pn/models'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @Component({ selector: 'app-registration-devices-edit-modal', + imports: [FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './registration-devices-edit-modal.component.html' // styleUrls: ['./registration-devices-create.component.scss'] , - standalone: false }) export class RegistrationDevicesEditModalComponent implements OnInit { private registrationDevicesService = inject(TimePlanningPnRegistrationDevicesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-otp-code/registration-devices-otp-code.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-otp-code/registration-devices-otp-code.component.ts index 1e4675a9a..4dcaf9814 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-otp-code/registration-devices-otp-code.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-actions/registration-devices-otp-code/registration-devices-otp-code.component.ts @@ -1,16 +1,17 @@ import {Component, OnInit, inject } from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {TimePlanningRegistrationDeviceModel} from '../../../../../models'; import {TimePlanningPnRegistrationDevicesService} from '../../../../../services'; +import {TranslatePipe} from '@ngx-translate/core'; @Component({ selector: 'app-registration-devices-otp-code', + imports: [TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './registration-devices-otp-code.component.html', - standalone: false }) export class RegistrationDevicesOtpCodeComponent implements OnInit { private timePlanningPnRegistrationDevicesService = inject(TimePlanningPnRegistrationDevicesService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-container/registration-devices-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-container/registration-devices-container.component.ts index faf4b3d7f..d75e91293 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-container/registration-devices-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-container/registration-devices-container.component.ts @@ -11,11 +11,14 @@ import {dialogConfigHelper} from 'src/app/common/helpers'; import { RegistrationDevicesCreateModalComponent } from '../index'; +import {TranslatePipe} from '@ngx-translate/core'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; +import {RegistrationDevicesTableComponent} from '../registration-devices-table/registration-devices-table.component'; @Component({ selector: 'app-registration-devices-container', + imports: [TranslatePipe, EformNewSubheaderComponent, RegistrationDevicesTableComponent], templateUrl: './registration-devices-container.component.html', - standalone: false }) export class RegistrationDevicesContainerComponent implements OnInit { public dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.html b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.html index e7e9d71b7..c55f70d6e 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.html +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.html @@ -39,7 +39,7 @@
- diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.ts index 9e033836b..96fd15ecf 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/components/registration-devices-table/registration-devices-table.component.ts @@ -1,8 +1,8 @@ import {Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; -import {MtxGridColumn} from '@ng-matero/extensions/grid'; -import {TranslateService} from '@ngx-translate/core'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import {TimePlanningRegistrationDeviceModel} from '../../../../../../modules/time-planning-pn/models'; import {Subscription} from 'rxjs'; import { @@ -13,11 +13,16 @@ import { import {MatDialog} from '@angular/material/dialog'; import {dialogConfigHelper} from 'src/app/common/helpers'; import {Overlay} from '@angular/cdk/overlay'; +import {NgIf} from '@angular/common'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; @Component({ selector: 'app-registration-devices-table', + imports: [NgIf, TranslatePipe, MatIcon, MatIconButton, MtxGrid, MatTooltip, MatMenu, MatMenuItem, MatMenuTrigger], templateUrl: './registration-devices-table.component.html', - standalone: false }) export class RegistrationDevicesTableComponent implements OnInit { private dialog = inject(MatDialog); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts index 33af1b4fe..0000dd7ce 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts @@ -6,46 +6,13 @@ import { RegistrationDevicesOtpCodeComponent, RegistrationDevicesTableComponent } from './components'; -import {AsyncPipe, NgIf} from '@angular/common'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; -import {MatButton, MatIconButton} from '@angular/material/button'; -import {TranslateModule} from '@ngx-translate/core'; import { RegistrationDevicesRouting } from './registration-devices.routing'; -import {MtxGrid} from '@ng-matero/extensions/grid'; -import {MatDialogActions, MatDialogContent, MatDialogTitle} from '@angular/material/dialog'; -import {MatIcon} from '@angular/material/icon'; -import {MatTooltip} from '@angular/material/tooltip'; -import {FormsModule} from '@angular/forms'; -import {MatFormField, MatLabel} from '@angular/material/form-field'; -import {MatInput} from '@angular/material/input'; -import {MatMenu, MatMenuItem, MatMenuTrigger} from "@angular/material/menu"; @NgModule({ imports: [ - AsyncPipe, - EformSharedModule, - MatButton, - NgIf, - TranslateModule, RegistrationDevicesRouting, - MtxGrid, - MatDialogTitle, - MatDialogContent, - MatDialogActions, - MatIcon, - MatIconButton, - MatTooltip, - FormsModule, - MatFormField, - MatInput, - MatLabel, - MatMenu, - MatMenuItem, - MatMenuTrigger - ], - declarations: [ RegistrationDevicesTableComponent, RegistrationDevicesContainerComponent, RegistrationDevicesCreateModalComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/components/request-history-page/request-history-page.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/components/request-history-page/request-history-page.component.ts index 35e9bf69e..58d85888d 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/components/request-history-page/request-history-page.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/components/request-history-page/request-history-page.component.ts @@ -1,13 +1,20 @@ import { Component, OnInit, OnDestroy, inject, LOCALE_ID } from '@angular/core'; -import { formatDate } from '@angular/common'; +import {formatDate, NgClass} from '@angular/common'; import { TimePlanningPnRequestHistoryService, RequestHistoryQueryParams } from '../../../../services'; import { Subscription, forkJoin, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { TranslateService } from '@ngx-translate/core'; -import { MtxGridColumn } from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; +import {MtxGridColumn, MtxGrid} from '@ng-matero/extensions/grid'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe'; import { ToastrService } from 'ngx-toastr'; import { messages } from '../../../../consts/messages'; +import {FormsModule} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; +import {MatCard} from '@angular/material/card'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatChip} from '@angular/material/chips'; export interface RequestHistoryRow { type: string; @@ -26,9 +33,9 @@ export interface RequestHistoryRow { @AutoUnsubscribe() @Component({ selector: 'app-request-history-page', + imports: [NgClass, FormsModule, TranslatePipe, MatButton, MatCard, MatFormField, MatLabel, MatInput, MtxSelect, MtxGrid, MatChip], templateUrl: './request-history-page.component.html', styleUrls: ['./request-history-page.component.scss'], - standalone: false }) export class RequestHistoryPageComponent implements OnInit, OnDestroy { private requestHistoryService = inject(TimePlanningPnRequestHistoryService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts index d59c06758..6be7c419c 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts @@ -1,37 +1,12 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { EformSharedModule } from 'src/app/common/modules/eform-shared/eform-shared.module'; -import { MatButton } from '@angular/material/button'; -import { TranslateModule } from '@ngx-translate/core'; import { RequestHistoryRouting } from './request-history.routing'; -import { MtxGrid } from '@ng-matero/extensions/grid'; -import { FormsModule } from '@angular/forms'; -import { MatFormField, MatLabel } from '@angular/material/form-field'; -import { MatInput } from '@angular/material/input'; -import { MatCard } from '@angular/material/card'; -import { MatChip } from '@angular/material/chips'; -import { MtxSelectModule } from '@ng-matero/extensions/select'; import { RequestHistoryPageComponent } from './components'; @NgModule({ imports: [ - CommonModule, - EformSharedModule, - MatButton, - TranslateModule, RequestHistoryRouting, - MtxGrid, - FormsModule, - MatFormField, - MatInput, - MatLabel, - MatCard, - MatChip, - MtxSelectModule - ], - declarations: [ RequestHistoryPageComponent ], providers: [], diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/mobile-working-hours/mobile-working-hours.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/mobile-working-hours/mobile-working-hours.component.ts index ad37c1bbd..22af2a44e 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/mobile-working-hours/mobile-working-hours.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/mobile-working-hours/mobile-working-hours.component.ts @@ -7,12 +7,13 @@ import {format} from 'date-fns'; import {PARSING_DATE_FORMAT} from 'src/app/common/const'; import {take} from "rxjs"; import {TitleService} from "src/app/common/services"; -import {TranslateService} from "@ngx-translate/core"; +import {TranslateService, TranslatePipe} from "@ngx-translate/core"; +import {MatCard} from '@angular/material/card'; @Component({ selector: 'app-mobile-working-hours', + imports: [TranslatePipe, MatCard], templateUrl: './mobile-working-hours.component.html', styleUrls: ['./mobile-working-hours.component.scss'], - standalone: false }) export class MobileWorkingHoursComponent implements OnInit, OnDestroy { private titleService = inject(TitleService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-all-update-modal/working-hours-comment-office-all-update-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-all-update-modal/working-hours-comment-office-all-update-modal.component.ts index 78d7b9518..7b75d2681 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-all-update-modal/working-hours-comment-office-all-update-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-all-update-modal/working-hours-comment-office-all-update-modal.component.ts @@ -2,15 +2,19 @@ import {Component, OnDestroy, OnInit, inject } from '@angular/core'; import {AutoUnsubscribe} from 'ngx-auto-unsubscribe'; -import {FormGroup} from '@angular/forms'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {FormGroup, FormsModule} from '@angular/forms'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; +import {DatePipe} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @AutoUnsubscribe() @Component({ selector: 'app-working-hours-comment-office-all-update-modal', + imports: [DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './working-hours-comment-office-all-update-modal.component.html', styleUrls: ['./working-hours-comment-office-all-update-modal.component.scss'], - standalone: false }) export class WorkingHoursCommentOfficeAllUpdateModalComponent implements OnInit, OnDestroy { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-update-modal/working-hours-comment-office-update-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-update-modal/working-hours-comment-office-update-modal.component.ts index 74649593b..c902947ff 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-update-modal/working-hours-comment-office-update-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-comment-office-update-modal/working-hours-comment-office-update-modal.component.ts @@ -2,15 +2,19 @@ import {Component, OnDestroy, OnInit, inject } from '@angular/core'; import {AutoUnsubscribe} from 'ngx-auto-unsubscribe'; -import {FormGroup} from '@angular/forms'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {FormGroup, FormsModule} from '@angular/forms'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; +import {DatePipe} from '@angular/common'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; @AutoUnsubscribe() @Component({ selector: 'app-working-hours-comment-office-update-modal', + imports: [DatePipe, FormsModule, TranslatePipe, MatFormField, MatLabel, MatInput, MatDialogTitle, MatDialogContent, MatDialogActions], templateUrl: './working-hours-comment-office-update-modal.component.html', styleUrls: ['./working-hours-comment-office-update-modal.component.scss'], - standalone: false }) export class WorkingHoursCommentOfficeUpdateModalComponent implements OnInit, OnDestroy { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-upload-modal/working-hours-upload-modal.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-upload-modal/working-hours-upload-modal.component.ts index 960746bf9..9bf3155ab 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-upload-modal/working-hours-upload-modal.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-actions/working-hours-upload-modal/working-hours-upload-modal.component.ts @@ -1,20 +1,21 @@ import {Component, OnInit, inject } from '@angular/core'; -import {TranslateService} from '@ngx-translate/core'; -import {FileUploader} from 'ng2-file-upload'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; +import {FileUploader, FileUploadModule} from 'ng2-file-upload'; import {ToastrService} from 'ngx-toastr'; import {TemplateDto} from 'src/app/common/models/dto'; import {AuthStateService} from 'src/app/common/store'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import {MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions} from '@angular/material/dialog'; import {selectBearerToken} from 'src/app/state/auth/auth.selector'; import {Store} from '@ngrx/store'; +import {NgIf, NgStyle} from '@angular/common'; @Component({ selector: 'app-working-hours-upload-modal', + imports: [NgIf, NgStyle, TranslatePipe, MatDialogTitle, MatDialogContent, MatDialogActions, FileUploadModule], templateUrl: './working-hours-upload-modal.component.html', styleUrls: ['./working-hours-upload-modal.component.scss'], - standalone: false }) export class WorkingHoursUploadModalComponent implements OnInit { private toastrService = inject(ToastrService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-container/working-hours-container.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-container/working-hours-container.component.ts index 62a766f40..3e4f81bd1 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-container/working-hours-container.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-container/working-hours-container.component.ts @@ -10,13 +10,15 @@ import { TimePlanningPnWorkingHoursService, } from '../../../../services'; import {WorkingHourRequestModel, WorkingHourModel} from '../../../../models'; +import {EformNewSubheaderComponent} from 'src/app/common/modules/eform-shared/components'; +import {WorkingHoursTableComponent} from '../working-hours-table/working-hours-table.component'; @AutoUnsubscribe() @Component({ selector: 'app-working-hours-container', + imports: [EformNewSubheaderComponent, WorkingHoursTableComponent], templateUrl: './working-hours-container.component.html', styleUrls: ['./working-hours-container.component.scss'], - standalone: false }) export class WorkingHoursContainerComponent implements OnInit, OnDestroy { private workingHoursService = inject(TimePlanningPnWorkingHoursService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-header/working-hours-header.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-header/working-hours-header.component.ts index c44e828f2..184c86bb7 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-header/working-hours-header.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-header/working-hours-header.component.ts @@ -9,10 +9,10 @@ import {saveAs} from 'file-saver'; import {ToastrService} from 'ngx-toastr'; import {TimePlanningPnWorkingHoursService} from '../../../../services'; import {EMPTY, Subscription} from 'rxjs'; -import {MatIconRegistry} from '@angular/material/icon'; +import {MatIconRegistry, MatIcon} from '@angular/material/icon'; import {DomSanitizer} from '@angular/platform-browser'; import {catchError} from 'rxjs/operators'; -import {MatDatepickerInputEvent} from '@angular/material/datepicker'; +import {MatDatepickerInputEvent, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate} from '@angular/material/datepicker'; import {dialogConfigHelper} from 'src/app/common/helpers'; import {MatDialog} from '@angular/material/dialog'; import {Overlay} from '@angular/cdk/overlay'; @@ -22,12 +22,18 @@ import { import { WorkingHourRequestModel } from '../../../../models'; +import {NgIf} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {TranslatePipe} from '@ngx-translate/core'; +import {MatFormField, MatLabel, MatPrefix} from '@angular/material/form-field'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {MatTooltip} from '@angular/material/tooltip'; @Component({ selector: 'app-working-hours-header', + imports: [NgIf, FormsModule, TranslatePipe, MatIcon, MatFormField, MatLabel, MatPrefix, MtxSelect, MatTooltip, MatDatepickerToggle, MatDateRangeInput, MatDateRangePicker, MatStartDate, MatEndDate], templateUrl: './working-hours-header.component.html', styleUrls: ['./working-hours-header.component.scss'], - standalone: false }) export class WorkingHoursHeaderComponent implements OnInit { private toastrService = inject(ToastrService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.html b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.html index d650b1902..4d1067ae8 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.html +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.html @@ -56,7 +56,7 @@ - + diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.ts index 3b15278ac..87b9bc09f 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/components/working-hours-table/working-hours-table.component.ts @@ -8,11 +8,11 @@ import { SimpleChanges, TemplateRef, ViewChild, ViewEncapsulation, inject } from '@angular/core'; -import {AbstractControl, FormArray, FormControl, FormGroup} from '@angular/forms'; +import {AbstractControl, FormArray, FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; import {Subscription} from 'rxjs'; import {SiteDto} from 'src/app/common/models'; -import {MtxGridColumn, MtxGridRowClassFormatter} from '@ng-matero/extensions/grid'; -import {TranslateService} from '@ngx-translate/core'; +import {MtxGridColumn, MtxGridRowClassFormatter, MtxGrid} from '@ng-matero/extensions/grid'; +import {TranslateService, TranslatePipe} from '@ngx-translate/core'; import { DaysOfWeekEnum, FULL_DATE_FORMAT, HOURS_PICKER_ARRAY, @@ -32,14 +32,18 @@ import { WorkingHourModel, WorkingHourRequestModel } from '../../../../models'; +import {MatFormField} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {MtxSelect} from '@ng-matero/extensions/select'; +import {WorkingHoursHeaderComponent} from '../working-hours-header/working-hours-header.component'; @AutoUnsubscribe() @Component({ selector: 'app-working-hours-table', + imports: [ReactiveFormsModule, TranslatePipe, MatFormField, MatInput, MtxSelect, MtxGrid, WorkingHoursHeaderComponent], templateUrl: './working-hours-table.component.html', styleUrls: ['./working-hours-table.component.css'], encapsulation: ViewEncapsulation.None, - standalone: false }) export class WorkingHoursTableComponent implements OnInit, OnChanges, OnDestroy { private translateService = inject(TranslateService); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/working-hours.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/working-hours.module.ts index 71bc02dd8..6399dcd21 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/working-hours.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/working-hours/working-hours.module.ts @@ -1,9 +1,4 @@ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {TranslateModule} from '@ngx-translate/core'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; import {WorkingHoursRouting} from './working-hours.routing'; import { WorkingHoursContainerComponent, @@ -14,44 +9,12 @@ import { WorkingHoursUploadModalComponent, MobileWorkingHoursComponent, } from './components'; -import {MtxGridModule} from '@ng-matero/extensions/grid'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatInputModule} from '@angular/material/input'; -import {MtxSelectModule} from '@ng-matero/extensions/select'; -import {MatButtonModule} from '@angular/material/button'; -import {MatIconModule} from '@angular/material/icon'; -import {MatTableModule} from '@angular/material/table'; import {MatDialogModule} from '@angular/material/dialog'; -import {MatDatepickerModule} from '@angular/material/datepicker'; -import {MatTooltipModule} from '@angular/material/tooltip'; -import {FileUploadModule} from 'ng2-file-upload'; -import {MatCard} from "@angular/material/card"; -import {MatPasswordStrengthModule} from "@angular-material-extensions/password-strength"; @NgModule({ - imports: [ - CommonModule, - TranslateModule, - FormsModule, - EformSharedModule, - RouterModule, - ReactiveFormsModule, - WorkingHoursRouting, - MtxGridModule, - MatFormFieldModule, - MatInputModule, - MtxSelectModule, - MatButtonModule, - MatIconModule, - MatTableModule, - MatDialogModule, - MatDatepickerModule, - MatTooltipModule, - FileUploadModule, - MatCard, - MatPasswordStrengthModule, - ], - declarations: [ + imports: [ + WorkingHoursRouting, + MatDialogModule, MobileWorkingHoursComponent, WorkingHoursContainerComponent, WorkingHoursHeaderComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts index a34ce89cb..3cc907299 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts @@ -1,10 +1,5 @@ -import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {NgSelectModule} from '@ng-select/ng-select'; -import {TranslateModule} from '@ngx-translate/core'; -import {EformSharedModule} from 'src/app/common/modules/eform-shared/eform-shared.module'; +import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field'; import {TimePlanningPnRouting} from './time-planning-pn.routing'; import {TimePlanningPnLayoutComponent} from './layouts'; import { @@ -20,37 +15,6 @@ import { WorkdayEntityDialogComponent, DownloadExcelDialogComponent, VersionHistoryModalComponent, PayrollExportDialogComponent } from './components'; -import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; -import { - MAT_FORM_FIELD_DEFAULT_OPTIONS, MatError, - MatFormField, - MatLabel, - MatPrefix, - MatSuffix -} from '@angular/material/form-field'; -import {MatInput} from '@angular/material/input'; -import {MatButton, MatIconButton} from '@angular/material/button'; -import {MatSlideToggle} from '@angular/material/slide-toggle'; -import { - MatDatepicker, - MatDatepickerInput, - MatDatepickerToggle, - MatDateRangeInput, - MatDateRangePicker, MatEndDate, MatStartDate -} from '@angular/material/datepicker'; -import {MtxGrid} from '@ng-matero/extensions/grid'; -import {NgxChartsModule} from '@swimlane/ngx-charts'; -import {MatIcon} from '@angular/material/icon'; -import {MatTooltip} from '@angular/material/tooltip'; -import {NgxMaskDirective} from 'ngx-mask'; -import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; -import {MatTab, MatTabGroup} from '@angular/material/tabs'; -import {MatCheckbox} from '@angular/material/checkbox'; -import {MatRadioButton, MatRadioGroup} from '@angular/material/radio'; -import {MatDialogActions, MatDialogClose, MatDialogContent, MatDialogTitle} from '@angular/material/dialog'; -import {MtxSelect} from '@ng-matero/extensions/select'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {CdkScrollable} from '@angular/cdk/scrolling'; import {HelpIconComponent} from './help/components/help-icon/help-icon.component'; import {HelpHintComponent} from './help/components/help-hint/help-hint.component'; import {HelpPanelComponent} from './help/components/help-panel/help-panel.component'; @@ -58,56 +22,7 @@ import {HelpTourComponent} from './help/components/help-tour/help-tour.component @NgModule({ imports: [ - CommonModule, - TranslateModule, - FormsModule, - NgSelectModule, - EformSharedModule, - RouterModule, TimePlanningPnRouting, - ReactiveFormsModule, - MatCardHeader, - MatTab, - MatDialogActions, - MatDialogTitle, - MatDialogContent, - MatDialogClose, - MtxSelect, - MatCard, - MatCheckbox, - MatRadioGroup, - MatRadioButton, - MatCardContent, - MatIconButton, - MatFormField, - MatInput, - MatButton, - MatTabGroup, - MatSlideToggle, - MatLabel, - MatDatepicker, - MatDatepickerInput, - MatDatepickerToggle, - MatSuffix, - MtxGrid, - NgxChartsModule, - MatIcon, - MatTooltip, - NgxMaskDirective, - NgxMaterialTimepickerModule, - MatDateRangeInput, - MatDateRangePicker, - MatStartDate, - MatEndDate, - MatPrefix, - MatError, - OverlayModule, - // The day-cell dialog declares its own overflow container inside - // mat-dialog-content; ScrollDispatcher only sees containers marked - // cdkScrollable, and the help popovers dismiss on scroll. - CdkScrollable - ], - declarations: [ TimePlanningPnLayoutComponent, TimePlanningSettingsComponent, WorkdayEntityDialogComponent, From 057f7bc8e49623ffb4dea984c99a39ee32655214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 09:13:46 +0200 Subject: [PATCH 3/7] test: migrate time-planning-pn specs from Jest to Vitest Co-Authored-By: Claude Opus 5.5 --- .github/workflows/dotnet-core-master.yml | 15 +-- .github/workflows/dotnet-core-pr.yml | 15 +-- .../plannings/day-lock.util.spec.ts | 17 +-- .../plannings/pause-segments.util.spec.ts | 1 + .../assigned-site-dialog.component.spec.ts | 57 ++++----- .../download-excel-dialog.component.spec.ts | 39 +++--- .../payroll-export-dialog.component.spec.ts | 28 ++--- .../workday-entity-dialog.component.spec.ts | 68 +++++----- ...time-plannings-container.component.spec.ts | 92 +++++++------- .../time-plannings-table.component.spec.ts | 65 +++++----- .../help-hint/help-hint.component.spec.ts | 21 ++-- .../help-icon/help-icon.component.spec.ts | 44 ++++--- .../help-panel/help-panel.component.spec.ts | 119 +++++++++--------- .../help-tour/help-tour.component.spec.ts | 38 +++--- .../help/grid-help-anchors.ts | 18 ++- .../time-planning-pn/help/help-wiring.spec.ts | 73 ++++++----- .../time-planning-pn/help/i18n/da.spec.ts | 1 + .../help/i18n/day-type-copy.spec.ts | 1 + .../help/planning-help.registry.spec.ts | 1 + .../help/playwright-tour-seed.spec.ts | 3 +- .../services/help-content.service.spec.ts | 1 + .../help/services/help-panel.service.spec.ts | 1 + .../help/services/help-search.service.spec.ts | 1 + .../help/services/help-tour.service.spec.ts | 5 +- .../services/help-visibility.service.spec.ts | 1 + ...anning-pn-absence-requests.service.spec.ts | 32 ++--- ...planning-pn-break-policies.service.spec.ts | 36 +++--- ...ning-pn-pay-day-type-rules.service.spec.ts | 36 +++--- ...-planning-pn-pay-rule-sets.service.spec.ts | 32 ++--- ...planning-pn-pay-tier-rules.service.spec.ts | 36 +++--- ...ing-pn-pay-time-band-rules.service.spec.ts | 36 +++--- ...planning-pn-payroll-export.service.spec.ts | 18 +-- ...time-planning-pn-plannings.service.spec.ts | 22 ++-- ...-planning-pn-working-hours.service.spec.ts | 12 +- .../testing/detect-changes.ts | 14 +++ .../testing/mutation-observer.ts | 24 ++++ .../testing/page-providers.ts | 27 ++++ .../testing/register-test-locales.ts | 4 +- .../time-planning-pn/testing/source-paths.ts | 10 ++ 39 files changed, 577 insertions(+), 487 deletions(-) create mode 100644 eform-client/src/app/plugins/modules/time-planning-pn/testing/detect-changes.ts create mode 100644 eform-client/src/app/plugins/modules/time-planning-pn/testing/mutation-observer.ts create mode 100644 eform-client/src/app/plugins/modules/time-planning-pn/testing/page-providers.ts create mode 100644 eform-client/src/app/plugins/modules/time-planning-pn/testing/source-paths.ts diff --git a/.github/workflows/dotnet-core-master.yml b/.github/workflows/dotnet-core-master.yml index eb94374fb..41e62159c 100644 --- a/.github/workflows/dotnet-core-master.yml +++ b/.github/workflows/dotnet-core-master.yml @@ -81,20 +81,7 @@ jobs: - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Run Angular unit tests - run: | - cd eform-angular-frontend/eform-client - # Check if Jest is configured - if [ ! -f "jest.config.js" ] && [ ! -f "jest.config.ts" ]; then - echo "⚠️ Jest is not configured in the frontend repository." - echo "Unit tests require Jest to be configured. Skipping unit tests." - echo "" - echo "To enable unit tests, ensure Jest is configured in the frontend repository." - exit 0 - fi - - # Run Jest tests for time-planning-pn plugin - echo "Running Jest tests for time-planning-pn plugin..." - npm run test:unit -- --testPathPatterns=time-planning-pn --coverage --collectCoverageFrom='src/app/plugins/modules/time-planning-pn/**/*.ts' --coveragePathIgnorePatterns='\.spec\.ts$' + run: cd eform-angular-frontend/eform-client && npx ng test --watch=false --include='src/app/plugins/modules/time-planning-pn/**/*.spec.ts' pn-playwright-test: needs: build runs-on: ubuntu-latest diff --git a/.github/workflows/dotnet-core-pr.yml b/.github/workflows/dotnet-core-pr.yml index 615f5805a..9bfeab53d 100644 --- a/.github/workflows/dotnet-core-pr.yml +++ b/.github/workflows/dotnet-core-pr.yml @@ -74,20 +74,7 @@ jobs: - name: yarn install run: cd eform-angular-frontend/eform-client && yarn install - name: Run Angular unit tests - run: | - cd eform-angular-frontend/eform-client - # Check if Jest is configured - if [ ! -f "jest.config.js" ] && [ ! -f "jest.config.ts" ]; then - echo "⚠️ Jest is not configured in the frontend repository." - echo "Unit tests require Jest to be configured. Skipping unit tests." - echo "" - echo "To enable unit tests, ensure Jest is configured in the frontend repository." - exit 0 - fi - - # Run Jest tests for time-planning-pn plugin - echo "Running Jest tests for time-planning-pn plugin..." - npm run test:unit -- --testPathPatterns=time-planning-pn --coverage --collectCoverageFrom='src/app/plugins/modules/time-planning-pn/**/*.ts' --coveragePathIgnorePatterns='\.spec\.ts$' + run: cd eform-angular-frontend/eform-client && npx ng test --watch=false --include='src/app/plugins/modules/time-planning-pn/**/*.spec.ts' pn-playwright-test: needs: build runs-on: ubuntu-latest diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/day-lock.util.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/day-lock.util.spec.ts index f2cc3529c..9adc6e731 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/day-lock.util.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/day-lock.util.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, vi} from 'vitest'; import {DatePipe} from '@angular/common'; import {EMPTY, of, Subject, throwError} from 'rxjs'; import { @@ -189,7 +190,7 @@ describe('day-lock util', () => { }); describe('formatReconciledProvenance', () => { - const translate = {instant: jest.fn((key: string, params?: object) => key)} as any; + const translate = {instant: vi.fn((key: string, params?: object) => key)} as any; /** What the server sends after the projection tags it: a UTC instant, "Z" and all. */ const SERVER_STAMP = '2026-09-14T10:32:11Z'; @@ -200,7 +201,7 @@ describe('day-lock util', () => { // passed, so DatePipe renders it in the VIEWER's zone -- which is the point. // Passing 'UTC' would pin every viewer to the server's clock instead. const datePipe = new DatePipe('en-US'); - const transform = jest.spyOn(datePipe, 'transform'); + const transform = vi.spyOn(datePipe, 'transform'); formatReconciledProvenance(SERVER_STAMP, datePipe, translate); @@ -315,30 +316,30 @@ describe('day-lock util', () => { }); it('stops waiting for a request that never answers, and calls that unknown', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const seen: LockRequestOutcome[] = []; const request = new Subject(); sendLockRequest(request, outcome => seen.push(outcome)); expect(seen).toEqual([]); - jest.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); + vi.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); expect(seen).toEqual([{kind: 'unknown'}]); - jest.useRealTimers(); + vi.useRealTimers(); }); it('lets no late answer through, because the abort is from this end only', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const seen: LockRequestOutcome[] = []; const request = new Subject(); sendLockRequest(request, outcome => seen.push(outcome)); - jest.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); + vi.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); request.next({success: true}); request.complete(); expect(seen).toEqual([{kind: 'unknown'}]); - jest.useRealTimers(); + vi.useRealTimers(); }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/pause-segments.util.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/pause-segments.util.spec.ts index 06314abe9..99e897bf5 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/pause-segments.util.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/pause-segments.util.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { buildPauseSegments } from './pause-segments.util'; describe('buildPauseSegments', () => { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.spec.ts index 2a3acf5a1..17b6952db 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/assigned-site/assigned-site-dialog.component.spec.ts @@ -1,3 +1,5 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AssignedSiteDialogComponent } from './assigned-site-dialog.component'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; @@ -5,16 +7,15 @@ import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { TimePlanningPnSettingsService, TimePlanningPnPayRuleSetsService } from '../../../../services'; import { Store } from '@ngrx/store'; import { of } from 'rxjs'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('AssignedSiteDialogComponent', () => { let component: AssignedSiteDialogComponent; let fixture: ComponentFixture; - let mockSettingsService: jest.Mocked; - let mockPayRuleSetsService: jest.Mocked; - let mockStore: jest.Mocked; + let mockSettingsService: Mocked; + let mockPayRuleSetsService: Mocked; + let mockStore: Mocked; const mockAssignedSiteData = { id: 1, @@ -58,16 +59,16 @@ describe('AssignedSiteDialogComponent', () => { beforeEach(async () => { mockSettingsService = { - getGlobalAutoBreakCalculationSettings: jest.fn(), - updateAssignedSite: jest.fn(), - getAssignedSite: jest.fn(), - getAvailableTags: jest.fn(), + getGlobalAutoBreakCalculationSettings: vi.fn(), + updateAssignedSite: vi.fn(), + getAssignedSite: vi.fn(), + getAvailableTags: vi.fn(), } as any; mockPayRuleSetsService = { - getPayRuleSets: jest.fn(), + getPayRuleSets: vi.fn(), } as any; mockStore = { - select: jest.fn(), + select: vi.fn(), } as any; mockStore.select.mockReturnValue(of(true)); @@ -82,9 +83,7 @@ describe('AssignedSiteDialogComponent', () => { ); await TestBed.configureTestingModule({ - declarations: [AssignedSiteDialogComponent], - imports: [ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA], + imports: [AssignedSiteDialogComponent, ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], providers: [ FormBuilder, { provide: MAT_DIALOG_DATA, useValue: mockAssignedSiteData }, @@ -272,7 +271,7 @@ describe('AssignedSiteDialogComponent', () => { }); it('should handle tags loading error gracefully', () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); mockSettingsService.getAvailableTags.mockReturnValue( of({ success: false, model: null }) as any ); @@ -466,9 +465,7 @@ describe('AssignedSiteDialogComponent', () => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ - declarations: [AssignedSiteDialogComponent], - imports: [ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA], + imports: [AssignedSiteDialogComponent, ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], providers: [ FormBuilder, { provide: MAT_DIALOG_DATA, useValue: dataWithManager }, @@ -499,8 +496,8 @@ describe('AssignedSiteDialogComponent', () => { // and (b) onEntryMethodChange does not mutate editing-policy state. // // Sibling tests in this file deliberately avoid `fixture.detectChanges()` - // because the dialog's template uses Material form-control directives whose - // value accessors are not registered under NO_ERRORS_SCHEMA. We follow the + // because, when the bed declared the dialog under NO_ERRORS_SCHEMA, its + // Material form-control value accessors were not registered. We follow the // same pattern here: assert against component state, not the rendered DOM. function setupComponentWithData( @@ -509,9 +506,7 @@ describe('AssignedSiteDialogComponent', () => { const data = { ...mockAssignedSiteData, ...overrides }; TestBed.resetTestingModule(); TestBed.configureTestingModule({ - declarations: [AssignedSiteDialogComponent], - imports: [ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA], + imports: [AssignedSiteDialogComponent, ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], providers: [ FormBuilder, { provide: MAT_DIALOG_DATA, useValue: data }, @@ -606,10 +601,9 @@ describe('AssignedSiteDialogComponent', () => { describe('1-minute intervals (no longer editable)', () => { // We follow the same no-`detectChanges` pattern adopted in PR #1538: // assert against component state and observables, not the rendered DOM. - // Rendering the dialog under NO_ERRORS_SCHEMA hits the - // NG01203 "No value accessor for form control name: 'resigned'" issue - // because Material form-control directives aren't registered in the - // tested module. + // Rendering the dialog when the bed declared it under NO_ERRORS_SCHEMA hit + // NG01203 "No value accessor for form control name: 'resigned'", because + // Material form-control directives weren't registered in the tested module. function setupWithData( overrides: Partial, @@ -617,9 +611,7 @@ describe('AssignedSiteDialogComponent', () => { const data = { ...mockAssignedSiteData, ...overrides }; TestBed.resetTestingModule(); TestBed.configureTestingModule({ - declarations: [AssignedSiteDialogComponent], - imports: [ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA], + imports: [AssignedSiteDialogComponent, ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], providers: [ FormBuilder, { provide: MAT_DIALOG_DATA, useValue: data }, @@ -646,8 +638,7 @@ describe('AssignedSiteDialogComponent', () => { // The checkbox's *ngIf is `data.usePunchClock` inside the mobile-axis block // (gated by !data.resigned && data.allowPersonalTimeRegistration && admin). // Same no-`detectChanges` pattern as the sibling blocks above: assert - // against component state, not the rendered DOM, because Material - // form-control directives aren't registered under NO_ERRORS_SCHEMA. + // against component state, not the rendered DOM (see the first block). function setupWithData( overrides: Partial & { overMidnight?: boolean }, @@ -655,9 +646,7 @@ describe('AssignedSiteDialogComponent', () => { const data = { ...mockAssignedSiteData, ...overrides }; TestBed.resetTestingModule(); TestBed.configureTestingModule({ - declarations: [AssignedSiteDialogComponent], - imports: [ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], - schemas: [NO_ERRORS_SCHEMA], + imports: [AssignedSiteDialogComponent, ReactiveFormsModule, TranslateModule.forRoot(), HttpClientTestingModule], providers: [ FormBuilder, { provide: MAT_DIALOG_DATA, useValue: data }, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts index d0bb91246..5a8f44df6 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts @@ -1,3 +1,6 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked, Mock} from 'vitest'; +import { provideNativeDateAdapter } from '@angular/material/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DownloadExcelDialogComponent, DownloadExcelDialogData } from './download-excel-dialog.component'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; @@ -5,7 +8,6 @@ import { TimePlanningPnPlanningsService, TimePlanningPnWorkingHoursService } fro import { ToastrService } from 'ngx-toastr'; import { of, throwError } from 'rxjs'; import { format } from 'date-fns'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; @@ -13,9 +15,9 @@ import { CommonModule } from '@angular/common'; describe('DownloadExcelDialogComponent', () => { let component: DownloadExcelDialogComponent; let fixture: ComponentFixture; - let mockWorkingHoursService: jest.Mocked; - let mockPlanningsService: jest.Mocked; - let mockToastrService: jest.Mocked; + let mockWorkingHoursService: Mocked; + let mockPlanningsService: Mocked; + let mockToastrService: Mocked; let dialogData: DownloadExcelDialogData; /** Two active workers on tag 1, one on tag 2, and a resigned one that also carries tag 1. */ @@ -28,26 +30,26 @@ describe('DownloadExcelDialogComponent', () => { beforeEach(async () => { // Mock URL.createObjectURL and URL.revokeObjectURL for file-saver - global.URL.createObjectURL = jest.fn(() => 'mock-url'); - global.URL.revokeObjectURL = jest.fn(); + global.URL.createObjectURL = vi.fn(() => 'mock-url'); + global.URL.revokeObjectURL = vi.fn(); // Mock HTMLAnchorElement.prototype.click to prevent navigation errors - const mockClick = jest.fn(); + const mockClick = vi.fn(); Object.defineProperty(HTMLAnchorElement.prototype, 'click', { configurable: true, value: mockClick, }); mockWorkingHoursService = { - downloadReport: jest.fn(), - downloadReportAllWorkers: jest.fn(), + downloadReport: vi.fn(), + downloadReportAllWorkers: vi.fn(), } as any; mockPlanningsService = { - getSiteTags: jest.fn().mockReturnValue(of({ success: true, model: siteTags })), + getSiteTags: vi.fn().mockReturnValue(of({ success: true, model: siteTags })), } as any; mockToastrService = { - error: jest.fn(), - success: jest.fn(), + error: vi.fn(), + success: vi.fn(), } as any; dialogData = { @@ -60,10 +62,9 @@ describe('DownloadExcelDialogComponent', () => { }; await TestBed.configureTestingModule({ - declarations: [DownloadExcelDialogComponent], - imports: [CommonModule, FormsModule, TranslateModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [DownloadExcelDialogComponent, CommonModule, FormsModule, TranslateModule.forRoot()], providers: [ + provideNativeDateAdapter(), { provide: MAT_DIALOG_DATA, useValue: dialogData }, { provide: TimePlanningPnWorkingHoursService, useValue: mockWorkingHoursService }, { provide: TimePlanningPnPlanningsService, useValue: mockPlanningsService }, @@ -188,7 +189,7 @@ describe('DownloadExcelDialogComponent', () => { }); }); - it('should show error toast when download fails', (done) => { + it('should show error toast when download fails', () => new Promise((done) => { mockWorkingHoursService.downloadReport.mockReturnValue( throwError(() => new Error('Download failed')) ); @@ -200,7 +201,7 @@ describe('DownloadExcelDialogComponent', () => { expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); done(); }, 100); - }); + })); }); describe('Excel Report All Workers Download', () => { @@ -233,7 +234,7 @@ describe('DownloadExcelDialogComponent', () => { expect('tagIds' in callArgs).toBe(false); }); - it('should show error toast when download all workers fails', (done) => { + it('should show error toast when download all workers fails', () => new Promise((done) => { mockWorkingHoursService.downloadReportAllWorkers.mockReturnValue( throwError(() => new Error('Download failed')) ); @@ -245,7 +246,7 @@ describe('DownloadExcelDialogComponent', () => { expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); done(); }, 100); - }); + })); it('should not include siteId in all workers report model', () => { const mockBlob = new Blob(['test'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.spec.ts index a54c76d2f..8b4c62bea 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/payroll-export/payroll-export-dialog.component.spec.ts @@ -1,5 +1,7 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; +import { provideNativeDateAdapter } from '@angular/material/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { TranslateModule } from '@ngx-translate/core'; import { HttpClientTestingModule } from '@angular/common/http/testing'; @@ -11,19 +13,19 @@ import { of } from 'rxjs'; describe('PayrollExportDialogComponent', () => { let component: PayrollExportDialogComponent; let fixture: ComponentFixture; - let mockDialogRef: jest.Mocked>; - let mockPayrollExportService: jest.Mocked; + let mockDialogRef: Mocked>; + let mockPayrollExportService: Mocked; const mockDialogData = { cutoffDay: 19, payrollSystem: 1 }; beforeEach(async () => { mockDialogRef = { - close: jest.fn(), + close: vi.fn(), } as any; mockPayrollExportService = { - preview: jest.fn(), - exportPayroll: jest.fn(), + preview: vi.fn(), + exportPayroll: vi.fn(), } as any; mockPayrollExportService.preview.mockReturnValue( @@ -31,10 +33,9 @@ describe('PayrollExportDialogComponent', () => { ); await TestBed.configureTestingModule({ - declarations: [PayrollExportDialogComponent], - imports: [TranslateModule.forRoot(), HttpClientTestingModule, ToastrModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [PayrollExportDialogComponent, TranslateModule.forRoot(), HttpClientTestingModule, ToastrModule.forRoot()], providers: [ + provideNativeDateAdapter(), { provide: MAT_DIALOG_DATA, useValue: mockDialogData }, { provide: MatDialogRef, useValue: mockDialogRef }, { provide: TimePlanningPnPayrollExportService, useValue: mockPayrollExportService }, @@ -53,7 +54,7 @@ describe('PayrollExportDialogComponent', () => { it('should calculate period when before cutoff day', () => { // Simulate a date before the cutoff (e.g., April 10 with cutoff 19) const fakeNow = new Date(2026, 3, 10); // April 10, 2026 - jest.spyOn(global, 'Date').mockImplementation((...args: any[]) => { + vi.spyOn(global, 'Date').mockImplementation((...args: any[]) => { if (args.length === 0) { return fakeNow; } @@ -62,7 +63,7 @@ describe('PayrollExportDialogComponent', () => { }); const OriginalDate = global.Date; // Restore Date before calling ngOnInit - jest.restoreAllMocks(); + vi.restoreAllMocks(); // Directly set a known date scenario by calling ngOnInit with controlled state // Since we cannot easily mock Date constructor used internally, @@ -95,10 +96,9 @@ describe('PayrollExportDialogComponent', () => { // Reconfigure with no cutoffDay TestBed.resetTestingModule(); await TestBed.configureTestingModule({ - declarations: [PayrollExportDialogComponent], - imports: [TranslateModule.forRoot(), HttpClientTestingModule, ToastrModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [PayrollExportDialogComponent, TranslateModule.forRoot(), HttpClientTestingModule, ToastrModule.forRoot()], providers: [ + provideNativeDateAdapter(), { provide: MAT_DIALOG_DATA, useValue: { cutoffDay: 0, payrollSystem: 1 } }, { provide: MatDialogRef, useValue: mockDialogRef }, { provide: TimePlanningPnPayrollExportService, useValue: mockPayrollExportService }, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.spec.ts index efdf2377b..bb8da76b0 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.spec.ts @@ -1,5 +1,8 @@ +import {describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi} from 'vitest'; +import type {Mocked, Mock} from 'vitest'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { MODULE_ROOT } from '../../../../testing/source-paths'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { WorkdayEntityDialogComponent } from './workday-entity-dialog.component'; import { LOCK_REQUEST_TIMEOUT_MS } from '../../day-lock.util'; @@ -9,7 +12,6 @@ import { TimePlanningPnPlanningsService, TimePlanningPnGpsCoordinatesService, Ti import { TranslateService } from '@ngx-translate/core'; import { DatePipe, CommonModule } from '@angular/common'; import { EMPTY, of, Subject, throwError } from 'rxjs'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; @@ -23,14 +25,14 @@ import { ToastrService } from 'ngx-toastr'; describe('WorkdayEntityDialogComponent', () => { let component: WorkdayEntityDialogComponent; let fixture: ComponentFixture; - let mockPlanningsService: jest.Mocked; - let mockTranslateService: jest.Mocked; - let mockGpsCoordinatesService: jest.Mocked; - let mockPictureSnapshotsService: jest.Mocked; - let mockDomSanitizer: jest.Mocked; - let mockTemplateFilesService: jest.Mocked; - let mockDialogRef: jest.Mocked>; - let mockToastrService: jest.Mocked; + let mockPlanningsService: Mocked; + let mockTranslateService: Mocked; + let mockGpsCoordinatesService: Mocked; + let mockPictureSnapshotsService: Mocked; + let mockDomSanitizer: Mocked; + let mockTemplateFilesService: Mocked; + let mockDialogRef: Mocked>; + let mockToastrService: Mocked; const mockData = { planningPrDayModels: { @@ -102,33 +104,33 @@ describe('WorkdayEntityDialogComponent', () => { beforeEach(async () => { mockPlanningsService = { - updatePlanning: jest.fn(), - reconcileDay: jest.fn(), - unreconcileDay: jest.fn(), + updatePlanning: vi.fn(), + reconcileDay: vi.fn(), + unreconcileDay: vi.fn(), } as any; mockTranslateService = { - instant: jest.fn(), - stream: jest.fn(), + instant: vi.fn(), + stream: vi.fn(), onLangChange: of({ lang: 'en' }), } as any; mockGpsCoordinatesService = { - getByPlanRegistrationId: jest.fn().mockReturnValue(of({ success: false, model: null })), + getByPlanRegistrationId: vi.fn().mockReturnValue(of({ success: false, model: null })), } as any; mockPictureSnapshotsService = { - getByPlanRegistrationId: jest.fn().mockReturnValue(of({ success: false, model: null })), + getByPlanRegistrationId: vi.fn().mockReturnValue(of({ success: false, model: null })), } as any; mockDomSanitizer = { - bypassSecurityTrustResourceUrl: jest.fn().mockImplementation((url) => url), + bypassSecurityTrustResourceUrl: vi.fn().mockImplementation((url) => url), } as any; mockTemplateFilesService = { - getImage: jest.fn().mockReturnValue(of(new Blob())), + getImage: vi.fn().mockReturnValue(of(new Blob())), } as any; mockToastrService = { - error: jest.fn(), - success: jest.fn(), + error: vi.fn(), + success: vi.fn(), } as any; mockDialogRef = { - close: jest.fn(), + close: vi.fn(), // The reconcile/unlock PUTs flip this while they are in flight, so it has to // start as the real MatDialogRef default rather than undefined. disableClose: false, @@ -144,9 +146,7 @@ describe('WorkdayEntityDialogComponent', () => { mockTranslateService.stream.mockReturnValue(of('Translated')); await TestBed.configureTestingModule({ - declarations: [WorkdayEntityDialogComponent], - imports: [CommonModule, ReactiveFormsModule, TranslateModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [WorkdayEntityDialogComponent, CommonModule, ReactiveFormsModule, TranslateModule.forRoot()], providers: [ FormBuilder, DatePipe, @@ -218,21 +218,21 @@ describe('WorkdayEntityDialogComponent', () => { }); it('offers the dialog tour with the real audience flags, not hardcoded falses', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); localStorage.removeItem(TOUR_STORAGE_KEY); const tour = TestBed.inject(HelpTourService); - const start = jest.spyOn(tour, 'start').mockImplementation(() => undefined); + const start = vi.spyOn(tour, 'start').mockImplementation(() => undefined); component.isAdmin = true; component.isFirstUser = true; (component as any).startDialogTourOnce(); - jest.runAllTimers(); + vi.runAllTimers(); // Both axes, so a later entry gated on either is judged by the right one. expect(start).toHaveBeenCalledWith('dialog', { isAdmin: true, isFirstUser: true }); start.mockRestore(); - jest.useRealTimers(); + vi.useRealTimers(); }); }); @@ -953,8 +953,8 @@ describe('WorkdayEntityDialogComponent', () => { // Timing out aborts the request from this end, but the server may commit a // moment later, so the day's state is UNKNOWN rather than unchanged. const tour = TestBed.inject(HelpTourService); - const start = jest.spyOn(tour, 'start').mockImplementation(() => undefined); - jest.useFakeTimers(); + const start = vi.spyOn(tour, 'start').mockImplementation(() => undefined); + vi.useFakeTimers(); const put = new Subject(); mockPlanningsService.reconcileDay.mockReturnValue(put.asObservable()); armReconcile(); @@ -963,7 +963,7 @@ describe('WorkdayEntityDialogComponent', () => { expect(component.lockRequestInFlight).toBe(true); expect(mockDialogRef.disableClose).toBe(true); - jest.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); + vi.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); // Settled and usable again... expect(component.lockRequestInFlight).toBe(false); @@ -984,7 +984,7 @@ describe('WorkdayEntityDialogComponent', () => { expect(component.lockStateChanged).toBe(true); expect(mockDialogRef.close).not.toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); start.mockRestore(); }); @@ -1046,7 +1046,7 @@ describe('WorkdayEntityDialogComponent', () => { armUnlock(); component.unlockWordCtrl.setValue('LÅS OP'); let disableCloseAtClose: boolean | undefined; - (mockDialogRef.close as jest.Mock) + (mockDialogRef.close as Mock) .mockImplementation(() => { disableCloseAtClose = mockDialogRef.disableClose; }); component.onUnlockConfirm(); @@ -1215,7 +1215,7 @@ describe('WorkdayEntityDialogComponent', () => { // so the structural invariants of the footer are read off the file. The // Playwright specs in shard `s` exercise the rendered version. const template = readFileSync( - join(__dirname, 'workday-entity-dialog.component.html'), 'utf8'); + join(MODULE_ROOT, 'components/plannings/time-planning-actions/workday-entity/workday-entity-dialog.component.html'), 'utf8'); const buttonWithId = (id: string): string => { const match = new RegExp(`]*\\bid="${id}"[^>]*>`).exec(template); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.spec.ts index 695fb0aa9..7b53aa13b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-container/time-plannings-container.component.spec.ts @@ -1,5 +1,9 @@ +import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest'; +import type {Mocked, MockInstance} from 'vitest'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { MODULE_ROOT } from '../../../testing/source-paths'; +import { pageProviders } from '../../../testing/page-providers'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TimePlanningsContainerComponent } from './time-plannings-container.component'; import { TimePlanningPnPlanningsService } from '../../../services/time-planning-pn-plannings.service'; @@ -9,7 +13,6 @@ import { Store } from '@ngrx/store'; import { selectCurrentUserIsFirstUser } from 'src/app/state'; import { BehaviorSubject, EMPTY, of, Subject, throwError } from 'rxjs'; import { addDays, endOfWeek, format, startOfWeek, subDays } from 'date-fns'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ToastrService } from 'ngx-toastr'; import { HelpTourService } from '../../../help/services/help-tour.service'; @@ -19,30 +22,30 @@ import { LOCK_REQUEST_TIMEOUT_MS } from '../day-lock.util'; describe('TimePlanningsContainerComponent', () => { let component: TimePlanningsContainerComponent; let fixture: ComponentFixture; - let mockPlanningsService: jest.Mocked; - let mockSettingsService: jest.Mocked; - let mockDialog: jest.Mocked; - let mockStore: jest.Mocked; + let mockPlanningsService: Mocked; + let mockSettingsService: Mocked; + let mockDialog: Mocked; + let mockStore: Mocked; beforeEach(async () => { mockPlanningsService = { - getPlannings: jest.fn(), - updatePlanning: jest.fn(), - reconcileThrough: jest.fn(), + getPlannings: vi.fn(), + updatePlanning: vi.fn(), + reconcileThrough: vi.fn(), } as any; mockSettingsService = { - getAvailableSites: jest.fn(), - getResignedSites: jest.fn(), - getAssignedSite: jest.fn(), - updateAssignedSite: jest.fn(), - getAvailableTags: jest.fn(), - getPayrollSettings: jest.fn(), + getAvailableSites: vi.fn(), + getResignedSites: vi.fn(), + getAssignedSite: vi.fn(), + updateAssignedSite: vi.fn(), + getAvailableTags: vi.fn(), + getPayrollSettings: vi.fn(), } as any; mockDialog = { - open: jest.fn(), + open: vi.fn(), } as any; mockStore = { - select: jest.fn(), + select: vi.fn(), } as any; mockStore.select.mockReturnValue(of('en-US')); @@ -52,17 +55,16 @@ describe('TimePlanningsContainerComponent', () => { mockPlanningsService.getPlannings.mockReturnValue(of({ success: true, model: [] }) as any); await TestBed.configureTestingModule({ - declarations: [TimePlanningsContainerComponent], - imports: [TranslateModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [TimePlanningsContainerComponent, TranslateModule.forRoot()], providers: [ + ...pageProviders(), { provide: TimePlanningPnPlanningsService, useValue: mockPlanningsService }, { provide: TimePlanningPnSettingsService, useValue: mockSettingsService }, { provide: MatDialog, useValue: mockDialog }, { provide: Store, useValue: mockStore }, { provide: ToastrService, - useValue: { success: jest.fn(), warning: jest.fn(), error: jest.fn() }, + useValue: { success: vi.fn(), warning: vi.fn(), error: vi.fn() }, }, ] }).compileComponents(); @@ -142,7 +144,7 @@ describe('TimePlanningsContainerComponent', () => { describe('Event Handlers', () => { it('should call getPlannings when onTimePlanningChanged is triggered', () => { - jest.spyOn(component, 'getPlannings'); + vi.spyOn(component, 'getPlannings'); component.onTimePlanningChanged({}); @@ -150,7 +152,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('should call getPlannings when onAssignedSiteChanged is triggered', () => { - jest.spyOn(component, 'getPlannings'); + vi.spyOn(component, 'getPlannings'); component.onAssignedSiteChanged({}); @@ -158,7 +160,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('should update siteId and call getPlannings when onSiteChanged is triggered', () => { - jest.spyOn(component, 'getPlannings'); + vi.spyOn(component, 'getPlannings'); const testSiteId = 123; component.onSiteChanged(testSiteId); @@ -227,7 +229,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('should update selectedTagIds and call getPlannings when onTagsChanged is triggered', () => { - jest.spyOn(component, 'getPlannings'); + vi.spyOn(component, 'getPlannings'); const testTagIds = [1, 2, 3]; component.onTagsChanged(testTagIds); @@ -237,7 +239,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('should hold an empty list, never null, when the select clears its last tag', () => { - jest.spyOn(component, 'getPlannings'); + vi.spyOn(component, 'getPlannings'); component.selectedTagIds = [1]; component.onTagsChanged(null); @@ -284,19 +286,19 @@ describe('TimePlanningsContainerComponent', () => { describe('Page help tour', () => { let tour: HelpTourService; - let start: jest.SpyInstance; + let start: MockInstance; beforeEach(() => { localStorage.clear(); tour = TestBed.inject(HelpTourService); - start = jest.spyOn(tour, 'start').mockImplementation(() => undefined); - jest.useFakeTimers(); + start = vi.spyOn(tour, 'start').mockImplementation(() => undefined); + vi.useFakeTimers(); component.dateFrom = new Date(2024, 0, 15); component.dateTo = new Date(2024, 0, 21); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); start.mockRestore(); localStorage.clear(); }); @@ -308,7 +310,7 @@ describe('TimePlanningsContainerComponent', () => { mockPlanningsService.getPlannings.mockReturnValue(of({ success: true, model: [] }) as any); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).not.toHaveBeenCalled(); }); @@ -318,7 +320,7 @@ describe('TimePlanningsContainerComponent', () => { of({ success: true, model: [{ siteId: 1, siteName: 'A' }] }) as any); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); // Literals, not component.isAdmin / component.isFirstUser: reading the expected // values off the component under test asserts nothing about what was passed. @@ -330,17 +332,17 @@ describe('TimePlanningsContainerComponent', () => { // around a start the gate refuses would mean this planner never gets the // tour on this page visit, even once help becomes visible to them. const visibility = TestBed.inject(HelpVisibilityService); - const isVisible = jest.spyOn(visibility, 'isVisible', 'get').mockReturnValue(false); + const isVisible = vi.spyOn(visibility, 'isVisible', 'get').mockReturnValue(false); mockPlanningsService.getPlannings.mockReturnValue( of({ success: true, model: [{ siteId: 1, siteName: 'A' }] }) as any); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).not.toHaveBeenCalled(); isVisible.mockReturnValue(true); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).toHaveBeenCalledWith('page', { isAdmin: false, isFirstUser: true }); isVisible.mockRestore(); @@ -351,9 +353,9 @@ describe('TimePlanningsContainerComponent', () => { of({ success: true, model: [{ siteId: 1, siteName: 'A' }] }) as any); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).toHaveBeenCalledTimes(1); }); @@ -362,7 +364,7 @@ describe('TimePlanningsContainerComponent', () => { // start('dialog') is otherwise called from one place, gated on hasSeen, so // this is the only route back to the dialog tour once it has been skipped. component.replayTour('dialog'); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).toHaveBeenCalledWith('dialog', { isAdmin: false, isFirstUser: true }); }); @@ -373,7 +375,7 @@ describe('TimePlanningsContainerComponent', () => { of({ success: true, model: [{ siteId: 1, siteName: 'A' }] }) as any); component.getPlannings(); - jest.runAllTimers(); + vi.runAllTimers(); expect(start).not.toHaveBeenCalled(); }); @@ -629,7 +631,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('reports the two skip reasons as separate figures, never merged', () => { - const instant = jest.spyOn(TestBed.inject(TranslateService), 'instant'); + const instant = vi.spyOn(TestBed.inject(TranslateService), 'instant'); mockPlanningsService.reconcileThrough.mockReturnValue(of({ success: true, model: resultModel({ @@ -653,7 +655,7 @@ describe('TimePlanningsContainerComponent', () => { }); it('leaves the no-registration figure out when there is none', () => { - const instant = jest.spyOn(TestBed.inject(TranslateService), 'instant'); + const instant = vi.spyOn(TestBed.inject(TranslateService), 'instant'); mockPlanningsService.reconcileThrough.mockReturnValue( of({ success: true, model: resultModel() }) as any); @@ -751,15 +753,15 @@ describe('TimePlanningsContainerComponent', () => { }); it('stops waiting for a request that never answers, and reloads rather than claim failure', () => { - jest.useFakeTimers(); - const tour = jest.spyOn(TestBed.inject(HelpTourService), 'start').mockImplementation(() => undefined); + vi.useFakeTimers(); + const tour = vi.spyOn(TestBed.inject(HelpTourService), 'start').mockImplementation(() => undefined); const request = new Subject(); mockPlanningsService.reconcileThrough.mockReturnValue(request as any); component.confirmReconcileThrough(); expect(component.reconcileInFlight).toBe(true); - jest.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); + vi.advanceTimersByTime(LOCK_REQUEST_TIMEOUT_MS + 1); expect(component.reconcileInFlight).toBe(false); expect(toastr.error).toHaveBeenCalledWith('lockRequestUncertain'); @@ -774,7 +776,7 @@ describe('TimePlanningsContainerComponent', () => { expect(mockPlanningsService.getPlannings.mock.calls.length).toBe(after); tour.mockRestore(); - jest.useRealTimers(); + vi.useRealTimers(); }); it('refuses to commit twice while a request is in flight', () => { @@ -885,7 +887,7 @@ describe('TimePlanningsContainerComponent', () => { // what some other user may do. Read for the RIGHT flag, too: isAdmin still gates // the payroll export button in this same toolbar. const template = readFileSync( - join(__dirname, 'time-plannings-container.component.html'), 'utf8'); + join(MODULE_ROOT, 'components/plannings/time-plannings-container/time-plannings-container.component.html'), 'utf8'); const field = /]*data-tp-help="toolbar\.reconcileThrough"[^>]*>/.exec(template); expect(field).not.toBeNull(); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.spec.ts index ba5907ac2..d2ecf3cf9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-plannings-table/time-plannings-table.component.spec.ts @@ -1,5 +1,8 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked, Mock} from 'vitest'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { MODULE_ROOT } from '../../../testing/source-paths'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TimePlanningsTableComponent } from './time-plannings-table.component'; import { TimePlanningPnPlanningsService } from '../../../services/time-planning-pn-plannings.service'; @@ -7,7 +10,7 @@ import { TimePlanningPnSettingsService } from '../../../services/time-planning-p import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; -import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectorRef } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectCurrentUserIsFirstUser } from 'src/app/state'; import { BehaviorSubject, of } from 'rxjs'; @@ -22,31 +25,31 @@ registerTestLocales(); describe('TimePlanningsTableComponent', () => { let component: TimePlanningsTableComponent; let fixture: ComponentFixture; - let mockPlanningsService: jest.Mocked; - let mockSettingsService: jest.Mocked; - let mockDialog: jest.Mocked; - let mockTranslateService: jest.Mocked; - let mockStore: jest.Mocked; + let mockPlanningsService: Mocked; + let mockSettingsService: Mocked; + let mockDialog: Mocked; + let mockTranslateService: Mocked; + let mockStore: Mocked; beforeEach(async () => { mockPlanningsService = { - getPlannings: jest.fn(), - updatePlanning: jest.fn(), + getPlannings: vi.fn(), + updatePlanning: vi.fn(), } as any; mockSettingsService = { - getAssignedSite: jest.fn(), - updateAssignedSite: jest.fn(), + getAssignedSite: vi.fn(), + updateAssignedSite: vi.fn(), } as any; mockDialog = { - open: jest.fn(), + open: vi.fn(), } as any; mockTranslateService = { - stream: jest.fn(), - instant: jest.fn(), + stream: vi.fn(), + instant: vi.fn(), onLangChange: of({ lang: 'en' }), } as any; mockStore = { - select: jest.fn(), + select: vi.fn(), } as any; mockStore.select.mockReturnValue(of(true)); @@ -54,9 +57,7 @@ describe('TimePlanningsTableComponent', () => { mockTranslateService.instant.mockReturnValue('Translated'); await TestBed.configureTestingModule({ - declarations: [TimePlanningsTableComponent], - imports: [TranslateModule.forRoot()], - schemas: [NO_ERRORS_SCHEMA], + imports: [TimePlanningsTableComponent, TranslateModule.forRoot()], providers: [ { provide: TimePlanningPnPlanningsService, useValue: mockPlanningsService }, { provide: TimePlanningPnSettingsService, useValue: mockSettingsService }, @@ -410,11 +411,11 @@ describe('TimePlanningsTableComponent', () => { } }); - const dialogData = () => (mockDialog.open as jest.Mock).mock.calls[0][1].data; + const dialogData = () => (mockDialog.open as Mock).mock.calls[0][1].data; beforeEach(() => { mockSettingsService.getAssignedSite.mockReturnValue(of({ success: true, model: {} }) as any); - (mockDialog.open as jest.Mock).mockReturnValue({ afterClosed: () => of(undefined) } as any); + (mockDialog.open as Mock).mockReturnValue({ afterClosed: () => of(undefined) } as any); }); it('does not open a placeholder day, which has no registration behind it', () => { @@ -459,14 +460,14 @@ describe('TimePlanningsTableComponent', () => { describe('the close contract after a reconcile or unlock', () => { /** A dialog that closed with `payload`, having reported `lockStateChanged`. */ const dialogThatClosed = (lockStateChanged: boolean, payload: any) => { - (mockDialog.open as jest.Mock).mockReturnValue({ + (mockDialog.open as Mock).mockReturnValue({ componentInstance: { lockStateChanged }, afterClosed: () => of(payload), } as any); }; it('reloads the grid and sends no save when the dialog reconciled the day', () => { - const changed = jest.fn(); + const changed = vi.fn(); component.timePlanningChanged.subscribe(changed); // A reconcile can close through Cancel, Esc or the backdrop, so the close // payload proves nothing either way; lockStateChanged is what decides. @@ -546,7 +547,7 @@ describe('TimePlanningsTableComponent', () => { it('should format time correctly when on same day', () => { // This test depends on the DatePipe transform which we've mocked - jest.spyOn(component['datePipe'], 'transform').mockReturnValue('10:30'); + vi.spyOn(component['datePipe'], 'transform').mockReturnValue('10:30'); const result = component.getStopTimeDisplay('2024-01-15T08:00:00', '2024-01-15T10:30:00'); expect(result).toBe('10:30'); }); @@ -564,7 +565,7 @@ describe('TimePlanningsTableComponent', () => { }); it("uses HH:mm format when row.useOneMinuteIntervals is false", () => { - const transformSpy = jest + const transformSpy = vi .spyOn(component['datePipe'], 'transform') .mockReturnValue('07:03'); const result = component.formatStamp( @@ -576,7 +577,7 @@ describe('TimePlanningsTableComponent', () => { }); it("uses HH:mm format when row.useOneMinuteIntervals is true", () => { - const transformSpy = jest + const transformSpy = vi .spyOn(component['datePipe'], 'transform') .mockReturnValue('07:03'); const result = component.formatStamp( @@ -588,7 +589,7 @@ describe('TimePlanningsTableComponent', () => { }); it("falls back to HH:mm when row is null/undefined (defensive)", () => { - const transformSpy = jest + const transformSpy = vi .spyOn(component['datePipe'], 'transform') .mockReturnValue('07:03'); const result = component.formatStamp(null as any, '2026-05-15T07:03:53Z'); @@ -618,7 +619,7 @@ describe('TimePlanningsTableComponent', () => { }); it("uses HH:mm format when flag off (legacy parity)", () => { - const transformSpy = jest + const transformSpy = vi .spyOn(component['datePipe'], 'transform') .mockReturnValue('15:30'); const result = component.getStopTimeDisplayWithSeconds( @@ -631,7 +632,7 @@ describe('TimePlanningsTableComponent', () => { }); it("uses HH:mm format when flag on", () => { - const transformSpy = jest + const transformSpy = vi .spyOn(component['datePipe'], 'transform') .mockReturnValue('15:30'); const result = component.getStopTimeDisplayWithSeconds( @@ -646,7 +647,7 @@ describe('TimePlanningsTableComponent', () => { describe('formatDuration', () => { beforeEach(() => { - jest + vi .spyOn(component['translateService'], 'instant') .mockImplementation((key: string) => key); }); @@ -709,7 +710,7 @@ describe('TimePlanningsTableComponent', () => { * view. */ const stubHeaderFlush = (target: any) => { - target.cdr = { detectChanges: jest.fn() }; + target.cdr = { detectChanges: vi.fn() }; }; /** @@ -734,8 +735,8 @@ describe('TimePlanningsTableComponent', () => { * would silently widen a previewed scope. */ describe('row selection', () => { - const changed = jest.fn(); - const reset = jest.fn(); + const changed = vi.fn(); + const reset = vi.fn(); beforeEach(() => { changed.mockClear(); @@ -927,7 +928,7 @@ describe('TimePlanningsTableComponent', () => { // binding is read off the file — and read for the RIGHT flag: isAdmin still // gates unrelated chrome in this same template. const template = readFileSync( - join(__dirname, 'time-plannings-table.component.html'), 'utf8'); + join(MODULE_ROOT, 'components/plannings/time-plannings-table/time-plannings-table.component.html'), 'utf8'); expect(template).toContain('[rowSelectable]="isFirstUser"'); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.spec.ts index 6ca17a952..c6a6d1d56 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-hint/help-hint.component.spec.ts @@ -1,6 +1,8 @@ +import {describe, it, expect, beforeEach} from 'vitest'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIconModule } from '@angular/material/icon'; import { TranslateService } from '@ngx-translate/core'; +import { detectChanges } from '../../../testing/detect-changes'; import { HelpHintComponent } from './help-hint.component'; import { of } from 'rxjs'; import { enUS } from '../../i18n/enUS'; @@ -23,8 +25,7 @@ describe('HelpHintComponent', () => { // The stub is shared by every case here; the gate tests flip it. helpVisibility.isVisible = true; await TestBed.configureTestingModule({ - declarations: [HelpHintComponent], - imports: [MatIconModule], + imports: [HelpHintComponent, MatIconModule], providers: [ { provide: TranslateService, useValue: { currentLang: 'en-US' } }, provideHelpVisibility, @@ -35,46 +36,46 @@ describe('HelpHintComponent', () => { it('renders the entry short text', () => { fixture.componentInstance.helpId = 'dayCell.futureDisabled'; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.textContent).toContain(enUS['dayCell.futureDisabled'].short); }); it('uses the info tone by default and warn when asked', () => { fixture.componentInstance.helpId = 'dayCell.futureDisabled'; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.help-text')?.classList).not.toContain('help-text--warn'); fixture.componentInstance.tone = 'warn'; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.help-text')?.classList).toContain('help-text--warn'); }); it('renders nothing for an unknown id', () => { fixture.componentInstance.helpId = 'nope' as never; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.help-text')).toBeNull(); }); it('renders the correct icon based on tone', () => { fixture.componentInstance.helpId = 'dayCell.futureDisabled'; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.mat-icon').textContent).toContain('info'); fixture.componentInstance.tone = 'warn'; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.mat-icon').textContent).toContain('warning'); }); it('renders nothing when help is not visible, and the hint again when it is', () => { fixture.componentInstance.helpId = 'dayCell.futureDisabled'; helpVisibility.isVisible = false; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.help-text')).toBeNull(); // The admin half: otherwise the assertion above is indistinguishable from // the component rendering nothing under any circumstances. helpVisibility.isVisible = true; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('.help-text')).not.toBeNull(); expect(fixture.nativeElement.textContent).toContain(enUS['dayCell.futureDisabled'].short); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.spec.ts index 1e320825d..46cb64c3a 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-icon/help-icon.component.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach} from 'vitest'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CloseScrollStrategy, OverlayModule } from '@angular/cdk/overlay'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -8,6 +9,8 @@ import { HelpIconComponent } from './help-icon.component'; import { of } from 'rxjs'; import { enUS } from '../../i18n/enUS'; import { HelpVisibilityService } from '../../services/help-visibility.service'; +import { detectChanges } from '../../../testing/detect-changes'; +import { useWorkingMutationObserver } from '../../../testing/mutation-observer'; /** * The one dependency the help chrome gained when help became admin-only. A stub @@ -20,14 +23,15 @@ const provideHelpVisibility = { provide: HelpVisibilityService, useValue: helpVi describe('HelpIconComponent', () => { + useWorkingMutationObserver(); + let fixture: ComponentFixture; beforeEach(async () => { // The stub is shared by every case here; the gate tests flip it. helpVisibility.isVisible = true; await TestBed.configureTestingModule({ - declarations: [HelpIconComponent], - imports: [OverlayModule, NoopAnimationsModule, MatIconModule, MatButtonModule], + imports: [HelpIconComponent, OverlayModule, NoopAnimationsModule, MatIconModule, MatButtonModule], providers: [ { provide: TranslateService, useValue: { currentLang: 'en-US' } }, provideHelpVisibility, @@ -36,7 +40,7 @@ describe('HelpIconComponent', () => { fixture = TestBed.createComponent(HelpIconComponent); fixture.componentInstance.helpId = 'toolbar.dateRange'; - fixture.detectChanges(); + detectChanges(fixture); }); it('labels the button with the entry title', () => { @@ -47,7 +51,7 @@ describe('HelpIconComponent', () => { it('starts closed and opens on click', () => { expect(fixture.componentInstance.isOpen).toBe(false); fixture.nativeElement.querySelector('button').click(); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.isOpen).toBe(true); }); @@ -62,11 +66,11 @@ describe('HelpIconComponent', () => { it('closes on Escape', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); expect(document.querySelector('.tp-help-popover')).not.toBeNull(); press('Escape'); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.isOpen).toBe(false); expect(document.querySelector('.tp-help-popover')).toBeNull(); @@ -74,10 +78,10 @@ describe('HelpIconComponent', () => { it('ignores other keys', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); press('a'); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.isOpen).toBe(true); expect(document.querySelector('.tp-help-popover')).not.toBeNull(); @@ -87,12 +91,12 @@ describe('HelpIconComponent', () => { const seen: string[] = []; fixture.componentInstance.openInPanel.subscribe(id => seen.push(id)); fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); const more = document.querySelector('.tp-help-popover__more') as HTMLButtonElement; expect(more).not.toBeNull(); more.click(); - fixture.detectChanges(); + detectChanges(fixture); expect(seen).toEqual(['toolbar.dateRange']); expect(fixture.componentInstance.isOpen).toBe(false); @@ -100,7 +104,7 @@ describe('HelpIconComponent', () => { it('closes when a click lands outside the popover, without a backdrop to swallow it', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); // No backdrop means the same click that dismisses the popover reaches the // control underneath - on this page every day cell is a click target. @@ -109,7 +113,7 @@ describe('HelpIconComponent', () => { const outside = document.createElement('button'); document.body.appendChild(outside); outside.dispatchEvent(new MouseEvent('click', { bubbles: true })); - fixture.detectChanges(); + detectChanges(fixture); outside.remove(); expect(fixture.componentInstance.isOpen).toBe(false); @@ -117,18 +121,18 @@ describe('HelpIconComponent', () => { it('stays open when the click lands inside the popover', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); const body = document.querySelector('.tp-help-popover__body') as HTMLElement; body.dispatchEvent(new MouseEvent('click', { bubbles: true })); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.isOpen).toBe(true); }); it('describes the popover as a note, not a modal dialog it does not implement', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); const popover = document.querySelector('.tp-help-popover'); expect(popover).not.toBeNull(); @@ -144,13 +148,13 @@ describe('HelpIconComponent', () => { it('dismisses on scroll', () => { fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); expect(document.querySelector('.tp-help-popover')).not.toBeNull(); // ScrollDispatcher listens for 'scroll' on the document and pushes through // its scrolled() stream, which the close strategy is subscribed to. document.dispatchEvent(new Event('scroll')); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.isOpen).toBe(false); expect(document.querySelector('.tp-help-popover')).toBeNull(); @@ -159,7 +163,7 @@ describe('HelpIconComponent', () => { it('renders nothing for an unknown id rather than throwing', () => { const other = TestBed.createComponent(HelpIconComponent); other.componentInstance.helpId = 'nope' as never; - expect(() => other.detectChanges()).not.toThrow(); + expect(() => detectChanges(other)).not.toThrow(); expect(other.nativeElement.querySelector('button')).toBeNull(); }); @@ -169,7 +173,7 @@ describe('HelpIconComponent', () => { // forced — no popover either. helpVisibility.isVisible = false; fixture.componentInstance.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('button')).toBeNull(); expect(document.querySelector('.tp-help-popover')).toBeNull(); @@ -177,7 +181,7 @@ describe('HelpIconComponent', () => { // And an admin still gets all of it. Without this half, the assertions above // would pass just as well if the component had been deleted. helpVisibility.isVisible = true; - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.nativeElement.querySelector('button')).not.toBeNull(); expect(document.querySelector('.tp-help-popover')).not.toBeNull(); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.spec.ts index 7e4b1405c..b26ffd3a5 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-panel/help-panel.component.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; @@ -10,6 +11,7 @@ import { enUS, enUSUi } from '../../i18n/enUS'; import { HelpPanelService } from '../../services/help-panel.service'; import { HelpVisibilityService } from '../../services/help-visibility.service'; import { HelpPanelComponent } from './help-panel.component'; +import { detectChanges } from '../../../testing/detect-changes'; /** * The one dependency the help chrome gained when help became admin-only. A stub @@ -42,8 +44,7 @@ describe('HelpPanelComponent', () => { // The stub is shared by every case here; the gate tests flip it. helpVisibility.isVisible = true; await TestBed.configureTestingModule({ - declarations: [HelpPanelComponent], - imports: [FormsModule, MatIconModule, MatButtonModule], + imports: [HelpPanelComponent, FormsModule, MatIconModule, MatButtonModule], providers: [ HelpPanelService, { provide: TranslateService, useValue: { currentLang: 'en-US' } }, @@ -54,7 +55,7 @@ describe('HelpPanelComponent', () => { fixture = TestBed.createComponent(HelpPanelComponent); component = fixture.componentInstance; panel = TestBed.inject(HelpPanelService); - fixture.detectChanges(); + detectChanges(fixture); }); it('renders nothing while closed', () => { @@ -64,7 +65,7 @@ describe('HelpPanelComponent', () => { it('renders its chrome from the help ui strings, never a translate key', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); const header = fixture.nativeElement.querySelector('.tp-help-panel__top h5') as HTMLElement; const input = fixture.nativeElement.querySelector('.tp-help-panel__search input') as HTMLInputElement; @@ -80,7 +81,7 @@ describe('HelpPanelComponent', () => { it('browses grouped sections in order when open with no query', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(panelEl()).not.toBeNull(); expect(component.isSearching).toBe(false); @@ -100,7 +101,7 @@ describe('HelpPanelComponent', () => { it('groups every entry under its own section', () => { component.isAdmin = true; panel.open(); - fixture.detectChanges(); + detectChanges(fixture); for (const group of component.sections) { expect(group.entries.every(entry => entry.section === group.section)).toBe(true); @@ -110,10 +111,10 @@ describe('HelpPanelComponent', () => { it('switches to results when a query is typed and back when it is cleared', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); component.onQueryChange('vacation'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isSearching).toBe(true); expect(component.results.length).toBeGreaterThan(0); @@ -124,7 +125,7 @@ describe('HelpPanelComponent', () => { expect(countText()).not.toContain(enUSUi.noResults); component.clearQuery(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isSearching).toBe(false); expect(component.results).toEqual([]); @@ -133,12 +134,12 @@ describe('HelpPanelComponent', () => { it('drives the query from the search input through ngModel', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); const input = fixture.nativeElement.querySelector('.tp-help-panel__search input') as HTMLInputElement; input.value = 'vacation'; input.dispatchEvent(new Event('input')); - fixture.detectChanges(); + detectChanges(fixture); expect(component.query).toBe('vacation'); expect(component.isSearching).toBe(true); @@ -148,7 +149,7 @@ describe('HelpPanelComponent', () => { it('treats a whitespace-only query as browsing', () => { panel.open(); component.onQueryChange(' '); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isSearching).toBe(false); expect(sectionHeadings().length).toBe(5); @@ -157,13 +158,13 @@ describe('HelpPanelComponent', () => { it('hides admin-only entries when isAdmin is false, in browse and in results', () => { component.isAdmin = false; panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(browsedIds()).not.toContain('toolbar.payrollExport'); expect(text()).not.toContain(enUS['toolbar.payrollExport'].title); component.onQueryChange('payroll'); - fixture.detectChanges(); + detectChanges(fixture); expect(resultIds().length).toBeGreaterThan(0); expect(resultIds()).not.toContain('toolbar.payrollExport'); @@ -173,7 +174,7 @@ describe('HelpPanelComponent', () => { component.isAdmin = true; panel.open(); component.onQueryChange('payroll'); - fixture.detectChanges(); + detectChanges(fixture); expect(resultIds()).toContain('toolbar.payrollExport'); expect(text()).toContain(enUS['toolbar.payrollExport'].title); @@ -181,7 +182,7 @@ describe('HelpPanelComponent', () => { it('marks and expands the deep-link target', () => { panel.open('flex.sumFlex'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.targetId).toBe('flex.sumFlex'); expect(component.expanded).toBe('flex.sumFlex'); @@ -194,19 +195,19 @@ describe('HelpPanelComponent', () => { it('expands and collapses an entry on click', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(text()).not.toContain(enUS['task.registerVacation'].steps?.[0] as string); component.toggleEntry('task.registerVacation'); - fixture.detectChanges(); + detectChanges(fixture); const steps = Array.from(fixture.nativeElement.querySelectorAll('.tp-help-entry__steps li')) .map(el => ((el as HTMLElement).textContent ?? '').trim()); expect(steps).toEqual(enUS['task.registerVacation'].steps); component.toggleEntry('task.registerVacation'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.expanded).toBeNull(); expect(fixture.nativeElement.querySelector('.tp-help-entry__steps')).toBeNull(); @@ -215,17 +216,17 @@ describe('HelpPanelComponent', () => { it('closes through the service and forgets the query', () => { panel.open(); component.onQueryChange('vacation'); - fixture.detectChanges(); + detectChanges(fixture); component.close(); - fixture.detectChanges(); + detectChanges(fixture); expect(panelEl()).toBeNull(); expect(component.query).toBe(''); expect(component.results).toEqual([]); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isSearching).toBe(false); expect(sectionHeadings().length).toBe(5); @@ -238,10 +239,10 @@ describe('HelpPanelComponent', () => { const replays: HelpTourName[] = []; component.replayTourRequested.subscribe(tour => replays.push(tour)); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); clickReplay(); - fixture.detectChanges(); + detectChanges(fixture); expect(replays).toEqual(['page']); expect(panelEl()).toBeNull(); @@ -254,23 +255,23 @@ describe('HelpPanelComponent', () => { const replays: HelpTourName[] = []; component.replayTourRequested.subscribe(tour => replays.push(tour)); panel.open('dayCell.save', 'dialog'); - fixture.detectChanges(); + detectChanges(fixture); clickReplay(); - fixture.detectChanges(); + detectChanges(fixture); expect(replays).toEqual(['dialog']); }); it('goes back to the page tour once the panel has closed', () => { panel.open('dayCell.save', 'dialog'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.surface).toBe('dialog'); panel.close(); - fixture.detectChanges(); + detectChanges(fixture); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.surface).toBe('page'); }); @@ -279,7 +280,7 @@ describe('HelpPanelComponent', () => { panel.open(); // 'avatar' appears in exactly one entry's prose. component.onQueryChange('avatar'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.results.length).toBe(1); expect(enUSUi.resultCountOne).not.toBe(enUSUi.resultCount); @@ -290,9 +291,9 @@ describe('HelpPanelComponent', () => { // `related` is registry data the panel is the only consumer of; unrendered it // is dead weight the integrity spec alone keeps honest. panel.open(); - fixture.detectChanges(); + detectChanges(fixture); component.toggleEntry('task.registerVacation'); - fixture.detectChanges(); + detectChanges(fixture); const links = Array.from( fixture.nativeElement.querySelectorAll('.tp-help-entry__related-link'), @@ -306,13 +307,13 @@ describe('HelpPanelComponent', () => { it('moves the panel to a related control when its link is used', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); component.toggleEntry('task.registerVacation'); - fixture.detectChanges(); + detectChanges(fixture); const first = component.related('task.registerVacation')[0]; (fixture.nativeElement.querySelector('.tp-help-entry__related-link') as HTMLElement).click(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.expanded).toBe(first); expect(component.targetId).toBe(first); @@ -327,16 +328,16 @@ describe('HelpPanelComponent', () => { component.replayTourRequested.subscribe(tour => replays.push(tour)); // A deep link already expands its target, so there is nothing to toggle. panel.open('task.registerVacation', 'dialog'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.expanded).toBe('task.registerVacation'); (fixture.nativeElement.querySelector('.tp-help-entry__related-link') as HTMLElement).click(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.surface).toBe('dialog'); clickReplay(); - fixture.detectChanges(); + detectChanges(fixture); expect(replays).toEqual(['dialog']); }); @@ -354,7 +355,7 @@ describe('HelpPanelComponent', () => { it('names the query and offers the tasks when nothing matches', () => { panel.open(); component.onQueryChange('zzzqqq'); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isSearching).toBe(true); expect(component.isFallback).toBe(true); @@ -370,7 +371,7 @@ describe('HelpPanelComponent', () => { component.isAdmin = true; panel.open(); component.onQueryChange('payroll'); - fixture.detectChanges(); + detectChanges(fixture); const labels = Array.from(fixture.nativeElement.querySelectorAll('.tp-help-entry__kind')) .map(el => (el as HTMLElement).getAttribute('aria-label')); @@ -382,15 +383,15 @@ describe('HelpPanelComponent', () => { it('closes on Escape, and only while open', () => { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isOpen).toBe(false); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(panelEl()).not.toBeNull(); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isOpen).toBe(false); expect(panelEl()).toBeNull(); @@ -404,14 +405,14 @@ describe('HelpPanelComponent', () => { // key press reaches the page (from the focused element, bubbling through body) // and assert the body listener never sees it. panel.open(); - fixture.detectChanges(); + detectChanges(fixture); - const dispatcherStandIn = jest.fn(); + const dispatcherStandIn = vi.fn(); document.body.addEventListener('keydown', dispatcherStandIn); const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }); document.body.dispatchEvent(event); document.body.removeEventListener('keydown', dispatcherStandIn); - fixture.detectChanges(); + detectChanges(fixture); expect(dispatcherStandIn).not.toHaveBeenCalled(); expect(event.defaultPrevented).toBe(true); @@ -422,7 +423,7 @@ describe('HelpPanelComponent', () => { it('lets Escape through to the page once the panel has closed', () => { // The shield must not outlive the panel, or Escape would stop closing the // day-cell dialog at all. - const dispatcherStandIn = jest.fn(); + const dispatcherStandIn = vi.fn(); document.body.addEventListener('keydown', dispatcherStandIn); document.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })); @@ -433,14 +434,14 @@ describe('HelpPanelComponent', () => { it('leaves other keys alone while open', () => { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); - const dispatcherStandIn = jest.fn(); + const dispatcherStandIn = vi.fn(); document.body.addEventListener('keydown', dispatcherStandIn); document.body.dispatchEvent( new KeyboardEvent('keydown', { key: 'a', bubbles: true, cancelable: true })); document.body.removeEventListener('keydown', dispatcherStandIn); - fixture.detectChanges(); + detectChanges(fixture); expect(dispatcherStandIn).toHaveBeenCalledTimes(1); expect(component.isOpen).toBe(true); @@ -449,14 +450,14 @@ describe('HelpPanelComponent', () => { it('rebuilds what isAdmin filters when it arrives after opening', () => { panel.open(); component.onQueryChange('payroll'); - fixture.detectChanges(); + detectChanges(fixture); expect(browsedIds()).not.toContain('toolbar.payrollExport'); expect(resultIds()).not.toContain('toolbar.payrollExport'); component.isAdmin = true; component.ngOnChanges({ isAdmin: new SimpleChange(false, true, false) }); - fixture.detectChanges(); + detectChanges(fixture); expect(browsedIds()).toContain('toolbar.payrollExport'); expect(resultIds()).toContain('toolbar.payrollExport'); @@ -466,7 +467,7 @@ describe('HelpPanelComponent', () => { // Both guards, on their false side. ngOnChanges fires once at creation with // firstChange true, before the panel has ever opened; rebuilding then would // hand *ngFor a fresh array on every open and drop focus and scroll position. - const rebuild = jest.spyOn(component as any, 'buildSections'); + const rebuild = vi.spyOn(component as any, 'buildSections'); component.isAdmin = true; component.ngOnChanges({ isAdmin: new SimpleChange(undefined, true, true) }); @@ -479,7 +480,7 @@ describe('HelpPanelComponent', () => { // A change that is neither of those does rebuild, so the test above is not // passing because buildSections is unreachable. panel.open(); - fixture.detectChanges(); + detectChanges(fixture); rebuild.mockClear(); component.ngOnChanges({ isAdmin: new SimpleChange(false, true, false) }); expect(rebuild).toHaveBeenCalled(); @@ -503,27 +504,27 @@ describe('HelpPanelComponent', () => { helpVisibility.isVisible = false; panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isOpen).toBe(false); expect(panelEl()).toBeNull(); panel.open('flex.sumFlex' as HelpEntryId); - fixture.detectChanges(); + detectChanges(fixture); expect(panelEl()).toBeNull(); // And the markup is gated too, so isOpen being set by any other route than // the service — a refactor, a test, a subclass — still renders nothing. component.isOpen = true; - fixture.detectChanges(); + detectChanges(fixture); expect(panelEl()).toBeNull(); component.isOpen = false; // An admin opens it exactly as before. helpVisibility.isVisible = true; panel.close(); - fixture.detectChanges(); + detectChanges(fixture); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(component.isOpen).toBe(true); expect(panelEl()).not.toBeNull(); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.spec.ts index 34c26bcc2..5912147f3 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/components/help-tour/help-tour.component.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach} from 'vitest'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ElementRef } from '@angular/core'; import { OverlayModule } from '@angular/cdk/overlay'; @@ -18,8 +19,12 @@ const provideHelpVisibility = { provide: HelpVisibilityService, useValue: helpVi import { HelpTourService } from '../../services/help-tour.service'; import { HelpTourName } from '../../help.model'; import { enUS, enUSUi } from '../../i18n/enUS'; +import { detectChanges } from '../../../testing/detect-changes'; +import { useWorkingMutationObserver } from '../../../testing/mutation-observer'; describe('HelpTourComponent', () => { + useWorkingMutationObserver(); + const anchor = (id: string): HTMLElement => { const element = document.createElement('div'); element.setAttribute('data-tp-help', id); @@ -30,7 +35,7 @@ describe('HelpTourComponent', () => { const mount = (tour: HelpTourName = 'page'): ComponentFixture => { const fixture = TestBed.createComponent(HelpTourComponent); fixture.componentInstance.tour = tour; - fixture.detectChanges(); + detectChanges(fixture); return fixture; }; @@ -43,8 +48,7 @@ describe('HelpTourComponent', () => { document.body.querySelectorAll('[data-tp-help]').forEach(element => element.remove()); localStorage.clear(); TestBed.configureTestingModule({ - declarations: [HelpTourComponent], - imports: [OverlayModule], + imports: [HelpTourComponent, OverlayModule], providers: [ { provide: TranslateService, useValue: { currentLang: 'en-US' } }, provideHelpVisibility, @@ -78,8 +82,10 @@ describe('HelpTourComponent', () => { const dialogTour = mount('dialog'); TestBed.inject(HelpTourService).start('dialog', { isAdmin: false }); - pageTour.detectChanges(); - dialogTour.detectChanges(); + // Dialog first: a zoneless check verifies every attached view, so checking the + // page first would trip over the dialog's not-yet-refreshed step. + detectChanges(dialogTour); + detectChanges(pageTour); expect(dialogTour.componentInstance.state?.entry.id).toBe('dayCell.plannedTimes'); expect(pageTour.componentInstance.state).toBeNull(); @@ -144,7 +150,7 @@ describe('HelpTourComponent', () => { .toBe('1 / 2'); fixture.componentInstance.next(); - fixture.detectChanges(); + detectChanges(fixture); expect(card()!.querySelector('.tp-help-tour__step')!.textContent!.replace(/\s+/g, ' ').trim()) .toBe('2 / 2'); @@ -166,7 +172,7 @@ describe('HelpTourComponent', () => { const fixture = mount(); (card()!.querySelector('.tp-help-tour__next') as HTMLButtonElement).click(); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state?.entry.id).toBe('grid.openDay'); expect(fixture.componentInstance.origin?.nativeElement).toBe(second); @@ -178,7 +184,7 @@ describe('HelpTourComponent', () => { const fixture = mount(); (card()!.querySelector('.tp-help-tour__skip') as HTMLButtonElement).click(); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state).toBeNull(); expect(card()).toBeNull(); @@ -197,11 +203,11 @@ describe('HelpTourComponent', () => { document.body.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true })); press('a'); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state).not.toBeNull(); press('Escape'); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state).toBeNull(); expect(card()).toBeNull(); @@ -220,7 +226,7 @@ describe('HelpTourComponent', () => { expect(card()).not.toBeNull(); element.remove(); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state).toBeNull(); expect(fixture.componentInstance.origin).toBeNull(); @@ -239,7 +245,7 @@ describe('HelpTourComponent', () => { expect(card()).not.toBeNull(); element.remove(); - fixture.detectChanges(); + detectChanges(fixture); expect(tour.isRunning).toBe(false); expect(tour.hasSeen('dialog')).toBe(false); @@ -247,12 +253,12 @@ describe('HelpTourComponent', () => { // Still offerable: the same tour runs again and renders. element = anchor('dayCell.plannedTimes'); tour.start('dialog', { isAdmin: false }); - fixture.detectChanges(); + detectChanges(fixture); expect(card()).not.toBeNull(); // The other direction: the user ending it does count. (card()!.querySelector('.tp-help-tour__skip') as HTMLButtonElement).click(); - fixture.detectChanges(); + detectChanges(fixture); expect(card()).toBeNull(); expect(tour.hasSeen('dialog')).toBe(true); @@ -265,7 +271,7 @@ describe('HelpTourComponent', () => { element.remove(); const replacement = anchor('toolbar.dateRange'); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.origin?.nativeElement).toBe(replacement); expect(card()).not.toBeNull(); @@ -277,7 +283,7 @@ describe('HelpTourComponent', () => { const fixture = mount(); fixture.componentInstance.next(); - fixture.detectChanges(); + detectChanges(fixture); expect(fixture.componentInstance.state).toBeNull(); expect(fixture.componentInstance.origin).toBeNull(); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/grid-help-anchors.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/grid-help-anchors.ts index a1215b344..2518c319b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/grid-help-anchors.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/grid-help-anchors.ts @@ -1,4 +1,5 @@ import { isDevMode } from '@angular/core'; +import { HelpEntry } from './help.model'; import { PLANNING_HELP_ENTRIES } from './planning-help.registry'; /** @@ -10,12 +11,21 @@ import { PLANNING_HELP_ENTRIES } from './planning-help.registry'; export const GRID_NAME_HEADER_SELECTOR = 'th.mat-column-siteName'; /** Read from the registry rather than repeated, so the stamp cannot drift from it. */ -const SORT_NAME_ANCHOR = PLANNING_HELP_ENTRIES.find(entry => entry.id === 'grid.sortName')?.anchor; +const findSortNameAnchor = (entries: readonly HelpEntry[]) => + entries.find(entry => entry.id === 'grid.sortName')?.anchor; +const SORT_NAME_ANCHOR = findSortNameAnchor(PLANNING_HELP_ENTRIES); let warnedAboutMissingAnchor = false; -export function applyGridHelpAnchors(root: ParentNode | null | undefined): void { - if (!SORT_NAME_ANCHOR) { +/** + * `entries` exists for specs: the app always stamps from PLANNING_HELP_ENTRIES. + */ +export function applyGridHelpAnchors( + root: ParentNode | null | undefined, + entries: readonly HelpEntry[] = PLANNING_HELP_ENTRIES, +): void { + const sortNameAnchor = entries === PLANNING_HELP_ENTRIES ? SORT_NAME_ANCHOR : findSortNameAnchor(entries); + if (!sortNameAnchor) { // A stamp that quietly stops happening is worse than one that complains: // the registry would still advertise an anchor nothing produces. if (isDevMode() && !warnedAboutMissingAnchor) { @@ -29,6 +39,6 @@ export function applyGridHelpAnchors(root: ParentNode | null | undefined): void } const header = root?.querySelector(GRID_NAME_HEADER_SELECTOR); if (header && !header.hasAttribute('data-tp-help')) { - header.setAttribute('data-tp-help', SORT_NAME_ANCHOR); + header.setAttribute('data-tp-help', sortNameAnchor); } } diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/help-wiring.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/help-wiring.spec.ts index ad48e46c8..82dd5a2c8 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/help-wiring.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/help-wiring.spec.ts @@ -1,4 +1,6 @@ +import {describe, it, expect, beforeEach, beforeAll, afterAll, vi} from 'vitest'; import { readdirSync, readFileSync } from 'fs'; +import { createRequire } from 'module'; import { dirname, join } from 'path'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; @@ -15,8 +17,9 @@ import { HelpTourService } from './services/help-tour.service'; import { applyGridHelpAnchors } from './grid-help-anchors'; import { HelpVisibilityService } from './services/help-visibility.service'; import { enUSUi } from './i18n/enUS'; +import { CLIENT_ROOT, MODULE_ROOT } from '../testing/source-paths'; +import { detectChanges } from '../testing/detect-changes'; -const MODULE_ROOT = join(__dirname, '..'); /** Help is admin-only; the components under test need the flag, not a whole store. */ const provideHelpVisibility = { @@ -289,24 +292,20 @@ describe('help wiring', () => { }); it('warns instead of stopping silently if the registry drops the sortName anchor', () => { - jest.isolateModules(() => { - jest.doMock('./planning-help.registry', () => ({ PLANNING_HELP_ENTRIES: [] })); - const warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { applyGridHelpAnchors: stamp } = require('./grid-help-anchors'); - - const root = document.createElement('div'); - const header = document.createElement('th'); - header.className = 'mat-column-siteName'; - root.appendChild(header); - - stamp(root); - - expect(header.hasAttribute('data-tp-help')).toBe(false); - expect(warn).toHaveBeenCalledWith(expect.stringContaining('grid.sortName')); - warn.mockRestore(); - }); - jest.dontMock('./planning-help.registry'); + // An empty registry stands in for one that lost the sortName entry + // (module mocks of relative files don't apply under Angular's test builder). + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + const root = document.createElement('div'); + const header = document.createElement('th'); + header.className = 'mat-column-siteName'; + root.appendChild(header); + + applyGridHelpAnchors(root, []); + + expect(header.hasAttribute('data-tp-help')).toBe(false); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('grid.sortName')); + warn.mockRestore(); }); it('calls the header stamp from the table component', () => { @@ -324,7 +323,7 @@ describe('help wiring', () => { // loses hit-testing to it — a click on the panel would reach the backdrop // and close the dialog. The CDK value is read, not assumed. const cdkCss = readFileSync( - join(dirname(require.resolve('@angular/cdk/package.json')), 'overlay-prebuilt.css'), 'utf8'); + join(dirname(createRequire(join(CLIENT_ROOT, 'package.json')).resolve('@angular/cdk/package.json')), 'overlay-prebuilt.css'), 'utf8'); const cdkLayers = [...cdkCss.matchAll( /\.cdk-(?:overlay-backdrop|overlay-pane|global-overlay-wrapper)\{[^}]*?z-index:\s*(\d+)/g)] .map(match => Number(match[1])); @@ -365,8 +364,7 @@ describe('help deep link and tour scrolling', () => { const mountPanel = (): ComponentFixture => { TestBed.configureTestingModule({ - declarations: [HelpPanelComponent], - imports: [FormsModule, MatIconModule, MatButtonModule], + imports: [HelpPanelComponent, FormsModule, MatIconModule, MatButtonModule], providers: [ HelpPanelService, { provide: TranslateService, useValue: { currentLang: 'en-US' } }, @@ -374,7 +372,7 @@ describe('help deep link and tour scrolling', () => { ], }); const fixture = TestBed.createComponent(HelpPanelComponent); - fixture.detectChanges(); + detectChanges(fixture); return fixture; }; @@ -384,15 +382,15 @@ describe('help deep link and tour scrolling', () => { // Browsing the whole catalogue has nothing to scroll to. panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(scrolled).toHaveLength(0); // flex.sumFlex sits in the last section, well below the fold of a panel that // opens scrolled to the top. panel.close(); - fixture.detectChanges(); + detectChanges(fixture); panel.open('flex.sumFlex'); - fixture.detectChanges(); + detectChanges(fixture); const target = fixture.nativeElement.querySelector('.tp-help-entry--target') as HTMLElement; expect(target).not.toBeNull(); @@ -410,14 +408,14 @@ describe('help deep link and tour scrolling', () => { expect(host.parentNode).not.toBe(container); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); expect(host.parentNode).toBe(container); expect(document.activeElement) .toBe(host.querySelector('.tp-help-panel__search input')); panel.close(); - fixture.detectChanges(); + detectChanges(fixture); expect(host.parentNode).not.toBe(container); }); @@ -433,9 +431,9 @@ describe('help deep link and tour scrolling', () => { for (let cycle = 0; cycle < 2; cycle++) { panel.open(); - fixture.detectChanges(); + detectChanges(fixture); panel.close(); - fixture.detectChanges(); + detectChanges(fixture); } expect(host.nextSibling).toBe(marker); @@ -450,7 +448,7 @@ describe('help deep link and tour scrolling', () => { const panel = TestBed.inject(HelpPanelService); panel.open(); - fixture.detectChanges(); + detectChanges(fixture); const search = host.querySelector('.tp-help-panel__search input') as HTMLElement; expect(document.activeElement).toBe(search); @@ -459,12 +457,12 @@ describe('help deep link and tour scrolling', () => { const entryHead = host.querySelector('.tp-help-entry__head') as HTMLElement; entryHead.focus(); panel.open('flex.sumFlex'); - fixture.detectChanges(); + detectChanges(fixture); expect(document.activeElement).toBe(entryHead); // Closing hands focus back to whatever opened it. panel.close(); - fixture.detectChanges(); + detectChanges(fixture); expect(document.activeElement).toBe(trigger); trigger.remove(); @@ -472,8 +470,7 @@ describe('help deep link and tour scrolling', () => { it('scrolls the current tour anchor into view when a step becomes current', () => { TestBed.configureTestingModule({ - declarations: [HelpTourComponent], - imports: [OverlayModule], + imports: [HelpTourComponent, OverlayModule], providers: [ { provide: TranslateService, useValue: { currentLang: 'en-US' } }, provideHelpVisibility, @@ -489,18 +486,18 @@ describe('help deep link and tour scrolling', () => { const fixture: ComponentFixture = TestBed.createComponent(HelpTourComponent); fixture.componentInstance.tour = 'page'; - fixture.detectChanges(); + detectChanges(fixture); const tour = TestBed.inject(HelpTourService); tour.start('page', { isAdmin: true }); - fixture.detectChanges(); + detectChanges(fixture); const first = anchors.get('toolbar.dateRange') as HTMLElement; expect(scrolled).toContain(first); scrolled.length = 0; tour.next(); - fixture.detectChanges(); + detectChanges(fixture); expect(scrolled).toContain(anchors.get('toolbar.navForward') as HTMLElement); expect(scrolled).not.toContain(first); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/da.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/da.spec.ts index 5609bc7b4..4a7fc3f1c 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/da.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/da.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { da, daUi } from './da'; import { enUS, enUSUi } from './enUS'; import { HELP_IDS, HelpUiKey } from '../help.model'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/day-type-copy.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/day-type-copy.spec.ts index ce142c675..73994db13 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/day-type-copy.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/i18n/day-type-copy.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { HELP_LOCALES } from './index'; import { HelpProse } from '../help.model'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/planning-help.registry.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/planning-help.registry.spec.ts index 26723ab7f..ab5a765d6 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/planning-help.registry.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/planning-help.registry.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { PLANNING_HELP_ENTRIES } from './planning-help.registry'; import { enUS } from './i18n/enUS'; import { HelpEntry, HelpEntryId } from './help.model'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/playwright-tour-seed.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/playwright-tour-seed.spec.ts index 91cb0795a..e4381e22e 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/playwright-tour-seed.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/playwright-tour-seed.spec.ts @@ -1,6 +1,8 @@ +import {describe, it, expect} from 'vitest'; import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { TOUR_STORAGE_KEY } from './services/help-tour.service'; +import { CLIENT_ROOT } from '../testing/source-paths'; /** * The planning page starts its onboarding tours automatically the first time a @@ -15,7 +17,6 @@ import { TOUR_STORAGE_KEY } from './services/help-tour.service'; * exists and names the key HelpTourService actually reads, and the config points * at the file. */ -const CLIENT_ROOT = join(__dirname, '..', '..', '..', '..', '..', '..'); const SEED_RELATIVE = 'playwright/helpers/tour-seen.storage.json'; const SEED_PATH = join(CLIENT_ROOT, SEED_RELATIVE); const CONFIG_PATH = join(CLIENT_ROOT, 'playwright.config.ts'); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-content.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-content.service.spec.ts index 665ac8c33..83b27f6ad 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-content.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-content.service.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { TranslateService } from '@ngx-translate/core'; import { HelpContentService } from './help-content.service'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-panel.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-panel.service.spec.ts index 9fc4697e7..456ef0dcc 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-panel.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-panel.service.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach} from 'vitest'; import { firstValueFrom } from 'rxjs'; import { HelpEntryId } from '../help.model'; import { HelpPanelService } from './help-panel.service'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-search.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-search.service.spec.ts index f93ac26e8..e499349d6 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-search.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-search.service.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { TranslateService } from '@ngx-translate/core'; import { HelpSearchService } from './help-search.service'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-tour.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-tour.service.spec.ts index 300f0fcc7..12fcae5ab 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-tour.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-tour.service.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { TranslateService } from '@ngx-translate/core'; import { firstValueFrom, of } from 'rxjs'; @@ -247,7 +248,7 @@ describe('HelpTourService', () => { }); it('survives localStorage being unavailable', () => { - const getItem = jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + const getItem = vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { throw new Error('blocked'); }); expect(service.hasSeen('page')).toBe(false); @@ -255,7 +256,7 @@ describe('HelpTourService', () => { }); it('survives localStorage rejecting a write', () => { - const setItem = jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + const setItem = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { throw new Error('quota'); }); expect(() => service.markSeen('page')).not.toThrow(); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-visibility.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-visibility.service.spec.ts index ad21eb068..dfa862452 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-visibility.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/help/services/help-visibility.service.spec.ts @@ -1,3 +1,4 @@ +import {describe, it, expect, beforeEach} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { Store } from '@ngrx/store'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts index 9324a96b4..e18e533a9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts @@ -1,3 +1,5 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnAbsenceRequestsService } from './time-planning-pn-absence-requests.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; @@ -5,12 +7,12 @@ import { AbsenceRequestModel, AbsenceRequestDecisionModel } from '../models'; describe('TimePlanningPnAbsenceRequestsService', () => { let service: TimePlanningPnAbsenceRequestsService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - post: jest.fn(), - get: jest.fn(), + post: vi.fn(), + get: vi.fn(), } as any; service = new TimePlanningPnAbsenceRequestsService(mockApiBaseService); @@ -21,7 +23,7 @@ describe('TimePlanningPnAbsenceRequestsService', () => { }); describe('getInbox', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const managerSdkSitId = 1; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -35,9 +37,9 @@ describe('TimePlanningPnAbsenceRequestsService', () => { 'api/time-planning-pn/absence-requests/inbox', { managerSdkSitId } ); - }); + })); - it('should handle empty response', (done) => { + it('should handle empty response', () => new Promise((done) => { const managerSdkSitId = 1; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -46,11 +48,11 @@ describe('TimePlanningPnAbsenceRequestsService', () => { expect(result.model).toEqual([]); done(); }); - }); + })); }); describe('getMine', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const requestedBySdkSitId = 2; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -64,11 +66,11 @@ describe('TimePlanningPnAbsenceRequestsService', () => { 'api/time-planning-pn/absence-requests/mine', { requestedBySdkSitId } ); - }); + })); }); describe('approve', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const requestId = 123; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1, @@ -86,7 +88,7 @@ describe('TimePlanningPnAbsenceRequestsService', () => { 'api/time-planning-pn/absence-requests/123/approve', decisionModel ); - }); + })); it('should construct correct URL with id parameter', () => { const requestId = 456; @@ -106,7 +108,7 @@ describe('TimePlanningPnAbsenceRequestsService', () => { }); describe('reject', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const requestId = 789; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1, @@ -124,9 +126,9 @@ describe('TimePlanningPnAbsenceRequestsService', () => { 'api/time-planning-pn/absence-requests/789/reject', decisionModel ); - }); + })); - it('should handle rejection without comment', (done) => { + it('should handle rejection without comment', () => new Promise((done) => { const requestId = 111; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1 @@ -143,6 +145,6 @@ describe('TimePlanningPnAbsenceRequestsService', () => { 'api/time-planning-pn/absence-requests/111/reject', decisionModel ); - }); + })); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts index 1f24cb280..61c065997 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts @@ -1,17 +1,19 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnBreakPoliciesService } from './time-planning-pn-break-policies.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnBreakPoliciesService', () => { let service: TimePlanningPnBreakPoliciesService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), } as any; service = new TimePlanningPnBreakPoliciesService(mockApiBaseService); @@ -22,7 +24,7 @@ describe('TimePlanningPnBreakPoliciesService', () => { }); describe('getBreakPolicies', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10, @@ -39,9 +41,9 @@ describe('TimePlanningPnBreakPoliciesService', () => { 'api/time-planning-pn/break-policies', { offset: '0', pageSize: '10' } ); - }); + })); - it('should handle pagination', (done) => { + it('should handle pagination', () => new Promise((done) => { const mockRequest = { offset: 20, pageSize: 50, @@ -58,11 +60,11 @@ describe('TimePlanningPnBreakPoliciesService', () => { 'api/time-planning-pn/break-policies', { offset: '20', pageSize: '50' } ); - }); + })); }); describe('getBreakPolicy', () => { - it('should call apiBaseService.get with correct id', (done) => { + it('should call apiBaseService.get with correct id', () => new Promise((done) => { const mockId = 123; const mockResponse = { success: true, @@ -78,11 +80,11 @@ describe('TimePlanningPnBreakPoliciesService', () => { expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies/123' ); - }); + })); }); describe('createBreakPolicy', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockModel = { name: 'New Policy', breakPolicyRules: [] @@ -99,11 +101,11 @@ describe('TimePlanningPnBreakPoliciesService', () => { 'api/time-planning-pn/break-policies', mockModel ); - }); + })); }); describe('updateBreakPolicy', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockModel = { name: 'Updated Policy', breakPolicyRules: [] @@ -120,7 +122,7 @@ describe('TimePlanningPnBreakPoliciesService', () => { 'api/time-planning-pn/break-policies/123', mockModel ); - }); + })); it('should construct correct URL with id parameter', () => { const mockModel = { name: 'Test', breakPolicyRules: [] }; @@ -137,7 +139,7 @@ describe('TimePlanningPnBreakPoliciesService', () => { }); describe('deleteBreakPolicy', () => { - it('should call apiBaseService.delete with correct id', (done) => { + it('should call apiBaseService.delete with correct id', () => new Promise((done) => { const mockId = 123; const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); @@ -150,7 +152,7 @@ describe('TimePlanningPnBreakPoliciesService', () => { expect(mockApiBaseService.delete).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies/123' ); - }); + })); it('should handle different id values', () => { const mockResponse = { success: true }; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts index 795f04f81..ef2315938 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts @@ -1,17 +1,19 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnPayDayTypeRulesService } from './time-planning-pn-pay-day-type-rules.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnPayDayTypeRulesService', () => { let service: TimePlanningPnPayDayTypeRulesService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), } as any; service = new TimePlanningPnPayDayTypeRulesService(mockApiBaseService); @@ -22,7 +24,7 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); describe('getPayDayTypeRules', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payDayTypeRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -36,9 +38,9 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { 'api/time-planning-pn/pay-day-type-rules', { offset: '0', pageSize: '10' } ); - }); + })); - it('should include optional payRuleSetId filter', (done) => { + it('should include optional payRuleSetId filter', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10, payRuleSetId: 5 }; const mockResponse = { success: true, model: { total: 0, payDayTypeRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -51,11 +53,11 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { 'api/time-planning-pn/pay-day-type-rules', { offset: '0', pageSize: '10', payRuleSetId: '5' } ); - }); + })); }); describe('getPayDayTypeRule', () => { - it('should call apiBaseService.get with correct id', (done) => { + it('should call apiBaseService.get with correct id', () => new Promise((done) => { const mockResponse = { success: true, model: { id: 123, payRuleSetId: 1, dayType: 'Weekday' } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -65,11 +67,11 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123'); - }); + })); }); describe('createPayDayTypeRule', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockModel = { payRuleSetId: 1, dayType: 'Weekday' }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); @@ -80,11 +82,11 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules', mockModel); - }); + })); }); describe('updatePayDayTypeRule', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockModel = { id: 123, payRuleSetId: 1, dayType: 'Weekend' }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); @@ -95,11 +97,11 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123', mockModel); - }); + })); }); describe('deletePayDayTypeRule', () => { - it('should call apiBaseService.delete with correct id', (done) => { + it('should call apiBaseService.delete with correct id', () => new Promise((done) => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); @@ -109,6 +111,6 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123'); - }); + })); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts index 9a168dffd..43e816663 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts @@ -1,17 +1,19 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnPayRuleSetsService } from './time-planning-pn-pay-rule-sets.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnPayRuleSetsService', () => { let service: TimePlanningPnPayRuleSetsService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), } as any; service = new TimePlanningPnPayRuleSetsService(mockApiBaseService); @@ -22,7 +24,7 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); describe('getPayRuleSets', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payRuleSets: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -36,11 +38,11 @@ describe('TimePlanningPnPayRuleSetsService', () => { 'api/time-planning-pn/pay-rule-sets', { offset: '0', pageSize: '10' } ); - }); + })); }); describe('getPayRuleSet', () => { - it('should call apiBaseService.get with correct id', (done) => { + it('should call apiBaseService.get with correct id', () => new Promise((done) => { const mockResponse = { success: true, model: { id: 123, name: 'Test', payDayRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -50,11 +52,11 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123'); - }); + })); }); describe('createPayRuleSet', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockModel = { name: 'New RuleSet', payDayRules: [], payDayTypeRules: [] }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); @@ -65,11 +67,11 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets', mockModel); - }); + })); }); describe('updatePayRuleSet', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockModel = { name: 'Updated', payDayRules: [], payDayTypeRules: [] }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); @@ -80,11 +82,11 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123', mockModel); - }); + })); }); describe('deletePayRuleSet', () => { - it('should call apiBaseService.delete with correct id', (done) => { + it('should call apiBaseService.delete with correct id', () => new Promise((done) => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); @@ -94,6 +96,6 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123'); - }); + })); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts index b29197913..248741892 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts @@ -1,17 +1,19 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnPayTierRulesService } from './time-planning-pn-pay-tier-rules.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnPayTierRulesService', () => { let service: TimePlanningPnPayTierRulesService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), } as any; service = new TimePlanningPnPayTierRulesService(mockApiBaseService); @@ -22,7 +24,7 @@ describe('TimePlanningPnPayTierRulesService', () => { }); describe('getPayTierRules', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payTierRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -36,9 +38,9 @@ describe('TimePlanningPnPayTierRulesService', () => { 'api/time-planning-pn/pay-tier-rules', { offset: '0', pageSize: '10' } ); - }); + })); - it('should include optional payDayRuleId filter', (done) => { + it('should include optional payDayRuleId filter', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10, payDayRuleId: 3 }; const mockResponse = { success: true, model: { total: 0, payTierRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -51,11 +53,11 @@ describe('TimePlanningPnPayTierRulesService', () => { 'api/time-planning-pn/pay-tier-rules', { offset: '0', pageSize: '10', payDayRuleId: '3' } ); - }); + })); }); describe('getPayTierRule', () => { - it('should call apiBaseService.get with correct id', (done) => { + it('should call apiBaseService.get with correct id', () => new Promise((done) => { const mockResponse = { success: true, model: { id: 123, payDayRuleId: 1, order: 1, upToSeconds: 3600, payCode: 'CODE1' } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -65,11 +67,11 @@ describe('TimePlanningPnPayTierRulesService', () => { }); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123'); - }); + })); }); describe('createPayTierRule', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockModel = { payDayRuleId: 1, order: 1, upToSeconds: 3600, payCode: 'CODE1' }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); @@ -80,11 +82,11 @@ describe('TimePlanningPnPayTierRulesService', () => { }); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules', mockModel); - }); + })); }); describe('updatePayTierRule', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockModel = { id: 123, payDayRuleId: 1, order: 2, upToSeconds: 7200, payCode: 'CODE2' }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); @@ -95,11 +97,11 @@ describe('TimePlanningPnPayTierRulesService', () => { }); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123', mockModel); - }); + })); }); describe('deletePayTierRule', () => { - it('should call apiBaseService.delete with correct id', (done) => { + it('should call apiBaseService.delete with correct id', () => new Promise((done) => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); @@ -109,6 +111,6 @@ describe('TimePlanningPnPayTierRulesService', () => { }); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123'); - }); + })); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts index 52e063f4c..05d4ff21b 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts @@ -1,17 +1,19 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnPayTimeBandRulesService } from './time-planning-pn-pay-time-band-rules.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnPayTimeBandRulesService', () => { let service: TimePlanningPnPayTimeBandRulesService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), } as any; service = new TimePlanningPnPayTimeBandRulesService(mockApiBaseService); @@ -22,7 +24,7 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); describe('getPayTimeBandRules', () => { - it('should call apiBaseService.get with correct parameters', (done) => { + it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payTimeBandRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -36,9 +38,9 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { 'api/time-planning-pn/pay-time-band-rules', { offset: '0', pageSize: '10' } ); - }); + })); - it('should include optional payDayTypeRuleId filter', (done) => { + it('should include optional payDayTypeRuleId filter', () => new Promise((done) => { const mockRequest = { offset: 0, pageSize: 10, payDayTypeRuleId: 2 }; const mockResponse = { success: true, model: { total: 0, payTimeBandRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -51,11 +53,11 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { 'api/time-planning-pn/pay-time-band-rules', { offset: '0', pageSize: '10', payDayTypeRuleId: '2' } ); - }); + })); }); describe('getPayTimeBandRule', () => { - it('should call apiBaseService.get with correct id', (done) => { + it('should call apiBaseService.get with correct id', () => new Promise((done) => { const mockResponse = { success: true, model: { id: 123, payDayTypeRuleId: 1, startSecondOfDay: 0, endSecondOfDay: 43200, payCode: 'DAY' } @@ -68,11 +70,11 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123'); - }); + })); }); describe('createPayTimeBandRule', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockModel = { payDayTypeRuleId: 1, startSecondOfDay: 0, endSecondOfDay: 43200, payCode: 'DAY', priority: 1 }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); @@ -83,11 +85,11 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules', mockModel); - }); + })); }); describe('updatePayTimeBandRule', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockModel = { id: 123, payDayTypeRuleId: 1, startSecondOfDay: 43200, endSecondOfDay: 86400, payCode: 'NIGHT', priority: 1 }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); @@ -98,11 +100,11 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123', mockModel); - }); + })); }); describe('deletePayTimeBandRule', () => { - it('should call apiBaseService.delete with correct id', (done) => { + it('should call apiBaseService.delete with correct id', () => new Promise((done) => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); @@ -112,6 +114,6 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123'); - }); + })); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts index e4235c0ec..e4b4fd73f 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts @@ -1,3 +1,5 @@ +import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TimePlanningPnPayrollExportService } from './time-planning-pn-payroll-export.service'; @@ -6,14 +8,14 @@ import { of } from 'rxjs'; describe('TimePlanningPnPayrollExportService', () => { let service: TimePlanningPnPayrollExportService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; let httpTestingController: HttpTestingController; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), } as any; TestBed.configureTestingModule({ @@ -37,7 +39,7 @@ describe('TimePlanningPnPayrollExportService', () => { }); describe('preview', () => { - it('should call correct endpoint with start and end params', (done) => { + it('should call correct endpoint with start and end params', () => new Promise((done) => { const mockResponse = { success: true, model: { rows: [], alreadyExportedAt: null } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -49,9 +51,9 @@ describe('TimePlanningPnPayrollExportService', () => { expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/payroll/preview?start=2026-03-20&end=2026-04-19' ); - }); + })); - it('should pass different date ranges correctly', (done) => { + it('should pass different date ranges correctly', () => new Promise((done) => { const mockResponse = { success: true, model: null }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); @@ -63,7 +65,7 @@ describe('TimePlanningPnPayrollExportService', () => { expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/payroll/preview?start=2026-01-01&end=2026-01-31' ); - }); + })); }); describe('exportPayroll', () => { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts index d9463b73e..2c243e073 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts @@ -1,16 +1,18 @@ +import {describe, it, expect, beforeEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TimePlanningPnPlanningsService } from './time-planning-pn-plannings.service'; import { ApiBaseService } from 'src/app/common/services'; import { of } from 'rxjs'; describe('TimePlanningPnPlanningsService', () => { let service: TimePlanningPnPlanningsService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; beforeEach(() => { mockApiBaseService = { - post: jest.fn(), - put: jest.fn(), - get: jest.fn(), + post: vi.fn(), + put: vi.fn(), + get: vi.fn(), } as any; service = new TimePlanningPnPlanningsService(mockApiBaseService); @@ -21,7 +23,7 @@ describe('TimePlanningPnPlanningsService', () => { }); describe('getPlannings', () => { - it('should call apiBaseService.post with correct parameters', (done) => { + it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { const mockRequest = { dateFrom: '2024-01-01', dateTo: '2024-01-07', @@ -42,9 +44,9 @@ describe('TimePlanningPnPlanningsService', () => { 'api/time-planning-pn/plannings/index', mockRequest ); - }); + })); - it('should handle empty response', (done) => { + it('should handle empty response', () => new Promise((done) => { const mockRequest = { dateFrom: '2024-01-01', dateTo: '2024-01-07', @@ -60,11 +62,11 @@ describe('TimePlanningPnPlanningsService', () => { expect(result.model).toEqual([]); done(); }); - }); + })); }); describe('updatePlanning', () => { - it('should call apiBaseService.put with correct parameters', (done) => { + it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { const mockPlanningModel = { id: 123, planHours: 8, @@ -83,7 +85,7 @@ describe('TimePlanningPnPlanningsService', () => { 'api/time-planning-pn/plannings/123', mockPlanningModel ); - }); + })); it('should construct correct URL with id parameter', () => { const mockPlanningModel = { id: 456 } as any; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-working-hours.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-working-hours.service.spec.ts index fa8ba9a60..b990a5db7 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-working-hours.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-working-hours.service.spec.ts @@ -1,3 +1,5 @@ +import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest'; +import type {Mocked} from 'vitest'; import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TimePlanningPnWorkingHoursService } from './time-planning-pn-working-hours.service'; @@ -6,15 +8,15 @@ import { of } from 'rxjs'; describe('TimePlanningPnWorkingHoursService', () => { let service: TimePlanningPnWorkingHoursService; - let mockApiBaseService: jest.Mocked; + let mockApiBaseService: Mocked; let httpTestingController: HttpTestingController; beforeEach(() => { mockApiBaseService = { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - getBlobData: jest.fn().mockReturnValue(of(new Blob())), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + getBlobData: vi.fn().mockReturnValue(of(new Blob())), } as any; TestBed.configureTestingModule({ diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/testing/detect-changes.ts b/eform-client/src/app/plugins/modules/time-planning-pn/testing/detect-changes.ts new file mode 100644 index 000000000..1e552d099 --- /dev/null +++ b/eform-client/src/app/plugins/modules/time-planning-pn/testing/detect-changes.ts @@ -0,0 +1,14 @@ +import { ChangeDetectorRef } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; + +/** + * `fixture.detectChanges()` for specs that change component or service state + * directly between checks. `ng test` runs zoneless, so a check only refreshes + * views marked dirty, while its no-changes pass looks at every view and throws + * NG0100 on the state change it skipped. Marking the component first gives the + * refresh the app gets from zone.js after an event. + */ +export function detectChanges(fixture: ComponentFixture): void { + fixture.componentRef.injector.get(ChangeDetectorRef).markForCheck(); + fixture.detectChanges(); +} diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/testing/mutation-observer.ts b/eform-client/src/app/plugins/modules/time-planning-pn/testing/mutation-observer.ts new file mode 100644 index 000000000..5b4252953 --- /dev/null +++ b/eform-client/src/app/plugins/modules/time-planning-pn/testing/mutation-observer.ts @@ -0,0 +1,24 @@ +import { afterAll, beforeAll } from 'vitest'; + +/** + * CDK overlays watch their pane with a MutationObserver when they detach. The + * `ng test` DOM hands back an observer without `observe`, so closing a + * connected overlay throws. Call inside a `describe` whose specs open and close + * overlays; it installs a no-op observer only when the environment's is unusable. + */ +export function useWorkingMutationObserver(): void { + const original = globalThis.MutationObserver; + beforeAll(() => { + if (typeof original?.prototype?.observe === 'function') { + return; + } + globalThis.MutationObserver = class { + observe(): void { /* no-op */ } + disconnect(): void { /* no-op */ } + takeRecords(): MutationRecord[] { return []; } + } as unknown as typeof MutationObserver; + }); + afterAll(() => { + globalThis.MutationObserver = original; + }); +} diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/testing/page-providers.ts b/eform-client/src/app/plugins/modules/time-planning-pn/testing/page-providers.ts new file mode 100644 index 000000000..cfd1ca6a3 --- /dev/null +++ b/eform-client/src/app/plugins/modules/time-planning-pn/testing/page-providers.ts @@ -0,0 +1,27 @@ +import { DatePipe } from '@angular/common'; +import { EnvironmentProviders, Provider } from '@angular/core'; +import { provideNativeDateAdapter } from '@angular/material/core'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; +import { AppMenuStateService, AuthStateService } from 'src/app/common/store'; +import { SitesService, TitleService } from 'src/app/common/services'; +import { vi } from 'vitest'; + +/** + * What a page component's frontend children need in a test bed: the page + * subheader (eform-new-subheader) injects the menu/title/auth/sites services and + * the router, the toolbar's datepickers need a DateAdapter, and the grid injects + * the DatePipe the app provides at its root. + */ +export function pageProviders(): Array { + return [ + provideRouter([]), + provideNoopAnimations(), + provideNativeDateAdapter(), + DatePipe, + { provide: AppMenuStateService, useValue: {} }, + { provide: TitleService, useValue: { setTitle: vi.fn() } }, + { provide: AuthStateService, useValue: {} }, + { provide: SitesService, useValue: {} }, + ]; +} diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/testing/register-test-locales.ts b/eform-client/src/app/plugins/modules/time-planning-pn/testing/register-test-locales.ts index 416f83d16..34a28889d 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/testing/register-test-locales.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/testing/register-test-locales.ts @@ -30,8 +30,8 @@ import localeUk from '@angular/common/locales/uk'; * The locale data the running app registers, for a test bed. * * In production `src/main.ts` registers exactly this set before bootstrap, so - * every DatePipe call in the plugin can format in the user's language. A Jest - * bed never runs main.ts, and the host's `src/setup-jest.ts` (which is the only + * every DatePipe call in the plugin can format in the user's language. A test + * bed never runs main.ts, and the host's `src/test-setup.ts` (which is the only * truly global hook, and lives in eform-angular-frontend, not here) does not * register any of it — so a pipe asked for a non-English locale throws * NG0701 "Missing locale data". Angular resolves plain `en` from its built-in diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/testing/source-paths.ts b/eform-client/src/app/plugins/modules/time-planning-pn/testing/source-paths.ts new file mode 100644 index 000000000..9c843ac6f --- /dev/null +++ b/eform-client/src/app/plugins/modules/time-planning-pn/testing/source-paths.ts @@ -0,0 +1,10 @@ +import { join } from 'path'; + +/** + * Specs that read source files from disk. Angular's unit-test builder bundles + * each spec, so `__dirname` no longer points next to the spec. `ng test` runs + * from the host's eform-client directory (locally and in CI), so paths are + * resolved from the working directory instead. + */ +export const CLIENT_ROOT = process.cwd(); +export const MODULE_ROOT = join(CLIENT_ROOT, 'src', 'app', 'plugins', 'modules', 'time-planning-pn'); From e1ed059c70b2e42aeff65b15c2cf25a89b36658b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 09:24:28 +0200 Subject: [PATCH 4/7] fix: keep a feature-scoped MatDialog where shared modules used to provide it Co-Authored-By: Claude Opus 5.5 --- .../modules/absence-requests/absence-requests.module.ts | 2 ++ .../modules/registration-devices/registration-devices.module.ts | 2 ++ .../modules/request-history/request-history.module.ts | 2 ++ .../plugins/modules/time-planning-pn/time-planning-pn.module.ts | 2 ++ 4 files changed, 8 insertions(+) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts index 9be5a2825..b26f49bac 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/absence-requests/absence-requests.module.ts @@ -1,4 +1,5 @@ import { NgModule } from '@angular/core'; +import {MatDialogModule} from '@angular/material/dialog'; import { AbsenceRequestsRouting } from './absence-requests.routing'; import { AbsenceRequestsContainerComponent, @@ -9,6 +10,7 @@ import { @NgModule({ imports: [ + MatDialogModule, AbsenceRequestsRouting, AbsenceRequestsContainerComponent, AbsenceRequestsTableComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts index 0000dd7ce..f75858cf2 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/registration-devices/registration-devices.module.ts @@ -1,4 +1,5 @@ import {NgModule} from '@angular/core'; +import {MatDialogModule} from '@angular/material/dialog'; import { RegistrationDevicesContainerComponent, RegistrationDevicesCreateModalComponent, @@ -12,6 +13,7 @@ import { @NgModule({ imports: [ + MatDialogModule, RegistrationDevicesRouting, RegistrationDevicesTableComponent, RegistrationDevicesContainerComponent, diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts index 6be7c419c..d3d83ad02 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/modules/request-history/request-history.module.ts @@ -1,4 +1,5 @@ import { NgModule } from '@angular/core'; +import {MatDialogModule} from '@angular/material/dialog'; import { RequestHistoryRouting } from './request-history.routing'; import { RequestHistoryPageComponent @@ -6,6 +7,7 @@ import { @NgModule({ imports: [ + MatDialogModule, RequestHistoryRouting, RequestHistoryPageComponent ], diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts b/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts index 3cc907299..771b6a5fa 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/time-planning-pn.module.ts @@ -1,4 +1,5 @@ import {NgModule} from '@angular/core'; +import {MatDialogModule} from '@angular/material/dialog'; import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field'; import {TimePlanningPnRouting} from './time-planning-pn.routing'; import {TimePlanningPnLayoutComponent} from './layouts'; @@ -22,6 +23,7 @@ import {HelpTourComponent} from './help/components/help-tour/help-tour.component @NgModule({ imports: [ + MatDialogModule, TimePlanningPnRouting, TimePlanningPnLayoutComponent, TimePlanningSettingsComponent, From 0c240ae5f976f0963c83c10a4768ed1b671e0d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 11:44:08 +0200 Subject: [PATCH 5/7] fix: target the plugins page action menu by id prefix The Angular 21 frontend renders the installed-plugins row menu as id="actionMenu{{i}}", so every '#actionMenu' selector in the plugin's e2e tests timed out, the plugin was never enabled, and every later test in each shard failed. Match [id^="actionMenu"] instead. Co-Authored-By: Claude Opus 5.5 --- .../a/time-planning-enabled.spec.cy.ts | 4 +- .../b/activate-plugin.spec.cy.ts | 4 +- .../b/dashboard-assert.spec.cy.ts | 2 +- .../b/time-planning-settings.spec.cy.ts | 16 +++--- .../c/activate-plugin.spec.cy.ts | 4 +- .../c/dashboard-edit-a.spec.cy.ts | 2 +- .../d/activate-plugin.spec.cy.ts | 4 +- .../e/activate-plugin.spec.cy.ts | 4 +- .../f/activate-plugin.spec.cy.ts | 4 +- .../g/activate-plugin.spec.cy.ts | 4 +- .../h/activate-plugin.spec.cy.ts | 4 +- .../i/activate-plugin.spec.cy.ts | 4 +- .../j/activate-plugin.spec.cy.ts | 4 +- .../j/dashboard-edit-a.spec.cy.ts | 4 +- .../k/activate-plugin.spec.cy.ts | 4 +- .../k/leave-policies.spec.cy.ts | 2 +- .../l/activate-plugin.spec.cy.ts | 4 +- .../m/activate-plugin.spec.cy.ts | 4 +- .../m/manager-assignment.spec.cy.ts | 10 ++-- .../n/activate-plugin.spec.cy.ts | 4 +- .../o/activate-plugin.spec.cy.ts | 4 +- .../application-settings.plugins-page.spec.ts | 6 +-- .../a/time-planning-enabled.spec.ts | 4 +- .../b/activate-plugin.spec.ts | 4 +- .../b/dashboard-assert.spec.ts | 6 +-- .../b/time-planning-settings.spec.ts | 50 +++++++++---------- .../c/activate-plugin.spec.ts | 4 +- .../c/dashboard-edit-a.spec.ts | 6 +-- .../d/activate-plugin.spec.ts | 4 +- .../e/activate-plugin.spec.ts | 4 +- .../f/activate-plugin.spec.ts | 4 +- .../g/activate-plugin.spec.ts | 4 +- .../h/activate-plugin.spec.ts | 4 +- .../i/activate-plugin.spec.ts | 4 +- .../j/activate-plugin.spec.ts | 4 +- .../j/dashboard-edit-a.spec.ts | 6 +-- .../j1m/dashboard-edit-a.spec.ts | 6 +-- .../k/activate-plugin.spec.ts | 4 +- .../time-planning-pn/k/leave-policies.spec.ts | 6 +-- .../l/activate-plugin.spec.ts | 4 +- .../m/activate-plugin.spec.ts | 4 +- .../m/manager-assignment.spec.ts | 6 +-- .../n/activate-plugin.spec.ts | 4 +- .../o/activate-plugin.spec.ts | 4 +- .../p/activate-plugin.spec.ts | 4 +- .../time-planning-pn/p/payroll-export.spec.ts | 6 +-- .../q/activate-plugin.spec.ts | 4 +- .../r/activate-plugin.spec.ts | 4 +- .../s/activate-plugin.spec.ts | 4 +- 49 files changed, 135 insertions(+), 135 deletions(-) diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.cy.ts index 6cef7974c..d44d7be45 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.cy.ts @@ -15,7 +15,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -36,7 +36,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/b/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/b/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/b/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/b/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.cy.ts index 5a3611ed0..594b86518 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.cy.ts @@ -426,7 +426,7 @@ describe('Dashboard assert', () => { pluginPage.Navbar.goToPluginsPage(); cy.task('log', '[Folder b - Dashboard Assert] Clicking action menu'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.task('log', '[Folder b - Dashboard Assert] Setting up intercept for settings-get'); diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.cy.ts index d41dadbf1..f7b8f1a63 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.cy.ts @@ -15,7 +15,7 @@ describe('Enable Backend Config plugin', () => { it('should validate default Time registration plugin settings', () => { cy.task('log', '[Folder b - Settings] ========== Test: Validate default Time registration plugin settings =========='); cy.task('log', '[Folder b - Settings] Clicking action menu'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.task('log', '[Folder b - Settings] Setting up intercept for settings-get'); @@ -138,7 +138,7 @@ describe('Enable Backend Config plugin', () => { it('should activate auto calculation break times', () => { cy.task('log', '[Folder b - Settings] ========== Test: Activate auto calculation break times =========='); cy.task('log', '[Folder b - Settings] Clicking action menu'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.task('log', '[Folder b - Settings] Setting up intercept for settings-get'); @@ -300,7 +300,7 @@ describe('Enable Backend Config plugin', () => { pluginPage.Navbar.goToPluginsPage(); cy.task('log', '[Folder b - Settings] Clicking action menu to verify settings'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.task('log', '[Folder b - Settings] Setting up intercept for settings-get'); @@ -363,7 +363,7 @@ describe('Enable Backend Config plugin', () => { cy.task('log', '[Folder b - Settings] ========== Test: Toggle GPS and Snapshot settings =========='); // Navigate to settings page cy.task('log', '[Folder b - Settings] Clicking action menu'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.task('log', '[Folder b - Settings] Setting up intercept for settings-get'); @@ -401,7 +401,7 @@ describe('Enable Backend Config plugin', () => { cy.task('log', '[Folder b - Settings] Reloading page to verify GPS persistence'); cy.visit('http://localhost:4200'); pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get-2'); @@ -437,7 +437,7 @@ describe('Enable Backend Config plugin', () => { // Reload page and verify GPS is still OFF cy.visit('http://localhost:4200'); pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get-3'); @@ -472,7 +472,7 @@ describe('Enable Backend Config plugin', () => { cy.task('log', '[Folder b - Settings] Reloading page to verify Snapshot persistence'); cy.visit('http://localhost:4200'); pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get-4'); @@ -508,7 +508,7 @@ describe('Enable Backend Config plugin', () => { cy.task('log', '[Folder b - Settings] Reloading page to verify Snapshot is still OFF'); cy.visit('http://localhost:4200'); pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get-5'); diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/c/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/c/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/c/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/c/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.cy.ts index 81280f7ef..49c42d33d 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.cy.ts @@ -237,7 +237,7 @@ describe('Dashboard edit values', () => { it('should enable auto break calculations with empty values', () => { pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get'); diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/d/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/d/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/d/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/d/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/e/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/e/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/e/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/e/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/f/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/f/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/f/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/f/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/g/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/g/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/g/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/g/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/h/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/h/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/h/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/h/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/i/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/i/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/i/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/i/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/j/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/j/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/j/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/j/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.cy.ts index b38473b26..29873d1e6 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.cy.ts @@ -13,7 +13,7 @@ describe('Dashboard edit values', () => { cy.task('log', '[Folder j - Auto Break Settings] Navigating to plugins page'); pluginPage.Navbar.goToPluginsPage(); cy.task('log', '[Folder j - Auto Break Settings] Clicking action menu'); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get'); @@ -231,7 +231,7 @@ describe('Dashboard edit values', () => { // cy.wait(500); // // pluginPage.Navbar.goToPluginsPage(); - // cy.get('#actionMenu') + // cy.get('[id^="actionMenu"]') // .should('be.visible') // .click({ force: true }); // cy.intercept('GET', '**/api/time-planning-pn/settings').as('settings-get'); diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/k/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/k/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/k/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/k/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/k/leave-policies.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/k/leave-policies.spec.cy.ts index 454ac31c0..371d49770 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/k/leave-policies.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/k/leave-policies.spec.cy.ts @@ -24,7 +24,7 @@ describe('Time Planning - Leave policies', () => { loginPage.login(); pluginPage.Navbar.goToPluginsPage(); - cy.get('#actionMenu') + cy.get('[id^="actionMenu"]') .scrollIntoView().should('be.visible') .click({ force: true }); diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/l/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/l/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/l/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/l/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/m/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/m/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/m/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/m/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/m/manager-assignment.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/m/manager-assignment.spec.cy.ts index 77285dd72..edc6d5479 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/m/manager-assignment.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/m/manager-assignment.spec.cy.ts @@ -14,11 +14,11 @@ describe('Time Planning - Manager Assignment', () => { // pluginPage.Navbar.goToPluginsPage(); // cy.get('body').then(($body) => { - // if ($body.find('#actionMenu').length === 0) { + // if ($body.find('[id^="actionMenu"]').length === 0) { // throw new Error('Plugin menu not available'); // } // - // cy.get('#actionMenu') + // cy.get('[id^="actionMenu"]') // .scrollIntoView().should('be.visible') // .click({ force: true }); // @@ -211,7 +211,7 @@ describe('Time Planning - Manager Assignment', () => { */ it('should toggle IsManager on and off and persist the state', () => { cy.get('body').then(($body) => { - if ($body.find('#actionMenu').length === 0) { + if ($body.find('[id^="actionMenu"]').length === 0) { cy.task('log', '[Time Planning Tests] Plugin menu not available - skipping test'); return; } @@ -305,7 +305,7 @@ describe('Time Planning - Manager Assignment', () => { */ it('should show tags field when manager checkbox is on and handle random text without errors', () => { cy.get('body').then(($body) => { - if ($body.find('#actionMenu').length === 0) { + if ($body.find('[id^="actionMenu"]').length === 0) { cy.task('log', '[Time Planning Tests] Plugin menu not available - skipping test'); return; } @@ -439,7 +439,7 @@ describe('Time Planning - Manager Assignment', () => { cy.task('log', `[Test 3] ========== Starting test - Create tag and persist selection at ${new Date().toISOString()} ==========`); cy.get('body').then(($body) => { - if ($body.find('#actionMenu').length === 0) { + if ($body.find('[id^="actionMenu"]').length === 0) { cy.task('log', '[Test 3] Plugin menu not available - skipping test'); return; } diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/n/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/n/activate-plugin.spec.cy.ts index e41ac4fdf..ac9f81814 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/n/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/n/activate-plugin.spec.cy.ts @@ -12,7 +12,7 @@ describe('Enable Backend Config plugin', () => { const pluginName = 'Microting Time Planning Plugin'; // Open action menu for the plugin - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); // Click the status button inside the menu to enable the plugin @@ -33,7 +33,7 @@ describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status - cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); + cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); cy.wait(500); cy.get('#plugin-status-button0') diff --git a/eform-client/cypress/e2e/plugins/time-planning-pn/o/activate-plugin.spec.cy.ts b/eform-client/cypress/e2e/plugins/time-planning-pn/o/activate-plugin.spec.cy.ts index d0c83d3bb..d31d55f28 100644 --- a/eform-client/cypress/e2e/plugins/time-planning-pn/o/activate-plugin.spec.cy.ts +++ b/eform-client/cypress/e2e/plugins/time-planning-pn/o/activate-plugin.spec.cy.ts @@ -16,7 +16,7 @@ // const pluginName = 'Microting Time Planning Plugin'; // // Open action menu for the plugin -// cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); +// cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); // cy.wait(500); // // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ // // Verify the plugin is enabled by checking the status // // Re-open the action menu to check the status -// cy.contains('.mat-mdc-row', pluginName).first().find('#actionMenu').click(); +// cy.contains('.mat-mdc-row', pluginName).first().find('[id^="actionMenu"]').click(); // cy.wait(500); // cy.get('#plugin-status-button0') diff --git a/eform-client/e2e/Tests/time-planning-settings/application-settings.plugins-page.spec.ts b/eform-client/e2e/Tests/time-planning-settings/application-settings.plugins-page.spec.ts index fe710bd72..d3992ff0e 100644 --- a/eform-client/e2e/Tests/time-planning-settings/application-settings.plugins-page.spec.ts +++ b/eform-client/e2e/Tests/time-planning-settings/application-settings.plugins-page.spec.ts @@ -19,7 +19,7 @@ describe('Application settings page - site header section', function () { expect(backendPlugin.version).equal('1.0.0.0'); // Open action menu to check status - const actionMenuBtn = await $('#action-items-0').$('#actionMenu'); + const actionMenuBtn = await $('#action-items-0').$('[id^="actionMenu"]'); await actionMenuBtn.waitForDisplayed({ timeout: 40000 }); await actionMenuBtn.waitForClickable({ timeout: 40000 }); await actionMenuBtn.click(); @@ -38,7 +38,7 @@ describe('Application settings page - site header section', function () { it('should activate the plugin', async () => { // Open action menu - const actionMenuBtn = await $('#action-items-0').$('#actionMenu'); + const actionMenuBtn = await $('#action-items-0').$('[id^="actionMenu"]'); await actionMenuBtn.waitForDisplayed({ timeout: 40000 }); await actionMenuBtn.waitForClickable({ timeout: 40000 }); await actionMenuBtn.click(); @@ -70,7 +70,7 @@ describe('Application settings page - site header section', function () { expect(backendPlugin.version).equal('1.0.0.0'); // Open action menu to check new status - const actionMenuBtn2 = await $('#action-items-0').$('#actionMenu'); + const actionMenuBtn2 = await $('#action-items-0').$('[id^="actionMenu"]'); await actionMenuBtn2.waitForDisplayed({ timeout: 40000 }); await actionMenuBtn2.waitForClickable({ timeout: 40000 }); await actionMenuBtn2.click(); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.ts index 62ea839ee..6a17d2a4f 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/a/time-planning-enabled.spec.ts @@ -16,7 +16,7 @@ test.describe('Enable Time Planning plugin', () => { const pluginName = 'Microting Time Planning Plugin'; await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await page.locator('#plugin-status-button0').scrollIntoViewIfNeeded(); @@ -34,7 +34,7 @@ test.describe('Enable Time Planning plugin', () => { await new PluginPage(page).Navbar.goToPluginsPage(); await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/b/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/b/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/b/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/b/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.ts index a13320d02..9be00c5ad 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-assert.spec.ts @@ -370,9 +370,9 @@ test.describe('Dashboard assert', () => { await pluginPage.Navbar.goToPluginsPage(); - await page.locator('#actionMenu').scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await page.locator('[id^="actionMenu"]').scrollIntoViewIfNeeded(); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const [settingsResp] = await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.ts index eef8bef20..a910928ff 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/b/time-planning-settings.spec.ts @@ -56,10 +56,10 @@ test.describe('Enable Backend Config plugin', () => { }); test('should validate default Time registration plugin settings', async ({ page }) => { - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const [settingsResponse] = await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), @@ -151,10 +151,10 @@ test.describe('Enable Backend Config plugin', () => { }); test('should activate auto calculation break times', async ({ page }) => { - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const [settingsResponse] = await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), @@ -261,10 +261,10 @@ test.describe('Enable Backend Config plugin', () => { await page.goto('http://localhost:4200'); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const [settingsResponse2] = await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), @@ -305,10 +305,10 @@ test.describe('Enable Backend Config plugin', () => { test('should toggle GPS and Snapshot settings and persist changes', async ({ page }) => { // Navigate to settings page - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const [settingsResponse] = await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), @@ -332,10 +332,10 @@ test.describe('Enable Backend Config plugin', () => { // Reload page and verify GPS is still ON await page.goto('http://localhost:4200'); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), page.locator('#plugin-settings-link0').click(), @@ -362,10 +362,10 @@ test.describe('Enable Backend Config plugin', () => { // Reload page and verify GPS is still OFF await page.goto('http://localhost:4200'); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), page.locator('#plugin-settings-link0').click(), @@ -390,10 +390,10 @@ test.describe('Enable Backend Config plugin', () => { // Reload page and verify Snapshot is still ON await page.goto('http://localhost:4200'); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), page.locator('#plugin-settings-link0').click(), @@ -419,10 +419,10 @@ test.describe('Enable Backend Config plugin', () => { // Reload page and verify Snapshot is still OFF await page.goto('http://localhost:4200'); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); await Promise.all([ page.waitForResponse(r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET'), page.locator('#plugin-settings-link0').click(), @@ -445,7 +445,7 @@ test.describe('Enable Backend Config plugin', () => { const isPath = (url: string, path: string) => new URL(url).pathname === path; const SETTINGS = '/api/time-planning-pn/settings'; const PAYROLL_SETTINGS = '/api/time-planning-pn/payroll/settings'; - const actionMenu = page.locator('#actionMenu'); + const actionMenu = page.locator('[id^="actionMenu"]'); const gpsToggle = page.locator('#gpsEnabledToggle'); const gpsChecked = gpsToggle.locator('div button'); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/c/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/c/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/c/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/c/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.ts index 0a27a557b..1a4afd6ff 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/c/dashboard-edit-a.spec.ts @@ -239,10 +239,10 @@ test.describe('Dashboard edit values', () => { test('should enable auto break calculations with empty values', async ({ page }) => { const pluginPage = new PluginPage(page); await pluginPage.Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const settingsGetPromise = page.waitForResponse( r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET' diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/d/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/d/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/d/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/d/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/e/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/e/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/e/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/e/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/f/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/f/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/f/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/f/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/g/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/g/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/g/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/g/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/h/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/h/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/h/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/h/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/i/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/i/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/i/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/i/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/j/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/j/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/j/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/j/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.ts index e21504546..a34b350c1 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/j/dashboard-edit-a.spec.ts @@ -16,9 +16,9 @@ test.describe('Dashboard edit values', () => { await new LoginPage(page).login(); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu').scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await page.locator('[id^="actionMenu"]').scrollIntoViewIfNeeded(); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const settingsGetPromise = page.waitForResponse( r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET' diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/j1m/dashboard-edit-a.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/j1m/dashboard-edit-a.spec.ts index 0689c1830..0d0ccf36a 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/j1m/dashboard-edit-a.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/j1m/dashboard-edit-a.spec.ts @@ -49,9 +49,9 @@ test.describe('Dashboard edit values (j1m, flag-on, global values round-trip)', await new LoginPage(page).login(); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu').scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await page.locator('[id^="actionMenu"]').scrollIntoViewIfNeeded(); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const settingsGetPromise = page.waitForResponse( r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET' diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/k/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/k/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/k/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/k/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/k/leave-policies.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/k/leave-policies.spec.ts index 532067f02..bb13f9111 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/k/leave-policies.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/k/leave-policies.spec.ts @@ -25,10 +25,10 @@ test.describe('Time Planning - Leave policies', () => { const pluginPage = new PluginPage(page); await pluginPage.Navbar.goToPluginsPage(); - await page.locator('#actionMenu') + await page.locator('[id^="actionMenu"]') .scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); const planningsIndexResponse = page.waitForResponse( (resp) => resp.url().includes('/api/time-planning-pn/plannings/index') && resp.status() === 200 diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/l/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/l/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/l/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/l/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/m/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/m/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/m/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/m/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/m/manager-assignment.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/m/manager-assignment.spec.ts index 6d3ad53c8..534597595 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/m/manager-assignment.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/m/manager-assignment.spec.ts @@ -137,7 +137,7 @@ test.describe('Time Planning - Manager Assignment', () => { * Test 1: Check/uncheck IsManager and verify it persists */ test('should toggle IsManager on and off and persist the state', async ({ page }) => { - if (await page.locator('#actionMenu').count() === 0) { + if (await page.locator('[id^="actionMenu"]').count() === 0) { return; } @@ -202,7 +202,7 @@ test.describe('Time Planning - Manager Assignment', () => { * Test 2: Verify tags field shows when manager is on, test with random text */ test('should show tags field when manager checkbox is on and handle random text without errors', async ({ page }) => { - if (await page.locator('#actionMenu').count() === 0) { + if (await page.locator('[id^="actionMenu"]').count() === 0) { return; } @@ -270,7 +270,7 @@ test.describe('Time Planning - Manager Assignment', () => { * Test 3: Create a tag, use it in assigned-site-modal, and verify it persists */ test('should create a tag, use it in assigned-site-modal, and persist the selection', async ({ page }) => { - if (await page.locator('#actionMenu').count() === 0) { + if (await page.locator('[id^="actionMenu"]').count() === 0) { return; } diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/n/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/n/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/n/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/n/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/o/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/o/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/o/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/o/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/p/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/p/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/p/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/p/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/p/payroll-export.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/p/payroll-export.spec.ts index aad92ab8d..5c9f33bc6 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/p/payroll-export.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/p/payroll-export.spec.ts @@ -15,9 +15,9 @@ test.describe('Payroll export', () => { r => r.url().includes('/api/time-planning-pn/settings') && r.request().method() === 'GET' ); await new PluginPage(page).Navbar.goToPluginsPage(); - await page.locator('#actionMenu').scrollIntoViewIfNeeded(); - await expect(page.locator('#actionMenu')).toBeVisible(); - await page.locator('#actionMenu').click({ force: true }); + await page.locator('[id^="actionMenu"]').scrollIntoViewIfNeeded(); + await expect(page.locator('[id^="actionMenu"]')).toBeVisible(); + await page.locator('[id^="actionMenu"]').click({ force: true }); await page.locator('#plugin-settings-link0').click(); await settingsGetPromise; await page.waitForTimeout(2000); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/q/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/q/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/q/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/q/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/r/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/s/activate-plugin.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/s/activate-plugin.spec.ts index 05e9ede65..c86c5f63d 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/s/activate-plugin.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/s/activate-plugin.spec.ts @@ -15,7 +15,7 @@ test.describe('Enable Backend Config plugin', () => { // Open action menu for the plugin await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); // Click the status button inside the menu to enable the plugin @@ -37,7 +37,7 @@ test.describe('Enable Backend Config plugin', () => { // Verify the plugin is enabled by checking the status // Re-open the action menu to check the status await page.locator('.mat-mdc-row').filter({ hasText: pluginName }).first() - .locator('#actionMenu').click(); + .locator('[id^="actionMenu"]').click(); await page.waitForTimeout(500); await expect( From 90efd7d21abdbb101f1040c75c91263cda9475fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 11:46:18 +0200 Subject: [PATCH 6/7] test: assert before resolving in converted service specs The Jest-to-Vitest conversion wrapped `done` specs in new Promise, but of(...) emits synchronously, so done() ran before the trailing toHaveBeenCalledWith and a failure there was swallowed. Await firstValueFrom(...) and assert afterwards; the two setTimeout-based dialog specs now await the delay before asserting. Co-Authored-By: Claude Opus 5.5 --- .../download-excel-dialog.component.spec.ts | 20 +++--- ...anning-pn-absence-requests.service.spec.ts | 62 ++++++++----------- ...planning-pn-break-policies.service.spec.ts | 62 ++++++++----------- ...ning-pn-pay-day-type-rules.service.spec.ts | 60 +++++++----------- ...-planning-pn-pay-rule-sets.service.spec.ts | 52 +++++++--------- ...planning-pn-pay-tier-rules.service.spec.ts | 60 +++++++----------- ...ing-pn-pay-time-band-rules.service.spec.ts | 60 +++++++----------- ...planning-pn-payroll-export.service.spec.ts | 22 +++---- ...time-planning-pn-plannings.service.spec.ts | 32 ++++------ 9 files changed, 173 insertions(+), 257 deletions(-) diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts index 5a8f44df6..856bb2a98 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/components/plannings/time-planning-actions/download-excel/download-excel-dialog.component.spec.ts @@ -189,7 +189,7 @@ describe('DownloadExcelDialogComponent', () => { }); }); - it('should show error toast when download fails', () => new Promise((done) => { + it('should show error toast when download fails', async () => { mockWorkingHoursService.downloadReport.mockReturnValue( throwError(() => new Error('Download failed')) ); @@ -197,11 +197,9 @@ describe('DownloadExcelDialogComponent', () => { component.onDownloadExcelReport(); // Wait a bit for async operations - setTimeout(() => { - expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); - done(); - }, 100); - })); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); + }); }); describe('Excel Report All Workers Download', () => { @@ -234,7 +232,7 @@ describe('DownloadExcelDialogComponent', () => { expect('tagIds' in callArgs).toBe(false); }); - it('should show error toast when download all workers fails', () => new Promise((done) => { + it('should show error toast when download all workers fails', async () => { mockWorkingHoursService.downloadReportAllWorkers.mockReturnValue( throwError(() => new Error('Download failed')) ); @@ -242,11 +240,9 @@ describe('DownloadExcelDialogComponent', () => { component.onDownloadExcelReportAllWorkers(); // Wait a bit for async operations - setTimeout(() => { - expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); - done(); - }, 100); - })); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(mockToastrService.error).toHaveBeenCalledWith('Error downloading report'); + }); it('should not include siteId in all workers report model', () => { const mockBlob = new Blob(['test'], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts index e18e533a9..208c1add9 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-absence-requests.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnAbsenceRequestsService } from './time-planning-pn-absence-requests.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; import { AbsenceRequestModel, AbsenceRequestDecisionModel } from '../models'; describe('TimePlanningPnAbsenceRequestsService', () => { @@ -23,54 +23,48 @@ describe('TimePlanningPnAbsenceRequestsService', () => { }); describe('getInbox', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const managerSdkSitId = 1; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getInbox(managerSdkSitId).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getInbox(managerSdkSitId)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/absence-requests/inbox', { managerSdkSitId } ); - })); + }); - it('should handle empty response', () => new Promise((done) => { + it('should handle empty response', async () => { const managerSdkSitId = 1; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getInbox(managerSdkSitId).subscribe(result => { - expect(result.model).toEqual([]); - done(); - }); - })); + const result = await firstValueFrom(service.getInbox(managerSdkSitId)); + expect(result.model).toEqual([]); + }); }); describe('getMine', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const requestedBySdkSitId = 2; const mockResponse = { success: true, model: [] as AbsenceRequestModel[] }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getMine(requestedBySdkSitId).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getMine(requestedBySdkSitId)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/absence-requests/mine', { requestedBySdkSitId } ); - })); + }); }); describe('approve', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const requestId = 123; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1, @@ -79,16 +73,14 @@ describe('TimePlanningPnAbsenceRequestsService', () => { const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.approve(requestId, decisionModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.approve(requestId, decisionModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith( 'api/time-planning-pn/absence-requests/123/approve', decisionModel ); - })); + }); it('should construct correct URL with id parameter', () => { const requestId = 456; @@ -108,7 +100,7 @@ describe('TimePlanningPnAbsenceRequestsService', () => { }); describe('reject', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const requestId = 789; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1, @@ -117,18 +109,16 @@ describe('TimePlanningPnAbsenceRequestsService', () => { const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.reject(requestId, decisionModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.reject(requestId, decisionModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith( 'api/time-planning-pn/absence-requests/789/reject', decisionModel ); - })); + }); - it('should handle rejection without comment', () => new Promise((done) => { + it('should handle rejection without comment', async () => { const requestId = 111; const decisionModel: AbsenceRequestDecisionModel = { managerSdkSitId: 1 @@ -136,15 +126,13 @@ describe('TimePlanningPnAbsenceRequestsService', () => { const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.reject(requestId, decisionModel).subscribe(result => { - expect(result.success).toBe(true); - done(); - }); + const result = await firstValueFrom(service.reject(requestId, decisionModel)); + expect(result.success).toBe(true); expect(mockApiBaseService.post).toHaveBeenCalledWith( 'api/time-planning-pn/absence-requests/111/reject', decisionModel ); - })); + }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts index 61c065997..25946ec9a 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-break-policies.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnBreakPoliciesService } from './time-planning-pn-break-policies.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnBreakPoliciesService', () => { let service: TimePlanningPnBreakPoliciesService; @@ -24,7 +24,7 @@ describe('TimePlanningPnBreakPoliciesService', () => { }); describe('getBreakPolicies', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const mockRequest = { offset: 0, pageSize: 10, @@ -32,18 +32,16 @@ describe('TimePlanningPnBreakPoliciesService', () => { const mockResponse = { success: true, model: { total: 0, breakPolicies: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getBreakPolicies(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getBreakPolicies(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies', { offset: '0', pageSize: '10' } ); - })); + }); - it('should handle pagination', () => new Promise((done) => { + it('should handle pagination', async () => { const mockRequest = { offset: 20, pageSize: 50, @@ -51,20 +49,18 @@ describe('TimePlanningPnBreakPoliciesService', () => { const mockResponse = { success: true, model: { total: 100, breakPolicies: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getBreakPolicies(mockRequest).subscribe(result => { - expect(result.model.total).toEqual(100); - done(); - }); + const result = await firstValueFrom(service.getBreakPolicies(mockRequest)); + expect(result.model.total).toEqual(100); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies', { offset: '20', pageSize: '50' } ); - })); + }); }); describe('getBreakPolicy', () => { - it('should call apiBaseService.get with correct id', () => new Promise((done) => { + it('should call apiBaseService.get with correct id', async () => { const mockId = 123; const mockResponse = { success: true, @@ -72,19 +68,17 @@ describe('TimePlanningPnBreakPoliciesService', () => { }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getBreakPolicy(mockId).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getBreakPolicy(mockId)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies/123' ); - })); + }); }); describe('createBreakPolicy', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockModel = { name: 'New Policy', breakPolicyRules: [] @@ -92,20 +86,18 @@ describe('TimePlanningPnBreakPoliciesService', () => { const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.createBreakPolicy(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.createBreakPolicy(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies', mockModel ); - })); + }); }); describe('updateBreakPolicy', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockModel = { name: 'Updated Policy', breakPolicyRules: [] @@ -113,16 +105,14 @@ describe('TimePlanningPnBreakPoliciesService', () => { const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updateBreakPolicy(123, mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updateBreakPolicy(123, mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies/123', mockModel ); - })); + }); it('should construct correct URL with id parameter', () => { const mockModel = { name: 'Test', breakPolicyRules: [] }; @@ -139,20 +129,18 @@ describe('TimePlanningPnBreakPoliciesService', () => { }); describe('deleteBreakPolicy', () => { - it('should call apiBaseService.delete with correct id', () => new Promise((done) => { + it('should call apiBaseService.delete with correct id', async () => { const mockId = 123; const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); - service.deleteBreakPolicy(mockId).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.deleteBreakPolicy(mockId)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.delete).toHaveBeenCalledWith( 'api/time-planning-pn/break-policies/123' ); - })); + }); it('should handle different id values', () => { const mockResponse = { success: true }; diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts index ef2315938..bc6829346 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-day-type-rules.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnPayDayTypeRulesService } from './time-planning-pn-pay-day-type-rules.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPayDayTypeRulesService', () => { let service: TimePlanningPnPayDayTypeRulesService; @@ -24,93 +24,81 @@ describe('TimePlanningPnPayDayTypeRulesService', () => { }); describe('getPayDayTypeRules', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payDayTypeRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayDayTypeRules(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayDayTypeRules(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-day-type-rules', { offset: '0', pageSize: '10' } ); - })); + }); - it('should include optional payRuleSetId filter', () => new Promise((done) => { + it('should include optional payRuleSetId filter', async () => { const mockRequest = { offset: 0, pageSize: 10, payRuleSetId: 5 }; const mockResponse = { success: true, model: { total: 0, payDayTypeRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayDayTypeRules(mockRequest).subscribe(result => { - done(); - }); + await firstValueFrom(service.getPayDayTypeRules(mockRequest)); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-day-type-rules', { offset: '0', pageSize: '10', payRuleSetId: '5' } ); - })); + }); }); describe('getPayDayTypeRule', () => { - it('should call apiBaseService.get with correct id', () => new Promise((done) => { + it('should call apiBaseService.get with correct id', async () => { const mockResponse = { success: true, model: { id: 123, payRuleSetId: 1, dayType: 'Weekday' } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayDayTypeRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayDayTypeRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123'); - })); + }); }); describe('createPayDayTypeRule', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockModel = { payRuleSetId: 1, dayType: 'Weekday' }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.createPayDayTypeRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.createPayDayTypeRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules', mockModel); - })); + }); }); describe('updatePayDayTypeRule', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockModel = { id: 123, payRuleSetId: 1, dayType: 'Weekend' }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updatePayDayTypeRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updatePayDayTypeRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123', mockModel); - })); + }); }); describe('deletePayDayTypeRule', () => { - it('should call apiBaseService.delete with correct id', () => new Promise((done) => { + it('should call apiBaseService.delete with correct id', async () => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); - service.deletePayDayTypeRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.deletePayDayTypeRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-day-type-rules/123'); - })); + }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts index 43e816663..e96e8063a 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-rule-sets.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnPayRuleSetsService } from './time-planning-pn-pay-rule-sets.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPayRuleSetsService', () => { let service: TimePlanningPnPayRuleSetsService; @@ -24,78 +24,68 @@ describe('TimePlanningPnPayRuleSetsService', () => { }); describe('getPayRuleSets', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payRuleSets: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayRuleSets(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayRuleSets(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-rule-sets', { offset: '0', pageSize: '10' } ); - })); + }); }); describe('getPayRuleSet', () => { - it('should call apiBaseService.get with correct id', () => new Promise((done) => { + it('should call apiBaseService.get with correct id', async () => { const mockResponse = { success: true, model: { id: 123, name: 'Test', payDayRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayRuleSet(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayRuleSet(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123'); - })); + }); }); describe('createPayRuleSet', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockModel = { name: 'New RuleSet', payDayRules: [], payDayTypeRules: [] }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.createPayRuleSet(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.createPayRuleSet(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets', mockModel); - })); + }); }); describe('updatePayRuleSet', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockModel = { name: 'Updated', payDayRules: [], payDayTypeRules: [] }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updatePayRuleSet(123, mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updatePayRuleSet(123, mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123', mockModel); - })); + }); }); describe('deletePayRuleSet', () => { - it('should call apiBaseService.delete with correct id', () => new Promise((done) => { + it('should call apiBaseService.delete with correct id', async () => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); - service.deletePayRuleSet(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.deletePayRuleSet(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-rule-sets/123'); - })); + }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts index 248741892..7d2de6ad4 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-tier-rules.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnPayTierRulesService } from './time-planning-pn-pay-tier-rules.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPayTierRulesService', () => { let service: TimePlanningPnPayTierRulesService; @@ -24,93 +24,81 @@ describe('TimePlanningPnPayTierRulesService', () => { }); describe('getPayTierRules', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payTierRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTierRules(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayTierRules(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-tier-rules', { offset: '0', pageSize: '10' } ); - })); + }); - it('should include optional payDayRuleId filter', () => new Promise((done) => { + it('should include optional payDayRuleId filter', async () => { const mockRequest = { offset: 0, pageSize: 10, payDayRuleId: 3 }; const mockResponse = { success: true, model: { total: 0, payTierRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTierRules(mockRequest).subscribe(result => { - done(); - }); + await firstValueFrom(service.getPayTierRules(mockRequest)); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-tier-rules', { offset: '0', pageSize: '10', payDayRuleId: '3' } ); - })); + }); }); describe('getPayTierRule', () => { - it('should call apiBaseService.get with correct id', () => new Promise((done) => { + it('should call apiBaseService.get with correct id', async () => { const mockResponse = { success: true, model: { id: 123, payDayRuleId: 1, order: 1, upToSeconds: 3600, payCode: 'CODE1' } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTierRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayTierRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123'); - })); + }); }); describe('createPayTierRule', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockModel = { payDayRuleId: 1, order: 1, upToSeconds: 3600, payCode: 'CODE1' }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.createPayTierRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.createPayTierRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules', mockModel); - })); + }); }); describe('updatePayTierRule', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockModel = { id: 123, payDayRuleId: 1, order: 2, upToSeconds: 7200, payCode: 'CODE2' }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updatePayTierRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updatePayTierRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123', mockModel); - })); + }); }); describe('deletePayTierRule', () => { - it('should call apiBaseService.delete with correct id', () => new Promise((done) => { + it('should call apiBaseService.delete with correct id', async () => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); - service.deletePayTierRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.deletePayTierRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-tier-rules/123'); - })); + }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts index 05d4ff21b..6c0b794fd 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-pay-time-band-rules.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnPayTimeBandRulesService } from './time-planning-pn-pay-time-band-rules.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPayTimeBandRulesService', () => { let service: TimePlanningPnPayTimeBandRulesService; @@ -24,96 +24,84 @@ describe('TimePlanningPnPayTimeBandRulesService', () => { }); describe('getPayTimeBandRules', () => { - it('should call apiBaseService.get with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.get with correct parameters', async () => { const mockRequest = { offset: 0, pageSize: 10 }; const mockResponse = { success: true, model: { total: 0, payTimeBandRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTimeBandRules(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayTimeBandRules(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-time-band-rules', { offset: '0', pageSize: '10' } ); - })); + }); - it('should include optional payDayTypeRuleId filter', () => new Promise((done) => { + it('should include optional payDayTypeRuleId filter', async () => { const mockRequest = { offset: 0, pageSize: 10, payDayTypeRuleId: 2 }; const mockResponse = { success: true, model: { total: 0, payTimeBandRules: [] } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTimeBandRules(mockRequest).subscribe(result => { - done(); - }); + await firstValueFrom(service.getPayTimeBandRules(mockRequest)); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/pay-time-band-rules', { offset: '0', pageSize: '10', payDayTypeRuleId: '2' } ); - })); + }); }); describe('getPayTimeBandRule', () => { - it('should call apiBaseService.get with correct id', () => new Promise((done) => { + it('should call apiBaseService.get with correct id', async () => { const mockResponse = { success: true, model: { id: 123, payDayTypeRuleId: 1, startSecondOfDay: 0, endSecondOfDay: 43200, payCode: 'DAY' } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.getPayTimeBandRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPayTimeBandRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123'); - })); + }); }); describe('createPayTimeBandRule', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockModel = { payDayTypeRuleId: 1, startSecondOfDay: 0, endSecondOfDay: 43200, payCode: 'DAY', priority: 1 }; const mockResponse = { success: true }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.createPayTimeBandRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.createPayTimeBandRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules', mockModel); - })); + }); }); describe('updatePayTimeBandRule', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockModel = { id: 123, payDayTypeRuleId: 1, startSecondOfDay: 43200, endSecondOfDay: 86400, payCode: 'NIGHT', priority: 1 }; const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updatePayTimeBandRule(mockModel).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updatePayTimeBandRule(mockModel)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123', mockModel); - })); + }); }); describe('deletePayTimeBandRule', () => { - it('should call apiBaseService.delete with correct id', () => new Promise((done) => { + it('should call apiBaseService.delete with correct id', async () => { const mockResponse = { success: true }; mockApiBaseService.delete.mockReturnValue(of(mockResponse as any)); - service.deletePayTimeBandRule(123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.deletePayTimeBandRule(123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.delete).toHaveBeenCalledWith('api/time-planning-pn/pay-time-band-rules/123'); - })); + }); }); }); diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts index e4b4fd73f..9cd95a109 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-payroll-export.service.spec.ts @@ -4,7 +4,7 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TimePlanningPnPayrollExportService } from './time-planning-pn-payroll-export.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPayrollExportService', () => { let service: TimePlanningPnPayrollExportService; @@ -39,33 +39,29 @@ describe('TimePlanningPnPayrollExportService', () => { }); describe('preview', () => { - it('should call correct endpoint with start and end params', () => new Promise((done) => { + it('should call correct endpoint with start and end params', async () => { const mockResponse = { success: true, model: { rows: [], alreadyExportedAt: null } }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.preview('2026-03-20', '2026-04-19').subscribe((result) => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.preview('2026-03-20', '2026-04-19')); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/payroll/preview?start=2026-03-20&end=2026-04-19' ); - })); + }); - it('should pass different date ranges correctly', () => new Promise((done) => { + it('should pass different date ranges correctly', async () => { const mockResponse = { success: true, model: null }; mockApiBaseService.get.mockReturnValue(of(mockResponse as any)); - service.preview('2026-01-01', '2026-01-31').subscribe((result) => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.preview('2026-01-01', '2026-01-31')); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.get).toHaveBeenCalledWith( 'api/time-planning-pn/payroll/preview?start=2026-01-01&end=2026-01-31' ); - })); + }); }); describe('exportPayroll', () => { diff --git a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts index 2c243e073..302bead89 100644 --- a/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts +++ b/eform-client/src/app/plugins/modules/time-planning-pn/services/time-planning-pn-plannings.service.spec.ts @@ -2,7 +2,7 @@ import {describe, it, expect, beforeEach, vi} from 'vitest'; import type {Mocked} from 'vitest'; import { TimePlanningPnPlanningsService } from './time-planning-pn-plannings.service'; import { ApiBaseService } from 'src/app/common/services'; -import { of } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; describe('TimePlanningPnPlanningsService', () => { let service: TimePlanningPnPlanningsService; @@ -23,7 +23,7 @@ describe('TimePlanningPnPlanningsService', () => { }); describe('getPlannings', () => { - it('should call apiBaseService.post with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.post with correct parameters', async () => { const mockRequest = { dateFrom: '2024-01-01', dateTo: '2024-01-07', @@ -35,18 +35,16 @@ describe('TimePlanningPnPlanningsService', () => { const mockResponse = { success: true, model: [] }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.getPlannings(mockRequest).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.getPlannings(mockRequest)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.post).toHaveBeenCalledWith( 'api/time-planning-pn/plannings/index', mockRequest ); - })); + }); - it('should handle empty response', () => new Promise((done) => { + it('should handle empty response', async () => { const mockRequest = { dateFrom: '2024-01-01', dateTo: '2024-01-07', @@ -58,15 +56,13 @@ describe('TimePlanningPnPlanningsService', () => { const mockResponse = { success: true, model: [] }; mockApiBaseService.post.mockReturnValue(of(mockResponse as any)); - service.getPlannings(mockRequest).subscribe(result => { - expect(result.model).toEqual([]); - done(); - }); - })); + const result = await firstValueFrom(service.getPlannings(mockRequest)); + expect(result.model).toEqual([]); + }); }); describe('updatePlanning', () => { - it('should call apiBaseService.put with correct parameters', () => new Promise((done) => { + it('should call apiBaseService.put with correct parameters', async () => { const mockPlanningModel = { id: 123, planHours: 8, @@ -76,16 +72,14 @@ describe('TimePlanningPnPlanningsService', () => { const mockResponse = { success: true }; mockApiBaseService.put.mockReturnValue(of(mockResponse as any)); - service.updatePlanning(mockPlanningModel, 123).subscribe(result => { - expect(result).toEqual(mockResponse as any); - done(); - }); + const result = await firstValueFrom(service.updatePlanning(mockPlanningModel, 123)); + expect(result).toEqual(mockResponse as any); expect(mockApiBaseService.put).toHaveBeenCalledWith( 'api/time-planning-pn/plannings/123', mockPlanningModel ); - })); + }); it('should construct correct URL with id parameter', () => { const mockPlanningModel = { id: 456 } as any; From 2d3f22b78c1de088e02b74e2477cf9efaa856616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Sun, 18 Oct 2026 12:56:12 +0200 Subject: [PATCH 7/7] fix: wait for the timepicker minute face and the day dialog before acting The Angular 21 frontend bootstraps with provideZoneChangeDetection({ eventCoalescing: true }), so a click's re-render lands a frame later. After the hour click the forced minute click hit the still-rendered hour face (":00" at 360deg is hour 12, ":30" at 180deg is hour 6), giving 12:00 / 06:30 instead of 07:00 / 07:30. Scope the minute selectors to ngx-material-timepicker-minutes-face so the click waits for the face to switch. In l1m the non-waiting wipeBtn.count() ran before the dialog rendered, so the shift wipe was skipped and the leftover shift 2 failed the shift-order validator, keeping Save disabled; wait for the dialog first. Co-Authored-By: Claude Opus 5.5 --- .../b/dashboard-edit-a.spec.ts | 14 ++++++------ .../b/dashboard-edit-b.spec.ts | 22 +++++++++---------- .../c/time-planning-glsa-3f-pay-rules.spec.ts | 4 ++-- .../l1m/dashboard-edit-actual-exact.spec.ts | 5 ++++- .../r/dashboard-version-history.spec.ts | 4 ++-- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-a.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-a.spec.ts index 3399b5517..1c4fc465c 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-a.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-a.spec.ts @@ -205,7 +205,7 @@ async function clickTimePicker(page: import('@playwright/test').Page, timeStr: s await page.locator('[style="transform: rotateZ(' + degrees + 'deg) translateX(-50%);"] > span').click(); } if (minuteDegrees > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -267,7 +267,7 @@ test.describe('Dashboard edit values', () => { let minuteDegrees0 = 360 / 60 * parseInt(secondUpdatePlanTexts[i].plannedStartOfShift1.split(':')[1]); await page.locator('[style="transform: rotateZ(' + degrees0 + 'deg) translateX(-50%);"] > span').click(); if (minuteDegrees0 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees0 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees0 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="plannedStartOfShift1"]')).toHaveValue(secondUpdatePlanTexts[i].plannedStartOfShift1); @@ -282,7 +282,7 @@ test.describe('Dashboard edit values', () => { await page.locator('[style="transform: rotateZ(' + degrees1 + 'deg) translateX(-50%);"] > span').click(); } if (minuteDegrees1 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees1 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees1 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="plannedEndOfShift1"]')).toHaveValue(secondUpdatePlanTexts[i].plannedEndOfShift1); @@ -302,7 +302,7 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees2 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees2 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees2 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -319,7 +319,7 @@ test.describe('Dashboard edit values', () => { await page.locator('[style="transform: rotateZ(' + degrees3 + 'deg) translateX(-50%);"] > span').click(); } if (minuteDegrees3 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees3 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees3 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="plannedStartOfShift2"]')).toHaveValue(secondUpdatePlanTexts[i].plannedStartOfShift2); @@ -334,7 +334,7 @@ test.describe('Dashboard edit values', () => { await page.locator('[style="transform: rotateZ(' + degrees4 + 'deg) translateX(-50%);"] > span').click(); } if (minuteDegrees4 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees4 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees4 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="plannedEndOfShift2"]')).toHaveValue(secondUpdatePlanTexts[i].plannedEndOfShift2); @@ -353,7 +353,7 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees5 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees5 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees5 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="plannedBreakOfShift2"]')).toHaveValue(secondUpdatePlanTexts[i].plannedBreakOfShift2); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-b.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-b.spec.ts index a70632b1b..776f6b5c7 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-b.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/b/dashboard-edit-b.spec.ts @@ -167,7 +167,7 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees0 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees0 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees0 + 'deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -188,10 +188,10 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees1 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees1 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees1 + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (minuteDegrees1 === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -212,10 +212,10 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees2 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees2 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees2 + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (minuteDegrees2 === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="pause1Id"]')).toHaveValue(secondUpdateActualTexts[i].pause1Id); @@ -238,10 +238,10 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees3 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees3 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees3 + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (!minuteDegrees3 || isNaN(minuteDegrees3) || minuteDegrees3 === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -262,10 +262,10 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees4 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees4 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees4 + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (minuteDegrees4 === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); } @@ -286,10 +286,10 @@ test.describe('Dashboard edit values', () => { } } if (minuteDegrees5 > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees5 + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees5 + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (minuteDegrees5 === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator('[data-testid="pause2Id"]')).toHaveValue(secondUpdateActualTexts[i].pause2Id); diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts index 8437a1f81..2a984bfaf 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/c/time-planning-glsa-3f-pay-rules.spec.ts @@ -195,9 +195,9 @@ async function setTimepickerValue( // Select minute on the clock face const minuteDegrees = 360 / 60 * minutes; if (minuteDegrees === 0) { - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click(); } else { - await page.locator(`[style="transform: rotateZ(${minuteDegrees}deg) translateX(-50%);"] > span`).click({ force: true }); + await page.locator(`ngx-material-timepicker-minutes-face [style="transform: rotateZ(${minuteDegrees}deg) translateX(-50%);"] > span`).click({ force: true }); } // Confirm diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/l1m/dashboard-edit-actual-exact.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/l1m/dashboard-edit-actual-exact.spec.ts index 90c7c445b..898ea7c52 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/l1m/dashboard-edit-actual-exact.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/l1m/dashboard-edit-actual-exact.spec.ts @@ -87,7 +87,10 @@ async function openDialogForActiveCell(page: Page) { await page.locator('#cell0_0').click(); // Wipe any seed-prefilled shift-1 actual-start so the test starts from a - // known-empty pair. Mirrors the f1m beforeEach wipe. + // known-empty pair. Mirrors the f1m beforeEach wipe. Wait for the dialog + // first: count() does not wait, and the dialog renders a frame after the + // click, so counting straight away skipped the wipe. + await page.locator('[data-testid="start1StartedAt"]').waitFor({ state: 'visible', timeout: 15000 }); const wipeBtn = page.locator('[data-testid="start1StartedAt"]') .locator('xpath=ancestor::div[contains(@class,"flex-row")]') .locator('button mat-icon') diff --git a/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts b/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts index ef83c61f9..b63ef36ee 100644 --- a/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts +++ b/eform-client/playwright/e2e/plugins/time-planning-pn/r/dashboard-version-history.spec.ts @@ -61,7 +61,7 @@ async function pickPlannedTime(page: Page, testid: string, timeStr: string) { await page.locator('[style="transform: rotateZ(' + degrees + 'deg) translateX(-50%);"] > span').click(); } if (minuteDegrees > 0) { - await page.locator('[style="transform: rotateZ(' + minuteDegrees + 'deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(' + minuteDegrees + 'deg) translateX(-50%);"] > span').click({ force: true }); } if (minuteDegrees === 0) { // The minute face must be clicked even for :00 — skipping it keeps the @@ -71,7 +71,7 @@ async function pickPlannedTime(page: Page, testid: string, timeStr: string) { // clock-face__number--outer div intercepts pointer events on the "00" // span, so a plain click retries until the test timeout (CI run // 29317211838). - await page.locator('[style="transform: rotateZ(360deg) translateX(-50%);"] > span').click({ force: true }); + await page.locator('ngx-material-timepicker-minutes-face [style="transform: rotateZ(360deg) translateX(-50%);"] > span').click({ force: true }); } await page.locator('.timepicker-button span').filter({ hasText: 'Ok' }).click(); await expect(page.locator(`[data-testid="${testid}"]`)).toHaveValue(timeStr);