单元测试ViewChild和MockedDirective阻止它被初始化



当运行单元测试以查看ViewChild是否已定义时,它无法初始化。只有当我在组件上使用了*appUserProfile指令时,才会发生这种情况。

否则单元测试将通过,并且ViewChild将被定义。请帮助。

<div id="user" *appUserProfile="[ Profile.GAPGENAC ]" style="text-align: right">
<div class="page-title-div">
<mat-label class="page-title">View Roster</mat-label>
</div>
<div style="width: 100%; margin: 0 auto">
<div class="view-roster-controls">
<div id="filter-text-box-container">
<img
id="view-roster-search-icon"
src="../../assets/icons/search-outline.svg"
class="search-icon"
alt="search"
/>
<input
id="filter-text-box"
aria-label="Filter"
(input)="onFilterTextBoxChanged()"
type="text"
placeholder="Search the Roster"
/>
</div>
<div id="right-controls-container">
<button
id="view-roster-export"
(click)="onBtnExport()"
value="Export to CSV"
>
<img
id="view-roster-export-icon"
src="../../assets/icons/download-outline.svg"
class="export-icon"
alt="search"
/>
<p id="export-button-text">Export to CSV</p>
</button>
<div class="roster-controls-vertical-break"></div>
<div id="display-columns-container">
<div style="display: flex; height: 100%">
<img
id="view-roster-columns-icon"
src="../../assets/icons/columns.svg"
class="columns-icon"
alt="column"
/>
<p id="displayed-columns-text">Display Columns</p>
</div>
<mat-form-field id="invisible-display-columns" appearance="fill">
<mat-label for="displayColumn" id="mat-lbl-display-col"
>Displayed Columns</mat-label
>
<mat-select
multiple
[formControl]="selectFormControl"
role="combobox"
id="mat-sel-display-col"
aria-label="select"
aria-expanded="false">
<mat-option
role="option"
for="displayColumn"
*ngFor="let column of columnDefs"
[value]="column.field"
[attr.aria-label]="column.headerName"
aria-disabled="true"
(onSelectionChange)="selectionChange($event)"
>{{ column.headerName }}</mat-option>
</mat-select>
<div id="panel"></div>
</mat-form-field>
</div>
</div>
</div>
<ag-grid-angular
id="member-roster"
#memberRosterGrid
style="width: 100%"
class="ag-theme-alpine"
[columnDefs]="columnDefs"
[rowData]="rowData$ | async"
[defaultColDef]="defaultColDef"
[pagination]="true"
[paginationPageSize]="15"
[unSortIcon]="true"
(gridReady)="onGridReady()"
(cellValueChanged)="onCellValueChanged()"
(displayedColumnsChanged)="onDisplayedColumnsChanged($event)"
(columnVisible)="oncolumnVisible()"
(sortChanged)="onSortChanged()"
(filterChanged)="onFilterChanged()"
(paginationChanged)="onPaginationChanged()"
(bodyScroll)="onBodyScroll()"
>
</ag-grid-angular>
</div>
</div>
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Input, Directive, OnInit } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AgGridModule } from 'ag-grid-angular';
import { Observable } from 'rxjs';
import { UserProfileDirective } from 'src/app/directives/user-profile-directive';
import { Profile } from 'src/app/models/profile';
import { MaterialModule } from '../../material/material.module';
import { MemberRoster } from '../../models/memberroster.model';
import { AuthenticationService } from '../../services/authentication.service';
import { MemberService } from '../../services/member.service';
import { MemberrosterComponent } from './memberroster.component';
@Directive({ selector: '[appUserProfile]' })
export class MockUserProfileDirective implements OnInit {
@Input() appUserProfile: Profile[];
ngOnInit() {
console.log('mocked Directive: ', this.appUserProfile);
}
}
fdescribe('MemberRosterComponent', () => {
let component: MemberrosterComponent;
let fixture: ComponentFixture<MemberrosterComponent>;
let authServiceMock: AuthenticationService;
let authServiceSpyObj = jasmine.createSpyObj('AuthenticationService', ['isAuthenticated']);
let memberServiceSpyObj = jasmine.createSpyObj('MemberService', ['getMembers']);
memberServiceSpyObj.getMembers.and.returnValue(new Observable<MemberRoster[]>());
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
MemberrosterComponent,
MockUserProfileDirective
],
imports: [
HttpClientTestingModule,
AgGridModule,
MaterialModule,
BrowserAnimationsModule,
ReactiveFormsModule
],
providers: [
{ provide: AuthenticationService, useValue: authServiceSpyObj },
{ provide: MemberService, useValue: memberServiceSpyObj }
]
})
.compileComponents();
fixture = TestBed.createComponent(MemberrosterComponent);
component = fixture.componentInstance;
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
expect(component.rowData$).toBeDefined();
});
fit('grid API is available after `detectChanges`', fakeAsync(() => {
// Setup template bindings and run ngOInit. This causes the <ag-grid-angular> component to be created.
// As part of the creation the grid apis will be attached to the gridOptions property.
fixture.detectChanges();
flush();
console.log('component.grid: ', component.grid);
expect(component.grid.api).toBeDefined();
}));
});
import { Component, ViewChild, OnInit, ViewEncapsulation } from "@angular/core";
import { Router } from "@angular/router";
import { ILdapUser } from "../../models/ldap-user";
import { AuthenticationService } from "../../services/authentication.service";
import { MemberService } from "../../services/member.service";
import { MemberRoster } from "../../models/memberroster.model";
import { ColDef, ICellRendererParams, DisplayedColumnsChangedEvent } from "ag-grid-community";
import { AgGridAngular } from "ag-grid-angular";
import { FormControl } from "@angular/forms";
import { MatOptionSelectionChange } from "@angular/material/core";
import { Profile } from '../../models/profile';
import { Observable } from "rxjs";
@Component({
selector: "app-memberroster",
templateUrl: "./memberroster.component.html",
styleUrls: ["./memberroster.component.css"],
encapsulation: ViewEncapsulation.None,
})
export class MemberrosterComponent implements OnInit {
@ViewChild("memberRosterGrid") grid!: AgGridAngular;
user: ILdapUser;
rowData$: Observable<MemberRoster[]>;
userName: string;
groupNumber: string;
Profile = Profile;
constructor(
public authService: AuthenticationService,
public memberService: MemberService,
private router: Router
) { }
ngOnInit() {
console.log('ngOnInit called');
const ariaHiddenControls = document.querySelectorAll(
'[aria-hidden="true"]'
);
ariaHiddenControls.forEach((b) => b.setAttribute("tabindex", "-1"));
this.rowData$ = this.memberService.getMembers();
const pageControls = document.getElementById("ag-25-row-count");
if (pageControls) {
pageControls.insertAdjacentText("afterend", " records");
}
}
columnDefs: ColDef[] = [
{
field: "health_id",
headerName: "Member ID",
cellClass: "cellAlignment"
},
{
field: "last_name",
sort: "asc",
sortIndex: 0,
headerName: "Last Name",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
},
width: 160
},
{
field: "first_name",
headerName: "First Name",
filter: "agTextColumnFilter",
cellClass: "cellAlignment",
filterParams: {
buttons: ["clear", "apply"],
},
width: 160
},
{
field: "birth_date",
headerName: "Date of Birth",
comparator: this.dateComparator,
filter: "agDateColumnFilter",
cellClass: "cellAlignment",
filterParams: {
buttons: ["clear", "apply"],
comparator: (filterLocalDateAtMidnight, cellValue) => {
const cellDate = new Date(cellValue);
if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
return 0;
}
if (cellDate < filterLocalDateAtMidnight) {
return -1;
}
if (cellDate > filterLocalDateAtMidnight) {
return 1;
}
},
}
},
{
field: "contract_type",
headerName: "Contract Type",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
},
{
field: "enrollment_status",
headerName: "Enrollment Status",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
},
cellRenderer: this.enrollmentStatusRenderer
},
{
field: "section_number",
headerName: "Section #",
cellClass: "cellAlignment",
filter: "agNumberColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
},
{
field: "section_name",
headerName: "Section Name",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
},
{
field: "section_description",
headerName: "Section Description",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
},
{
field: "cancel_date",
headerName: "Cancel Date",
comparator: this.dateComparator,
filter: "agDateColumnFilter",
cellClass: "cellAlignment",
filterParams: {
buttons: ["clear", "apply"],
comparator: (filterLocalDateAtMidnight, cellValue) => {
const cellDate = new Date(cellValue);
if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
return 0;
}
if (cellDate < filterLocalDateAtMidnight) {
return -1;
}
if (cellDate > filterLocalDateAtMidnight) {
return 1;
}
},
}
},
{
field: "department_number",
headerName: "Department",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
},
{
field: "employee_id",
headerName: "Employee ID",
cellClass: "cellAlignment",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["clear", "apply"],
}
}
];
public defaultColDef: ColDef = {
resizable: true,
sortable: true,
minWidth: 100,
width: 200,
icons: {
menu: "<img src='../../assets/icons/filter.svg' class='view-roster-filter-icon' alt='filter'>",
move: "<img id='view-roster-move-icon' src='../../assets/icons/move-outline.svg' class='move-icon' alt='move'>",
sortAscending: "<div class='sort-icon-container'>" +
"<img src='../../assets/icons/caret-up-outline-green.svg' class='sort-icon-top' alt='ascending sort'>" +
"<img src='../../assets/icons/caret-down-outline.svg' class='sort-icon-bottom' alt='ascending sort'>" +
"</div>",
sortDescending: "<div class='sort-icon-container'>" +
"<img src='../../assets/icons/caret-up-outline.svg' class='sort-icon-top' alt='ascending sort'>" +
"<img src='../../assets/icons/caret-down-outline-green.svg' class='sort-icon-bottom' alt='ascending sort'>" +
"</div>",
sortUnSort: "<div class='sort-icon-container'>" +
"<img src='../../assets/icons/caret-up-outline.svg' class='sort-icon-top' alt='ascending sort'>" +
"<img src='../../assets/icons/caret-down-outline.svg' class='sort-icon-bottom' alt='ascending sort'>" +
"</div>"
}
};
selectFormControl: FormControl = new FormControl(
this.columnDefs.filter((col) => !col.hide).map((col) => col.field)
);
selectionChange(event: MatOptionSelectionChange) {
if (event.isUserInput) {
this.grid.columnApi.applyColumnState({
state: [
{
colId: event.source.value,
hide: !event.source.selected,
},
],
});
}
}
ariaControlsReset() {
let controls = document.querySelectorAll(
'[aria-description="Press ENTER to sort. Press CTRL ENTER to open column menu."]'
);
controls.forEach((b) => b.removeAttribute("aria-description"));
controls = document.querySelectorAll(
'[aria-description="Press ENTER to sort."]'
);
controls.forEach((b) => b.removeAttribute("aria-description"));
document.querySelector('[id="mat-sel-display-col"]').setAttribute("aria-controls", "panel");
}
onGridReady() {
console.log('on grid ready');
this.ariaControlsReset();
}
onCellValueChanged() {
this.ariaControlsReset();
}
onDisplayedColumnsChanged(event: DisplayedColumnsChangedEvent) {
this.ariaControlsReset();
for (let col of event.columnApi.getColumns()) {
if (!(event.columnApi.getAllDisplayedColumns().includes(col))) {
this.columnDefs.filter(colDef => colDef.field === col.getColDef().field).forEach(colDef => colDef.hide = true);
}
else {
this.columnDefs.filter(colDef => colDef.field === col.getColDef().field).forEach(colDef => colDef.hide = false);
}
}
this.selectFormControl = new FormControl(
this.columnDefs.filter((col) => !col.hide).map((col) => col.field)
);
}
oncolumnVisible() {
this.ariaControlsReset();
}
onSortChanged() {
this.ariaControlsReset();
}
onFilterChanged() {
this.ariaControlsReset();
}
onPaginationChanged() {
this.ariaControlsReset();
}
onBodyScroll() {
this.ariaControlsReset();
}
dateComparator(date1, date2) {
const date1Number = date1 && new Date(date1).getTime();
const date2Number = date2 && new Date(date2).getTime();
if (date1Number === null && date2Number === null) {
return 0;
}
if (date1Number === null) {
return -1;
} else if (date2Number === null) {
return 1;
}
return date1Number - date2Number;
}
onFilterTextBoxChanged() {
this.grid.api.setQuickFilter(
(document.getElementById("filter-text-box") as HTMLInputElement).value
);
}
onBtnExport() {
this.grid.api.exportDataAsCsv();
}
enrollmentStatusRenderer(params: ICellRendererParams) {
let cssClass = "";
if (params.value === "Active")
cssClass = "member-roster-enrollment-status-active";
else if (params.value === "Cancelled")
cssClass = "member-roster-enrollment-status-cancelled";
else if (params.value === "Future")
cssClass = "member-roster-enrollment-status-future";
return "<div class=" + cssClass + ">" + params.value + "</div>";
}
}

问题是,我正在创建一个模拟指令,而不是一个结构指令。

一个带有星号的结构指令在DOM中生成了ng-template。这意味着你的模拟需要调用createEmbeddedView来正确渲染DOM。

因此我需要将mock更改为

@Directive({ selector: '[appUserProfile]' })
export class MockUserProfileDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {  }
@Input() set appUserProfile(profiles: Profile[]) {
this.viewContainer.createEmbeddedView(this.templateRef);
}
}

相关内容

  • 没有找到相关文章

最新更新