У меня есть модальный компонент, который я хотел бы протестировать. Но он всегда жалуется на «Экспорт имени 'ngbNav' не найден». Любые идеи были бы замечательными, как решить эту проблему.
Error: Export of name 'ngbNav' not found!
at cacheMatchingLocalNames (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13112:1)
at resolveDirectives (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:12871:1)
at elementStartFirstCreatePass (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:21229:1)
at ɵɵelementStart (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:21271:1)
at TemplateModalComponent_Template (ng:///TemplateModalComponent.js:381:9)
at executeTemplate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:12129:1)
at renderView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11899:1)
at renderComponent (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13509:1)
at renderChildComponents (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11700:1)
at renderView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11925:1)
template-modal.component. html
<div class="modal-header">
<h4 class="modal-title" ngbTooltip="{{template ? template.id : ''}}" triggers="click:blur">{{"template_edit" | translate}}</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form class="form" [formGroup]="this.templateFormGroup">
<div formGroupName="template">
<div class="row">
<div class="col-md-6">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Name</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="name" formControlName="name" required/>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Instrument Type</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="instrumentType" formControlName="instrumentType" value="DERIVATIVE" readonly />
</div>
</div>
</div>
</div>
<div class="row" *ngIf="isStandardTemplate">
<div class="col-md-6">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Publisher</label>
<div class="col-sm-9">
<select class="form-control" formControlName="publisherId" (change)="resetAdvertiser()">
<option [value]="null">{{"select-choose" | translate}}</option>
<option [value]="p.id" *ngFor="let p of publisher | orderBy:'name'">{{p.name}}</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group row">
<label class="col-sm-3 col-form-label">Advertiser</label>
<div class="col-sm-9">
<select class="form-control" formControlName="advertiserId" (change)="resetPublisher()">
<option [value]="null">{{"select-choose" | translate}}</option>
<option [value]="a.id" *ngFor="let a of advertiser | orderBy:'name'">{{a.name}}</option>
</select>
</div>
</div>
</div>
</div>
<div class="row justify-content-end mb-3" *ngIf="!isStandardTemplate">
<div class="col-3">
<button class="btn btn-secondary form-control" (click)="createStandardTemplateModal()">Standard Templates</button>
</div>
</div>
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs justify-content-center">
<li [ngbNavItem]="1">
<a ngbNavLink>Filter</a>
<ng-template ngbNavContent>
<ngb-alert type="secondary" *ngIf="!filter || filter.length == 0">Keine Filter vorhanden. Bitte anlegen!
</ngb-alert>
<div class="table-responsive" *ngIf="filter.length > 0">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Variable</th>
<th>{{"criteria" | translate}}</th>
<th>{{"comment" | translate}}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let f of filter | orderBy:'name'">
<td>{{f.name}}</td>
<td>{{f.templateVariableName}}</td>
<td><span class="badge badge-pill badge-secondary mr-1" *ngFor="let i of f.filterCriteria">
{{i.field}}
</span></td>
<td>{{f.comment}}</td>
<td>
<span class="prodo-icon prodo-delete-icon cursor-pointer float-right" (click)="deleteFilter(f.id)" ngbTooltip="{{'delete' | translate}}"></span>
<span class="prodo-icon prodo-edit-icon cursor-pointer float-right" *ngIf="f.id" ngbTooltip="{{'edit' | translate}}" (click)="chooseFilter(f.id)"></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="row">
<div class="col">
<button type="button" class="btn btn-secondary"
(click)="newFilter()">{{"filter_new" | translate}}</button>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink>HTML-Template</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-12">
<editor formControlName="html" required name="html" [init]="tinyMceConfig"></editor>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="3">
<a ngbNavLink>{{"disclaimer_page_end" | translate}}</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-12">
<editor formControlName="disclaimer" name="disclaimer" [init]="tinyMceConfig">
</editor>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="4">
<a ngbNavLink>{{"disclaimer_at_ad" | translate}}</a>
<ng-template ngbNavContent>
<div class="row">
<div class="col-md-12">
<editor formControlName="disclaimerJson" name="disclaimerJson" [init]="tinyMceConfig">
</editor>
</div>
</div>
</ng-template>
</li>
</ul>
</div>
</form>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="activeModal.dismiss()">Close</button>
<button type="button" class="btn btn-success" (click)="passBack()" [disabled]="!templateFormGroup.valid">Speichern
</button>
</div>
template-modal.component.ts
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {NgbActiveModal, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {FormBuilder, FormControl, FormGroup} from "@angular/forms";
import {StandardTemplateModalComponent} from "./standard-template-modal/standard-template-modal.component";
import {FilterModalComponent} from "./filter-modal/filter-modal.component";
import {Filter} from "../../../model/filter.model";
import {Publisher} from "../../../model/publisher.model";
import {Advertiser} from "../../../model/advertiser.model";
import {Template} from "../../../model/template.model";
import {PublisherService} from "../../../services/publisher.service";
import {AdvertiserService} from "../../../services/advertiser.service";
import {FilterService} from "../../../services/filter.service";
import {TemplateService} from "../../../services/template.service";
import {InstrumentAttributeService} from "../../../services/instrument-attribute.service";
import {StandardTemplate} from "../../../model/standard-template.model";
@Component({
selector: 'app-template-modal',
templateUrl: './template-modal.component.html',
styleUrls: ['./template-modal.component.scss']
})
export class TemplateModalComponent implements OnInit {
@Input()
public template;
@Input()
advertisementId: string;
@Input()
isStandardTemplate: boolean;
filter: Filter[] = [];
publisher: Publisher[];
advertiser: Advertiser[];
standardTemplates: Template[];
templateFormGroup: FormGroup;
usedInstrumentTypesInFilter: string[];
fieldsOfInstrumentType: any[] = new Array<string>();
tinyMceConfig: any;
@Output() passEntry: EventEmitter<any> = new EventEmitter();
active = 1;
constructor(
public activeModal: NgbActiveModal,
public publisherService: PublisherService,
public advertiserService: AdvertiserService,
private filterService: FilterService,
private templateService: TemplateService,
private instrumentAttributeService: InstrumentAttributeService,
private fb: FormBuilder,
private modalService: NgbModal) {
}
ngOnInit() {
this.loadData();
this.configureTinyMce();
}
createFormGroup() {
return this.fb.group({
template: this.fb.group(this.template),
standardTemplate: new FormControl('')
}
);
}
loadData() {
this.publisherService.get().subscribe((data: Publisher[]) => { this.publisher = data; });
this.advertiserService.get().subscribe((data: Advertiser[]) => { this.advertiser = data; });
if (!this.isStandardTemplate) {
this.templateService.getSuitableStandardTemplates(this.advertisementId)
.subscribe( data => {
this.standardTemplates = data;
});
}
this.templateFormGroup = this.createFormGroup();
this.filterService.get(this.template.id)
.subscribe( data => {
this.filter = data;
this.usedInstrumentTypesInFilter = this.filter.map( f => f.zertartId).filter((el, i, a) => i === a.indexOf(el));
for ( const instrumentType of this.usedInstrumentTypesInFilter) {
this.filterService.getFilterCriteria(instrumentType).subscribe( fields => {
this.fieldsOfInstrumentType[instrumentType] = fields;
});
}
});
}
createStandardTemplateModal() {
const modalRef = this.modalService.open(StandardTemplateModalComponent, { centered: true, size: 'lg' });
modalRef.componentInstance.advertisementId = this.advertisementId;
modalRef.result.then((result: StandardTemplate) => {
if (result) {
// Updating form with values from Standard Template
this.templateFormGroup.get("template").patchValue(result);
// Result Object for saving
const template = this.createTemplateResult();
this.templateService.putTemplate(this.advertisementId, template).subscribe( t => {
// Template is saved, now fetch all filter and add them to the template
this.filterService.get(result.id).subscribe( data => {
for (const f of data) {
// setting the new template id of the filter and remove its id, as it will receive a new one
f.abstractTemplateId = t.id;
f.id = null;
this.filterService.post(f).subscribe( _ => this.activeModal.close() );
}
});
});
}
});
}
newFilter() {
const modalRef = this.modalService.open(FilterModalComponent, { centered: true, size: 'xl' });
modalRef.componentInstance._templateId = this.template.id;
modalRef.result.then((result: Filter) => {
if (result) {
this.filterService.post(result).subscribe(_ => {
this.loadData();
});
}
});
}
deleteFilter(filterId: string) {
this.filterService.delete(filterId).subscribe( _ => {
this.loadData();
});
}
chooseFilter(filterId: string) {
const modalRef = this.modalService.open(FilterModalComponent, { centered: true, size: 'xl' });
modalRef.componentInstance.filterId = filterId;
modalRef.result.then((result: Filter) => {
if (result) {
this.filterService.put(filterId, result).subscribe(_ => {
this.loadData();
});
}
});
}
configureTinyMce() {
const that = this;
this.tinyMceConfig = {
menubar: false,
branding: false,
height: 300,
base_url: '/tinymce',
suffix: '.min',
inline: false,
valid_elements: '*[*]',
plugins: [
'advlist lists link image directionality',
'searchreplace visualblocks visualchars media table paste pagebreak code'
],
toolbar: 'filterbutton filterIterationButton | undo redo formatselect table | bold italic strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat hr pagebreak code',
table_resize_bars: false,
setup: function (editor) {
const test = that.filter.map(f => {
if ( f.maximumNumberOfResults <= 1) {
return {
type: 'nestedmenuitem',
text: f.templateVariableName,
icon: 'template',
getSubmenuItems: function () {
return f.allowedFields.map(field => {
return {
type: 'menuitem',
text: field,
icon: 'paste-text',
onAction: function () {
editor.insertContent(
'<span th:text="${' + f.templateVariableName + '.' + field + '}">' + f.templateVariableName + '.' + field + '</span>'
);
}
};
});
}
};
} else {
return [];
}
});
const filterIteration = that.filter.map(f => {
if (f.maximumNumberOfResults > 1) {
return {
type: 'nestedmenuitem',
text: f.templateVariableName,
icon: 'template',
getSubmenuItems: function() {
const filterWithMultipleResults = (element: Filter) => element.maximumNumberOfResults > 1;
if ( that.filter.some(filterWithMultipleResults) ) {
return f.allowedFields.map( field => { return {
type: 'menuitem',
text: field,
icon: 'paste-text',
onAction: function () {
const iteration =
'<table>' +
'<tr th:each="item: ${' + f.templateVariableName + '}">' +
'<td th:text="${item.' + field + '}" />' +
'</tr>' +
'</table>';
console.log("inserting:");
console.log(iteration);
editor.insertContent(iteration);
}
}; });
} else {
return [];
}
}
};
} else {
return [];
}
});
/* example, adding a toolbar menu button */
editor.ui.registry.addMenuButton('filterbutton', {
text: 'Filter',
fetch: function (callback) {
callback(test);
}
});
editor.ui.registry.addMenuButton('filterIterationButton', {
text: 'Filter Iteration',
fetch: function (callback) {
callback(filterIteration);
}
});
},
image_advtab: true,
imagetools_toolbar: 'rotateleft rotateright | flipv fliph | editimage imageoptions',
paste_data_images: !0,
importcss_append: !0,
images_upload_handler: function (e, t, a) {
t('data:' + e.blob().type + ';base64,' + e.base64());
},
};
}
resetAdvertiser() {
this.templateFormGroup.get("template").get("advertiserId").reset();
}
resetPublisher() {
this.templateFormGroup.get("template").get("publisherId").reset();
}
createTemplateResult() {
let result;
if (this.isStandardTemplate) {
result = Object.assign({}, this.templateFormGroup.get('template').value) as StandardTemplate;
} else {
result = Object.assign({}, this.templateFormGroup.get('template').value) as Template;
}
result = Object.assign({}, result);
return result;
}
passBack() {
const result = this.createTemplateResult();
this.activeModal.close(result);
}
}
В моем тестовом файле я иду следующим образом:
import {async, TestBed} from '@angular/core/testing';
import {TemplateModalComponent} from './template-modal.component';
import {HttpClient} from "@angular/common/http";
import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
import {RouterTestingModule} from "@angular/router/testing";
import {TranslateLoader, TranslateModule} from "@ngx-translate/core";
import {HttpLoaderFactory} from "../../../app.module";
import {FormBuilder, FormsModule, NgForm} from "@angular/forms";
import {NgbActiveModal, NgbModal, NgbNav, NgbNavConfig} from "@ng-bootstrap/ng-bootstrap";
import {AdslotService} from "../../../services/adslot.service";
import {ErrorHandlingService} from "../../../services/error-handling.service";
import {ToastService} from "../../../services/toast.service";
describe('TemplateModalComponent', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
],
declarations: [ TemplateModalComponent ],
providers: [
FormsModule,
NgbActiveModal,
AdslotService,
ErrorHandlingService,
ToastService,
FormBuilder,
]
}).compileComponents();
// Inject the http service and test controller for each test
httpClient = TestBed.get(HttpClient);
httpTestingController = TestBed.get(HttpTestingController);
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(TemplateModalComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
});
Я очень признателен за любые подсказки или действия по отладке. Ура, Майк