diff --git a/jest.web-esm.workspace.ts b/jest.web-esm.workspace.ts new file mode 100644 index 000000000..505f1e097 --- /dev/null +++ b/jest.web-esm.workspace.ts @@ -0,0 +1,41 @@ +import { nxPreset } from '@nx/jest/preset'; +import { createEsmPreset } from 'jest-preset-angular/presets'; + +const angularEsmPreset = createEsmPreset({ + diagnostics: false, + tsconfig: '/apps/web/tsconfig.spec.json', +}); + +export default { + ...nxPreset, + ...angularEsmPreset, + rootDir: '.', + roots: ['/apps/web', '/libs'], + setupFilesAfterEnv: ['/apps/web/src/test-setup.ts'], + resolver: nxPreset.resolver, + moduleFileExtensions: Array.from( + new Set([ + ...(nxPreset.moduleFileExtensions ?? []), + ...(angularEsmPreset.moduleFileExtensions ?? []), + ]) + ), + testMatch: nxPreset.testMatch, + testEnvironmentOptions: {}, + snapshotSerializers: angularEsmPreset.snapshotSerializers, + moduleNameMapper: { + ...(angularEsmPreset.moduleNameMapper ?? {}), + '^@iptvnator/portal/xtream/feature$': + '/apps/web/src/test-stubs/xtream-feature.stub.ts', + tslib: 'tslib/tslib.es6.js', + '^iptv-playlist-parser$': + '/apps/web/src/test-stubs/iptv-playlist-parser.mjs', + '^rxjs': '/node_modules/rxjs/dist/bundles/rxjs.umd.js', + '^uuid$': '/node_modules/uuid/wrapper.mjs', + }, + transform: angularEsmPreset.transform, + transformIgnorePatterns: [], + extensionsToTreatAsEsm: angularEsmPreset.extensionsToTreatAsEsm, + modulePathIgnorePatterns: ['/dist/', '/.nx/'], + watchPathIgnorePatterns: ['/dist/', '/.nx/'], + coverageReporters: [...(nxPreset.coverageReporters ?? [])], +}; diff --git a/libs/ui/components/src/lib/confirm-dialog/dialog.service.spec.ts b/libs/ui/components/src/lib/confirm-dialog/dialog.service.spec.ts index 9c54c1818..9cf61982c 100644 --- a/libs/ui/components/src/lib/confirm-dialog/dialog.service.spec.ts +++ b/libs/ui/components/src/lib/confirm-dialog/dialog.service.spec.ts @@ -1,43 +1,65 @@ -/* import { inject, TestBed } from '@angular/core/testing'; -import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { TranslateModule } from '@ngx-translate/core'; -import { MockComponent, MockModule } from 'ng-mocks'; -import { EMPTY } from 'rxjs'; -import { ConfirmDialogData } from '../<../../../apps/web/src/app/shared/components/confirm-dialog/confirm-dialog-data.interface'; -import { ConfirmDialogComponent } from '../../../../apps/web/src/app/shared/components/confirm-dialog/confirm-dialog.component'; +import { TestBed } from '@angular/core/testing'; +import { MatDialog } from '@angular/material/dialog'; +import { of } from 'rxjs'; import { DialogService } from './dialog.service'; -describe('Service: Dialog', () => { +describe('DialogService', () => { + let service: DialogService; + let dialog: { open: jest.Mock }; + beforeEach(() => { + dialog = { + open: jest.fn(), + }; + TestBed.configureTestingModule({ - providers: [DialogService, MatDialog], - imports: [ - MockComponent(ConfirmDialogComponent), - MockModule(MatDialogModule), - TranslateModule.forRoot(), + providers: [ + DialogService, + { + provide: MatDialog, + useValue: dialog, + }, ], }); + + service = TestBed.inject(DialogService); }); - it('should create the service', inject( - [DialogService], - (service: DialogService) => { - expect(service).toBeTruthy(); - } - )); + it('opens the confirm dialog with the expected config', () => { + dialog.open.mockReturnValue({ + afterClosed: () => of(false), + }); - it('should open a confirm dialog', inject( - [MatDialog, DialogService], - (dialog: MatDialog, service: DialogService) => { - jest.spyOn(dialog, 'open').mockReturnValue({ - afterClosed: () => EMPTY, - } as any); - service.openConfirmDialog({ - title: 'Remove dialog', - message: 'Message', - } as ConfirmDialogData); - expect(dialog.open).toHaveBeenCalled(); - } - )); + service.openConfirmDialog({ + title: 'Remove', + message: 'Confirm removal?', + onConfirm: jest.fn(), + }); + + expect(dialog.open).toHaveBeenCalledWith( + expect.any(Function), + expect.objectContaining({ + data: expect.objectContaining({ + title: 'Remove', + message: 'Confirm removal?', + }), + width: '300px', + }) + ); + }); + + it('invokes onConfirm only after the dialog resolves truthy', () => { + const onConfirm = jest.fn(); + dialog.open.mockReturnValue({ + afterClosed: () => of(true), + }); + + service.openConfirmDialog({ + title: 'Remove', + message: 'Confirm removal?', + onConfirm, + }); + + expect(onConfirm).toHaveBeenCalledTimes(1); + }); }); - */ diff --git a/libs/ui/pipes/project.json b/libs/ui/pipes/project.json index 218d7eee7..ef437be53 100644 --- a/libs/ui/pipes/project.json +++ b/libs/ui/pipes/project.json @@ -7,11 +7,18 @@ "tags": ["scope:shared", "domain:shared-ui", "type:util"], "targets": { "test": { - "executor": "@nx/jest:jest", + "executor": "nx:run-commands", "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { - "jestConfig": "libs/ui/pipes/jest.config.ts", - "tsConfig": "libs/ui/pipes/tsconfig.spec.json" + "command": [ + "node", + "./tools/testing/run-web-esm-lib-tests.mjs", + "libs/ui/pipes/src" + ], + "env": { + "NODE_OPTIONS": "--experimental-vm-modules" + }, + "forwardAllArgs": true } }, "lint": { diff --git a/libs/ui/pipes/src/lib/moment-date.pipe.spec.ts b/libs/ui/pipes/src/lib/moment-date.pipe.spec.ts index 6c0300b92..630ed4ae1 100644 --- a/libs/ui/pipes/src/lib/moment-date.pipe.spec.ts +++ b/libs/ui/pipes/src/lib/moment-date.pipe.spec.ts @@ -1,9 +1,21 @@ -/* import { MomentDatePipe } from './moment-date.pipe'; +import { MomentDatePipe } from './moment-date.pipe'; -describe('Pipe: MomentDate', () => { - it('create an instance', () => { - const pipe = new MomentDatePipe(); - expect(pipe).toBeTruthy(); +describe('MomentDatePipe', () => { + let pipe: MomentDatePipe; + + beforeEach(() => { + pipe = new MomentDatePipe(); + }); + + it('formats ISO strings using the requested output format', () => { + expect(pipe.transform('2026-03-28T12:00:00.000Z', 'YYYY-MM-DD')).toBe( + '2026-03-28' + ); + }); + + it('supports explicit parse formats for non-ISO values', () => { + expect( + pipe.transform('28/03/2026', 'YYYY-MM-DD', 'DD/MM/YYYY') + ).toBe('2026-03-28'); }); }); - */ diff --git a/tools/testing/run-web-esm-lib-tests.mjs b/tools/testing/run-web-esm-lib-tests.mjs new file mode 100644 index 000000000..0d6ff6c40 --- /dev/null +++ b/tools/testing/run-web-esm-lib-tests.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node + +import { readdirSync } from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import process from 'node:process'; + +const [targetDir, ...jestArgs] = process.argv.slice(2); + +if (!targetDir) { + console.error( + 'Usage: node tools/testing/run-web-esm-lib-tests.mjs [jest args...]' + ); + process.exit(1); +} + +const workspaceRoot = process.cwd(); +const absoluteTargetDir = path.resolve(workspaceRoot, targetDir); + +function collectSpecFiles(directory) { + const entries = readdirSync(directory, { withFileTypes: true }); + const specFiles = []; + + for (const entry of entries) { + const fullPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + specFiles.push(...collectSpecFiles(fullPath)); + continue; + } + + if ( + entry.isFile() && + (entry.name.endsWith('.spec.ts') || entry.name.endsWith('.test.ts')) + ) { + specFiles.push(fullPath); + } + } + + return specFiles; +} + +const specFiles = collectSpecFiles(absoluteTargetDir).sort(); + +if (specFiles.length === 0) { + console.error(`No spec files found under ${targetDir}`); + process.exit(1); +} + +const jestBin = path.resolve(workspaceRoot, 'node_modules/jest/bin/jest.js'); +const jestConfig = path.resolve( + workspaceRoot, + 'jest.web-esm.workspace.ts' +); + +const result = spawnSync( + process.execPath, + [jestBin, '--config', jestConfig, '--runTestsByPath', ...specFiles, ...jestArgs], + { + env: process.env, + stdio: 'inherit', + } +); + +process.exit(result.status ?? 1);