UNPKG

@dbg-riskit/angular-testing

Version:

1,315 lines (1,292 loc) 55.2 kB
import '@dbg-riskit/angular-polyfill'; import { getTestBed, tick, discardPeriodicTasks } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import * as i0 from '@angular/core'; import { EventEmitter, Injectable, Component, input, signal, inject, ChangeDetectionStrategy } from '@angular/core'; import { MatMenuTrigger, MatMenuItem } from '@angular/material/menu'; import { By } from '@angular/platform-browser'; import { RiskCSVFileDownloadDirective, RiskFileDownloadDirective } from '@dbg-riskit/angular-file'; import 'file-saver'; import { RISK_INITIAL_LOAD_SELECTOR, RISK_NO_DATA_SELECTOR, RiskLayoutComponent, RiskLayoutHorizontalDirective, RiskLayoutVerticalDirective, RiskMessageComponent, RISK_GOOD_SELECTOR, RISK_ERROR_SELECTOR, RISK_INFO_SELECTOR, RISK_MESSAGE_SELECTOR, RISK_WARN_SELECTOR, RISK_UPDATE_FAILED_SELECTOR } from '@dbg-riskit/angular-view'; export { RiskLayoutComponent } from '@dbg-riskit/angular-view'; import { throwError, of, timer, defer, EMPTY, BehaviorSubject } from 'rxjs'; import { switchMap, defaultIfEmpty, map } from 'rxjs/operators'; import { MatCard } from '@angular/material/card'; import { MatIconButton, MatIconAnchor, MatButton } from '@angular/material/button'; import { MatButtonToggle } from '@angular/material/button-toggle'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; import { RiskDataTablePagingComponent, RiskDataTableRowDetailExpanderComponent, HIGHLIGHTER_CLASS, RiskDataTableComponent, RiskDataTableColumnCellDirective, RiskDataTableColumnDirective, RiskDataTableColumnFooterDirective, RiskDataTableColumnGroupDirective, RiskDataTableRowDetailDirective, RiskHighlighterDirective } from '@dbg-riskit/angular-datatable'; import { secureRandom, ReplaySubjectExt, globalScope } from '@dbg-riskit/common'; import { MatSidenavContainer, MatSidenav } from '@angular/material/sidenav'; import { MatToolbar } from '@angular/material/toolbar'; import { RouterLink } from '@angular/router'; import { AUTH_CONFIG, AuthFlow } from '@dbg-riskit/angular-auth'; import { AUTH_PROVIDER } from '@dbg-riskit/angular-common'; // Has to be first in this order const COMPILE_TIMEOUT_INTERVAL = Math.pow(2, 31) - 1; function initTestEnvironment() { Error.stackTraceLimit = 10; jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; // TODO: Quick fix for memory leaks window.addEventListener = () => { return; }; window.document.addEventListener = () => { return; }; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: false } }); } /** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */ const BUTTON_CLICK_EVENTS = { left: { button: 0 }, right: { button: 2 } }; /** Simulate element click. Defaults to mouse left-button click event. */ function click(el, eventObj = BUTTON_CLICK_EVENTS.left) { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } function setNgModelValue(element, value, realAsync = false) { if (!(element.nativeElement instanceof HTMLInputElement)) { throw new Error('Not an instance of HTMLInputElement'); } const input = element.nativeElement; input.value = value; dispatchEvent(input, 'input'); // tell Angular if (!realAsync) { tick(); } } function setNgModelSelectValue(element, selectedIndex, realAsync = false) { if (!(element.nativeElement instanceof HTMLSelectElement)) { throw new Error('Not an instance of HTMLInputElement'); } const input = element.nativeElement; input.selectedIndex = selectedIndex; dispatchEvent(input, 'change'); // tell Angular if (!realAsync) { tick(); } } function dispatchEvent(element, eventName) { if (element instanceof HTMLElement) { element.dispatchEvent(newEvent(eventName)); } else if (element instanceof Window) { element.dispatchEvent(newEvent(eventName)); } else { element.nativeElement.dispatchEvent(newEvent(eventName)); } } /** * Create custom DOM event the old fashioned way * * https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent * Although officially deprecated, some browsers (phantom) don't accept the preferred "new Event(eventName)" */ function newEvent(eventName, bubbles = false, cancelable = false) { const evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent' evt.initCustomEvent(eventName, bubbles, cancelable, null); return evt; } const FAKE_HTTP_ASYNC_TIMEOUT = 1000; class HttpServiceStub { constructor() { this.value = []; this.error = []; this.unauthorized = new EventEmitter(); } returnValue(value) { this.value.push(value); } popReturnValue() { return this.value.pop(); } shiftReturnValue() { return this.value.shift(); } throwError(value) { this.error.push(value); } get(request) { if (this.error.length) { const error = this.error.shift(); return throwError(error); } const value = this.value.shift(); return of(value); } post(request) { return this.get(request); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpServiceStub, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpServiceStub }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpServiceStub, decorators: [{ type: Injectable }] }); class HttpAsyncServiceStub extends HttpServiceStub { get(request, auth = true) { return timer(FAKE_HTTP_ASYNC_TIMEOUT).pipe(switchMap(() => super.get(request))); } post(request) { return this.get(request); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpAsyncServiceStub, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpAsyncServiceStub }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: HttpAsyncServiceStub, decorators: [{ type: Injectable }] }); class RiskMessageComponentDef { constructor(debugElement) { this.debugElement = debugElement; if (debugElement == null) { throw new Error('Debug element not found!'); } } get text() { return this.debugElement.query(By.directive(MatCard)).nativeElement.textContent.replace(/\n\s*/g, ' ') .replace(/\r\s*/g, ' ').trim(); } } class Page { constructor(fixture) { this.fixture = fixture; this._timeOffset = 0; this.debugElement = fixture.debugElement; this.component = this.debugElement.componentInstance; } detectChanges(millis = 0) { this.fixture.detectChanges(); tick(millis); this._timeOffset += millis; } advanceAndDetectChanges(millis = 0) { tick(millis); this._timeOffset += millis; this.detectChanges(); } advanceHTTP() { this.advanceAndDetectChanges(FAKE_HTTP_ASYNC_TIMEOUT); } advanceAndDetectChangesUsingOffset(millis) { this.advanceAndDetectChanges(millis - this._timeOffset); this.resetTimeOffset(); } resetTimeOffset() { this._timeOffset = 0; } destroy() { this.fixture.destroy(); try { // Move a lot into fututre ;) tick(60 * 60 * 1000); discardPeriodicTasks(); } catch (ignore) { // Nothing to do... } } setInput(inputName, value) { this.fixture.componentRef.setInput(inputName, value); this.detectChanges(); } } class PageWithLoading extends Page { constructor(fixture) { super(fixture); } get initialLoadComponent() { const element = this.debugElement.query(By.css(RISK_INITIAL_LOAD_SELECTOR)); if (element) { return new RiskMessageComponentDef(element); } return null; } get noDataComponent() { const element = this.debugElement.query(By.css(RISK_NO_DATA_SELECTOR)); if (element) { return new RiskMessageComponentDef(element); } return null; } } class RiskCSVDownloadMenuPage extends Page { constructor(fixture) { super(fixture); } get downloadWindowsLink() { // Open the menu first click(this.debugElement.query(By.directive(MatMenuTrigger))); this.detectChanges(500); return new DownloadLink(this.debugElement.queryAll(By.directive(MatMenuItem))[0], this); } get downloadUnixLink() { // Open the menu first click(this.debugElement.query(By.directive(MatMenuTrigger))); this.detectChanges(500); return new DownloadLink(this.debugElement.queryAll(By.directive(MatMenuItem))[1], this); } } class DownloadLink { constructor(element, page) { this.element = element; this.page = page; } get blobSpy() { this.setupBlobConstructorSpy(); return this._blobSpy; } get saveSpy() { this.setupSaveBlobSpy(); return this._saveBlobSpy; } click() { this.setupBlobConstructorSpy(); this.setupSaveBlobSpy(); click(this.element); this.page.detectChanges(); } setupBlobConstructorSpy() { if (!this._blobSpy) { let downloadDirective; try { downloadDirective = this.element.injector.get(RiskCSVFileDownloadDirective); } catch (_) { downloadDirective = this.element.injector.get(RiskFileDownloadDirective); } this._blobSpy = spyOn(downloadDirective, 'createBlob').and.callThrough(); } } setupSaveBlobSpy() { if (!this._saveBlobSpy) { this._saveBlobSpy = spyOn(window, 'saveAs'); } } } class DownloadTestComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DownloadTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DownloadTestComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` <a [risk-download]="data" [risk-download-content-type]="contentType" [risk-download-filename]="filename">Download</a> `, isInline: true, dependencies: [{ kind: "directive", type: RiskFileDownloadDirective, selector: "[risk-download]", inputs: ["risk-download", "risk-download-content-type", "risk-download-filename"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DownloadTestComponent, decorators: [{ type: Component, args: [{ template: ` <a [risk-download]="data" [risk-download-content-type]="contentType" [risk-download-filename]="filename">Download</a> `, imports: [RiskFileDownloadDirective] }] }] }); class DownloadTestComponentPage extends Page { constructor(fixture) { super(fixture); } get downloadLink() { return new DownloadLink(this.debugElement.query(By.directive(RiskFileDownloadDirective)), this); } } class RiskDataTableDefinition { constructor(debugElement, page) { this.debugElement = debugElement; this.page = page; } get component() { return this.debugElement.componentInstance; } get data() { return this.component.plainData; } get element() { return this.debugElement.query(By.css('.risk-data-table-wrapper > table')); } get header() { return new TableHeader(this.debugElement.query((de) => de.references.mainHeader), this.page); } get sorting() { return new TableSorting(this, this.page); } get body() { return new TableBody(this.debugElement.query((de) => de.references.mainBody), this.page); } get footer() { return new TableFooter(this.debugElement.query((de) => de.references.mainFooter)); } get recordsCount() { return new RecordsCount(this.debugElement.query(By.css('.risk-data-table-page-count'))); } get pager() { return new Pager(this.debugElement.query(By.directive(RiskDataTablePagingComponent)), this.page); } } // <editor-fold defaultstate="collapsed" desc="Table header"> class TableHeader { constructor(element, page) { this.element = element; this.page = page; } get rows() { return this.element.queryAll(By.css('tr')).map((element) => { return new TableHeaderRow(element, this.page); }); } get cells() { return this.element.queryAll(By.css('th')).map((element) => { return new TableHeaderCell(element, this.page); }); } } class TableHeaderRow { constructor(element, page) { this.element = element; this.page = page; } get cells() { return this.element.queryAll(By.css('th')).map((element) => { return new TableHeaderCell(element, this.page); }); } } class TableHeaderCell { constructor(element, page) { this.element = element; this.page = page; } get sortingHandle() { const handle = this.element.query(By.directive(MatIconButton)); if (handle) { return new SortingHandle(this.page, handle); } return null; } get tooltip() { const handle = this.element.query(By.directive(MatTooltip)); if (handle) { return handle.injector.get(MatTooltip).message; } return null; } get title() { const textNode = this.element.childNodes.find((node) => node.nativeNode.nodeType === Node.TEXT_NODE); if (textNode != null) { return textNode .nativeNode.textContent.trim(); } return ''; } get colspan() { return this.element.nativeElement.colspan; } get rowspan() { return this.element.nativeElement.rowspan; } } // <editor-fold defaultstate="collapsed" desc="Sorting"> class TableSorting { constructor(table, page) { this.table = table; this.page = page; } get handles() { const handles = this.table.debugElement.query((de) => de.references.mainHeader) .queryAll(By.directive(MatIconButton)); if (!handles) { return []; } return handles.map((handle) => { return new SortingHandle(this.page, handle); }); } get detailRowHandles() { const handles = this.table.debugElement.query(By.css('.risk-data-table-detail')).query(By.css('thead')) .queryAll(By.directive(MatIconButton)); if (!handles) { return null; } return handles.map((handle) => { return new SortingHandle(this.page, handle); }); } get currentOrdering() { return this.table.component.ordering(); } checkSorting(firstNRows = this.table.component.rows.length, criterium) { // WARN: call data once as it calls .map on all table rows const data = this.table.data; let ordering; if (criterium) { ordering = [criterium].concat(this.table.component._defaultOrdering()); } else { ordering = this.currentOrdering; } for (let i = 1; i < Math.min(data.length, firstNRows); i++) { ordering.some((criteria) => { //noinspection EqualityComparisonWithCoercionJS const valueA = criteria.get(data[i - 1]); const valueB = criteria.get(data[i]); if (valueA == null && valueB == null) { return false; } expect(valueA).not.toBeNull(); expect(valueA).not.toBeUndefined(); if (valueB != null) { if (criteria.descending) { expect(valueA >= valueB) .toBeTruthy('Expect: ' + valueA + ' >= ' + valueB); } else { expect(valueA <= valueB) .toBeTruthy('Expect: ' + valueA + ' <= ' + valueB); } } return valueA !== valueB; }); } } } class SortingHandle { constructor(page, handle) { this.page = page; this.handle = handle; } click() { click(this.handle); this.page.detectChanges(); } } // </editor-fold> // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Table body"> class TableBody { constructor(element, page) { this.element = element; this.page = page; } get rows() { return this.element.queryAll((de) => de.references.masterRow).map((element) => { return new TableBodyRow(element, this.page); }); } get cells() { let cells = []; this.element.queryAll((de) => de.references.masterRow).forEach((row) => { cells = cells.concat(row.queryAll(By.css('td')).map((cell) => { return new TableBodyCell(cell); })); }); return cells; } } class TableBodyRow { constructor(element, page) { this.element = element; this.page = page; } get expander() { return new RowExpander(this.element.query(By.directive(RiskDataTableRowDetailExpanderComponent)) .query(By.directive(MatIconAnchor))); } get cells() { return this.element.queryAll(By.css('td')).map((element) => { return new TableBodyCell(element); }); } get rowDetail() { const parent = this.element.parent; if (parent != null) { const next = parent.children[parent.children.indexOf(this.element) + 1]; if (next && !next.references.masterRow) { return new TableBodyDetail(next, this.page); } } return null; } get highlighted() { return this.element.nativeElement.classList.contains(HIGHLIGHTER_CLASS); } expandRow() { click(this.element); this.page.detectChanges(); } } class RowExpander { constructor(element) { this.element = element; } get icon() { return this.element.query(By.directive(MatIcon)).nativeElement.textContent.trim(); } get opened() { return this.icon === 'expand_less'; } get closed() { return this.icon === 'expand_more'; } } class TableBodyCell { constructor(element) { this.element = element; } get colspan() { return this.element.nativeElement.colspan; } get rowspan() { return this.element.nativeElement.rowspan; } } // <editor-fold defaultstate="collapsed" desc="Row detail"> class TableBodyDetail { constructor(element, page) { this.element = element; this.page = page; } get body() { return new TableBodyDetailBody(this.element.query(By.css('tbody'))); } get highlighted() { return this.element.nativeElement.classList.contains(HIGHLIGHTER_CLASS); } get colspan() { return this.element.children[0].nativeElement.colspan; } header() { return new TableHeader(this.element.query(By.css('thead')), this.page); } } class TableBodyDetailBody { constructor(element) { this.element = element; } get rows() { return this.element.queryAll(By.css('tr')).map((element) => { return new TableBodyDetailRow(element); }); } get cells() { let cells = []; this.element.queryAll(By.css('tr')).forEach((row) => { cells = cells.concat(row.queryAll(By.css('td')).map((cell) => { return new TableBodyCell(cell); })); }); return cells; } } class TableBodyDetailRow { constructor(element) { this.element = element; } get cells() { return this.element.queryAll(By.css('td')).map((element) => { return new TableBodyCell(element); }); } } // </editor-fold> // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Table footer"> class TableFooter { constructor(element) { this.element = element; } get rows() { return this.element.queryAll(By.css('tr')).map((element) => { return new TableFooterRow(element); }); } get cells() { let cells = []; this.element.queryAll(By.css('tr')).forEach((row) => { cells = cells.concat(row.queryAll(By.css('th')).map((cell) => { return new TableBodyCell(cell); })); }); return cells; } } class TableFooterRow { constructor(element) { this.element = element; } get cells() { return this.element.queryAll(By.css('th')).map((element) => { return new TableBodyCell(element); }); } } // </editor-fold> class RecordsCount { constructor(element) { this.element = element; } get message() { return this.element.nativeElement.textContent; } } class Pager { constructor(element, page) { this.element = element; this.page = page; } get pageButtons() { return this.element.queryAll(By.directive(MatButtonToggle)); } expectLeadingButtonsDisabled() { for (let i = 0; i < 2; i++) { expect(this.pageButtons[i].classes['mat-button-toggle-disabled']) .toBe(true, 'First two are disabled.'); } } expectLeadingButtonsNotDisabled() { for (let i = 0; i < 2; i++) { expect(this.pageButtons[i].classes['mat-button-toggle-disabled']) .not.toBe(true, 'First two are not disabled.'); } } expectTrailingButtonsDisabled() { for (let i = this.pageButtons.length - 1; i > this.pageButtons.length - 3; i--) { expect(this.pageButtons[i].classes['mat-button-toggle-disabled']) .toBe(true, 'Last two are disabled.'); } } expectTrailingButtonsNotDisabled() { for (let i = this.pageButtons.length - 1; i > this.pageButtons.length - 3; i--) { expect(this.pageButtons[i].classes['mat-button-toggle-disabled']) .not.toBe(true, 'Last two are not disabled.'); } } expectButtonNumbers(numbers) { for (let i = 0; i < numbers.length; i++) { expect(this.pageButtons[i + 2].query(By.css('.mat-button-toggle-label-content')) .nativeElement.textContent.trim()) .toEqual(numbers[i] + '', 'Button numbers are correct'); } } expectButtonActive(index) { expect(this.pageButtons[index].classes['mat-button-toggle-checked']) .toBe(true, 'Button is active.'); } click(index) { click(this.pageButtons[index].query(By.css('.mat-button-toggle-label-content')).nativeElement); this.page.detectChanges(); this.page.advanceAndDetectChanges(); } } function chceckSorting(page, criteria) { page.dataTable.sorting.checkSorting(150); page.dataTable.sorting.handles.forEach((handle, index) => { // Tigger sort based on a handle handle.click(); page.detectChanges(); // Check the sorting page.dataTable.sorting.checkSorting(150, { get: criteria[index] }); // Tigger sort based on a handle handle.click(); page.detectChanges(); // Check the sorting page.dataTable.sorting.checkSorting(150, { get: criteria[index], descending: true }); }); } class DataTableDefinitionHosted extends Page { constructor(fixture) { super(fixture); } get dataTable() { return new RiskDataTableDefinition(this.debugElement.query(By.directive(RiskDataTableComponent)), this); } setData(data) { this.fixture.componentRef.setInput('data', data); this.detectChanges(); } setPageSize(size) { this.fixture.componentRef.setInput('pageSize', size); this.detectChanges(); } } class TestDataTableHostComponent { constructor() { this.data = input(TestDataTableHostComponent.defaultData, ...(ngDevMode ? [{ debugName: "data" }] : [])); this.pageSize = input(20, ...(ngDevMode ? [{ debugName: "pageSize" }] : [])); this.footer = signal(TestDataTableHostComponent.defaultData[0], ...(ngDevMode ? [{ debugName: "footer" }] : [])); this.defaultOrdering = [ { get: (record) => { return record.value3; }, descending: true }, (record) => { return record.value2; } ]; this.valueGetter = (record) => { return record.value1; }; } static { this.defaultData = (() => { const data = []; for (let i = 0; i < 500; i++) { data.push({ value1: Math.floor(secureRandom() * 20) + ' - value 1', value2: Math.floor(secureRandom() * 20) + ' - value 2', value3: Math.floor(secureRandom() * 20) + ' - value 3' }); } return data; })(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TestDataTableHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.15", type: TestDataTableHostComponent, isStandalone: true, selector: "ng-component", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: ` <risk-data-table [data]="data()" [footer]="footer()" [pageSize]="pageSize()" [defaultOrdering]="defaultOrdering" [striped]="true"> <risk-data-table-column title="Test Column 1" [sortingKey]="valueGetter"> <ng-template let-record="row" risk-data-table-cell> {{ record.value1 }} </ng-template> </risk-data-table-column> <risk-data-table-column> <ng-template risk-data-table-cell let-record="row" let-expanded="expanded"> <risk-data-table-detail-expander [expanded]="expanded"/> </ng-template> <ng-template risk-data-table-footer-cell let-footer="footer"> {{ footer.value1 }} </ng-template> </risk-data-table-column> <!-- Sub detail --> <risk-data-table-detail-row> <risk-data-table-column-group> <risk-data-table-column title="Test Detail Column 1" [sortingKey]="valueGetter"> <ng-template risk-data-table-cell let-record="row"> {{ record.value1 }} </ng-template> </risk-data-table-column> </risk-data-table-column-group> <risk-data-table-column-group> <risk-data-table-column title="Test Detail Column 2"> <ng-template risk-data-table-cell let-record="row"> {{ record.value2 }} </ng-template> </risk-data-table-column> <risk-data-table-column title="Test Detail Column 3"> <ng-template risk-data-table-cell let-record="row"> {{ record.value3 }} </ng-template> </risk-data-table-column> </risk-data-table-column-group> <risk-data-table-column-group/> </risk-data-table-detail-row> </risk-data-table>`, isInline: true, dependencies: [{ kind: "directive", type: RiskDataTableColumnCellDirective, selector: "[risk-data-table-cell]" }, { kind: "directive", type: RiskDataTableColumnDirective, selector: "risk-data-table-column", inputs: ["title", "sortingKey", "tooltip", "contentAlign", "headerAlign", "footerAlign", "class"] }, { kind: "directive", type: RiskDataTableColumnFooterDirective, selector: "[risk-data-table-footer-cell]" }, { kind: "directive", type: RiskDataTableColumnGroupDirective, selector: "risk-data-table-column-group" }, { kind: "component", type: RiskDataTableComponent, selector: "risk-data-table", inputs: ["footer", "pageSize", "striped", "showFooter", "allowEmpty", "trackByRowKey", "highlighting", "stickyHeader", "stickyHeaderOffsetTopPx", "data", "defaultOrdering"], exportAs: ["dataTable"] }, { kind: "directive", type: RiskDataTableRowDetailDirective, selector: "risk-data-table-detail-row" }, { kind: "component", type: RiskDataTableRowDetailExpanderComponent, selector: "risk-data-table-detail-expander", inputs: ["expanded"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TestDataTableHostComponent, decorators: [{ type: Component, args: [{ template: ` <risk-data-table [data]="data()" [footer]="footer()" [pageSize]="pageSize()" [defaultOrdering]="defaultOrdering" [striped]="true"> <risk-data-table-column title="Test Column 1" [sortingKey]="valueGetter"> <ng-template let-record="row" risk-data-table-cell> {{ record.value1 }} </ng-template> </risk-data-table-column> <risk-data-table-column> <ng-template risk-data-table-cell let-record="row" let-expanded="expanded"> <risk-data-table-detail-expander [expanded]="expanded"/> </ng-template> <ng-template risk-data-table-footer-cell let-footer="footer"> {{ footer.value1 }} </ng-template> </risk-data-table-column> <!-- Sub detail --> <risk-data-table-detail-row> <risk-data-table-column-group> <risk-data-table-column title="Test Detail Column 1" [sortingKey]="valueGetter"> <ng-template risk-data-table-cell let-record="row"> {{ record.value1 }} </ng-template> </risk-data-table-column> </risk-data-table-column-group> <risk-data-table-column-group> <risk-data-table-column title="Test Detail Column 2"> <ng-template risk-data-table-cell let-record="row"> {{ record.value2 }} </ng-template> </risk-data-table-column> <risk-data-table-column title="Test Detail Column 3"> <ng-template risk-data-table-cell let-record="row"> {{ record.value3 }} </ng-template> </risk-data-table-column> </risk-data-table-column-group> <risk-data-table-column-group/> </risk-data-table-detail-row> </risk-data-table>`, imports: [ RiskDataTableColumnCellDirective, RiskDataTableColumnDirective, RiskDataTableColumnFooterDirective, RiskDataTableColumnGroupDirective, RiskDataTableComponent, RiskDataTableRowDetailDirective, RiskDataTableRowDetailExpanderComponent ] }] }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }] } }); class RiskHighLighterDirectiveTestComponent { trackBy(index, row) { return row.rowData; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RiskHighLighterDirectiveTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: RiskHighLighterDirectiveTestComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` <div [risk-data-table-highlighter]="trackBy" [risk-data-table-highlighter-context]="context"></div>`, isInline: true, dependencies: [{ kind: "directive", type: RiskHighlighterDirective, selector: "[risk-data-table-highlighter]", inputs: ["risk-data-table-highlighter", "risk-data-table-highlighter-context"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RiskHighLighterDirectiveTestComponent, decorators: [{ type: Component, args: [{ template: ` <div [risk-data-table-highlighter]="trackBy" [risk-data-table-highlighter-context]="context"></div>`, imports: [RiskHighlighterDirective] }] }] }); class HighLighterDirectivePage extends Page { constructor(fixture) { super(fixture); } get highlightedElement() { return this.debugElement.query(By.directive(RiskHighlighterDirective)); } get classList() { return this.highlightedElement.nativeElement.classList; } get highlighter() { return this.highlightedElement.injector.get(RiskHighlighterDirective); } } // @dynamic class ByUtil { static and(...predicates) { return (val) => { return predicates.every((predicate) => predicate(val)); }; } static or(...predicates) { return (val) => { return predicates.some((predicate) => predicate(val)); }; } static not(predicate) { return (val) => { return !predicate(val); }; } } class RiskLayoutComponentDefinition { constructor(debugElement, page) { this.debugElement = debugElement; this.page = page; } get component() { return this.debugElement.componentInstance; } get headToolbar() { return this.debugElement.query(ByUtil.and(By.directive(MatToolbar), By.css('.mat-elevation-z2'))); } get logo() { return this.headToolbar.query(By.css('.risk-layout-logo')); } get menu() { return this.headToolbar.queryAll(ByUtil.or(By.css('[menu-horizontal]'), By.css('risk-layout-horizontal'))); } get sideNavContainer() { return this.debugElement.query(By.directive(MatSidenavContainer)); } get sideNav() { return this.sideNavContainer.query(By.directive(MatSidenav)); } get sideNavMenu() { return this.sideNav.queryAll(ByUtil.or(By.css('[menu-vertical]'), By.css('risk-layout-vertical'))); } get content() { return this.sideNavContainer.query(By.css('.risk-layout-content')); } openSideNav() { this.sideNav.componentInstance.open(); this.page.advanceAndDetectChanges(); } closeSideNav() { this.sideNav.componentInstance.close(); this.page.advanceAndDetectChanges(1); } } class RiskLayoutTestComponentHostPage extends Page { get layoutComponent() { return new RiskLayoutComponentDefinition(this.debugElement.query(By.directive(RiskLayoutComponent)), this); } } class RiskLayoutTestHostComponent { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RiskLayoutTestHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: RiskLayoutTestHostComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` <risk-layout> <!-- menu-horizontal --> <risk-layout-horizontal> HorizontalMenu </risk-layout-horizontal> <!-- menu-horizontal --> <!-- menu-vertical --> <risk-layout-vertical> Vertical menu </risk-layout-vertical> <span>Content</span> </risk-layout> `, isInline: true, dependencies: [{ kind: "component", type: RiskLayoutComponent, selector: "risk-layout", inputs: ["smallScreenMenuVisible", "smallScreenWidth", "footerVisible", "toolbarBackgroundColor", "backgroundColor"] }, { kind: "directive", type: RiskLayoutHorizontalDirective, selector: "risk-layout-horizontal" }, { kind: "directive", type: RiskLayoutVerticalDirective, selector: "risk-layout-vertical" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RiskLayoutTestHostComponent, decorators: [{ type: Component, args: [{ template: ` <risk-layout> <!-- menu-horizontal --> <risk-layout-horizontal> HorizontalMenu </risk-layout-horizontal> <!-- menu-horizontal --> <!-- menu-vertical --> <risk-layout-vertical> Vertical menu </risk-layout-vertical> <span>Content</span> </risk-layout> `, imports: [ RiskLayoutComponent, RiskLayoutHorizontalDirective, RiskLayoutVerticalDirective ] }] }] }); class LinkDefinition { constructor(page, link) { this.page = page; this.link = link; } get stub() { return this.link.injector.get(RouterLink); } get text() { return this.link.nativeElement.textContent.trim(); } click() { click(this.link.nativeElement); this.page.advanceAndDetectChanges(); } } class LinkOnlyPage extends Page { constructor(fixture) { super(fixture); } get link() { return new LinkDefinition(this, this.debugElement.query(By.directive(RouterLink))); } } class LoginMenuPage extends Page { get menuTrigger() { return this.debugElement.query(By.directive(MatMenuTrigger)); } clickMenuTrigger() { click(this.menuTrigger); this.waitForMenu(); } waitForMenu() { this.detectChanges(500); } get loginLink() { return new LinkDefinition(this, this.debugElement.query(ByUtil.and(By.directive(MatMenuItem), (value) => value.nativeElement.textContent.trim().endsWith('Login')))); } get logoutLink() { return new LinkDefinition(this, this.debugElement.query(ByUtil.and(By.directive(MatMenuItem), (value) => value.nativeElement.textContent.trim().endsWith('Logout')))); } } class RiskLoginPage extends Page { constructor(fixture) { super(fixture); } get formElement() { return this.debugElement.query(By.css('form')); } get usernameElement() { return this.formElement.query(By.css('input[name=username]')); } set username(username) { setNgModelValue(this.usernameElement, username); this.fixture.detectChanges(); } get passwordElement() { return this.formElement.query(By.css('input[name=password]')); } set password(password) { setNgModelValue(this.passwordElement, password); this.fixture.detectChanges(); } get loginButtonElement() { return this.formElement.query(By.directive(MatButton)); } get successMessage() { const debugElement = this.debugElement.query(ByUtil.and(By.directive(RiskMessageComponent), By.css(RISK_GOOD_SELECTOR))); if (!debugElement) { return null; } return new RiskMessageComponentDef(debugElement); } get errorMessage() { const debugElement = this.debugElement.query(ByUtil.or(ByUtil.and(By.directive(RiskMessageComponent), By.css(RISK_ERROR_SELECTOR)), By.css('h3'))); if (!debugElement) { return null; } return new RiskMessageComponentDef(debugElement); } clickLogin() { click(this.loginButtonElement.nativeElement); tick(); this.detectChanges(); } } class TestMessageHostComponent { constructor() { this.message = 'custom message'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TestMessageHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TestMessageHostComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` <risk-error [message]="message"/> <risk-good [message]="message"/> <risk-info [message]="message"/> <risk-message [message]="message"/> <risk-warn [message]="message"/> <risk-initial-load [message]="message"/> <risk-update-failed [message]="message"/> <risk-no-data [message]="message"/> `, isInline: true, dependencies: [{ kind: "component", type: RiskMessageComponent, selector: "risk-error, risk-good, risk-info, risk-message, risk-warn, risk-initial-load, risk-no-data, risk-update-failed", inputs: ["message"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TestMessageHostComponent, decorators: [{ type: Component, args: [{ template: ` <risk-error [message]="message"/> <risk-good [message]="message"/> <risk-info [message]="message"/> <risk-message [message]="message"/> <risk-warn [message]="message"/> <risk-initial-load [message]="message"/> <risk-update-failed [message]="message"/> <risk-no-data [message]="message"/> `, imports: [ RiskMessageComponent ] }] }] }); class MessageHostedPage extends Page { constructor(fixture) { super(fixture); } get error() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_ERROR_SELECTOR))); } get good() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_GOOD_SELECTOR))); } get info() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_INFO_SELECTOR))); } get message() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_MESSAGE_SELECTOR))); } get warn() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_WARN_SELECTOR))); } get initialLoad() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_INITIAL_LOAD_SELECTOR))); } get noData() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_NO_DATA_SELECTOR))); } get updateFailed() { return new RiskMessageComponentDef(this.debugElement.query(By.css(RISK_UPDATE_FAILED_SELECTOR))); } } const storage = { authRequestedPath: null }; class AuthRoutingFlowServiceStub { constructor() { this.authServiceStub = inject(AUTH_PROVIDER); this.authConfig = inject(AUTH_CONFIG); // cleanup storage.authRequestedPath = null; } get authFlow() { return this.authConfig.flow; } get authorizationCodeFlow() { return this.authFlow === AuthFlow.AUTHORIZATION_CODE; } get implicitFlow() { return this.authFlow === AuthFlow.IMPLICIT; } get hybridFlow() { return this.authFlow === AuthFlow.HYBRID; } get directFlow() { return this.authFlow === AuthFlow.DIRECT; } logout() { return this.authServiceStub.logout().pipe(defaultIfEmpty(0), map(() => true)); } login(username, password) { return this.authServiceStub.directLogin(username, password); } loginViaService() { return this.authServiceStub.loginViaAuthService(); } storeRequestedPath(state) { storage.authRequestedPath = state?.url || null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthRoutingFlowServiceStub, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthRoutingFlowServiceStub }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthRoutingFlowServiceStub, decorators: [{ type: Injectable }], ctorParameters: () => [] }); class AuthServiceStub { constructor() { this._loggedInStream = new ReplaySubjectExt(1); } get loggedIn() { return defer(() => of(!!this.user)); } get userProfile() { return defer(() => of(this.user ? { name: this.user } : undefined)); } get loggedInStream() { return this._loggedInStream.asObservable(); } loginViaAuthService() { return of(true); } checkLocationForLoginData() { return of(true); } directLogin(username, password) { this.user = username; this.emitLoginStatusChange(true); return of(true); } logout() { this.emitLoginStatusChange(false); delete this.user; return EMPTY; } emitLoginStatusChange(status) { if (this._loggedInStream.lastValue !== status) { this._loggedInStream.next(status); } } refreshToken() { } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthServiceStub, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthServiceStub }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthServiceStub, decorators: [{ type: Injectable }] }); const WELL_KNOWN = { endpoints: { auth: '/auth', token: '/token', logout: '/logout' }, issuer: 'risk-auth' }; class WellKnownServiceStub { get wellKnown() { return of(WELL_KNOWN); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WellKnownServiceStub, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WellKnownServiceStub }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: WellKnownServiceStub, decorators: [{ type: Injectable }] }); /** * A lightweight Router stub for testing that doesn't require actual routing infrastructure. * Use this when you want complete isolation from the Router and only need to verify navigation calls. */ class RouterStub { constructor() { this._url = '/'; this._events$ = new BehaviorSubject(null); } get url() { return this._url; } get events() { return this._events$.asObservable(); } get routerState() { return { snapshot: { url: this._url, root: {} } }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars navigate(commands, _extras) { this._url = commands.join('/'); return Promise.resolve(true); } // eslint-disable-next-line @typescript-eslint/no-unused-vars navigateByUrl(url, _extras) { this._url = typeof url === 'string' ? url : url.toString(); return Promise.resolve(true); } // eslint-disable-next-line @typescript-eslint/no-unused-vars createUrlTree(_commands, _navigationExtras) { return {}; } // eslint-disable-next-line @typescript-eslint/no-unused-vars serializeUrl(_url) { return ''; } // eslint-d