Angular 9 Тест компонентов: экспорт с именем ngbNav не найден - PullRequest
0 голосов
/ 29 мая 2020

У меня есть модальный компонент, который я хотел бы протестировать. Но он всегда жалуется на «Экспорт имени '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">&times;</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();
  });
});

Я очень признателен за любые подсказки или действия по отладке. Ура, Майк

1 Ответ

2 голосов
/ 29 мая 2020

Вы должны импортировать NgbNavModule

imports: [NgbNavModule],
...