Ошибка углового теста - Невозможно привязать «элементы», так как это не известное свойство «app-dropdown» - PullRequest
0 голосов
/ 16 ноября 2018

Просто хочу сказать, что я признаю, что есть много сообщений SO, связанных с ошибками "Не удается связать с X, поскольку это не известное свойство Y".Я просмотрел тонну из них и нашел ряд ответов, которые решают конкретные проблемы, но у меня возникли проблемы с переводом на мой случай, который, на самом деле, я считаю довольно общим и связан с фундаментальным неправильным пониманиемкак я должен решать свой вариант использования.

Я создаю приложение Angular (7), которое я разделил на components и routes.components - это модульные элементы (выпадающие списки, модалы, кнопки и т. Д.), А маршруты - это отдельные страницы в приложении.Термин немного запутанный, потому что оба являются технически угловатыми компонентами.Другими словами, структура (в пределах src/) выглядит следующим образом:

- app
  - components
    - dropdown
      - dropdown.component.ts
      - dropdown.component.html
      - dropdown.component.scss
      - dropdown.component.spec.ts
  - routes
    - library
      - library.component.ts
      - library.component.html
      - library.component.scss
      - library.component.spec.ts
  ...

Итак, у меня есть маршрут к библиотеке, который является просто угловым компонентом, который импортирует компонент Dropdown и выглядит следующим образом:

import { Component, OnInit } from '@angular/core';

import { DropdownComponent } from '../../components/dropdown/dropdown.component';

@Component({
  selector: 'app-library',
  templateUrl: './library.component.html',
  styleUrls: ['./library.component.scss']
})
export class LibraryComponent implements OnInit {
  pickItem($event) {
    console.log($event.item, $event.index);
  }

  constructor() { }

  ngOnInit() {}
}

Соответствующий HTML-файл библиотеки:

<div class="library row py4">
  <h3 class="typ--geo-bold typ--caps mb5">Style Library</h3>

  <div class="mb4" style="max-width: 35rem;">
    <p class="typ--geo-bold typ--caps">Dropdowns</p>
    <app-dropdown
      [items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Default dropdown"
    ></app-dropdown>
    <br />
    <app-dropdown
      [items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Inline dropdown"
      class="dropdown--inline"
    ></app-dropdown>
  </div>
</div>

Компонент раскрывающегося списка является базовым компонентом, имеющим аналогичную структуру.Я не буду вставлять это здесь, если не спросят, потому что я не уверен, что это будет добавка.(Достаточно сказать, что принимает items в качестве входных данных - относится к приведенной ниже ошибке).

Это прекрасно работает в браузере и корректно создается в рабочей среде.

Когда я запускаю тест library.components.spec.ts, я сталкиваюсь со следующей ошибкой:

Failed: Template parse errors:
Can't bind to 'items' since it isn't a known property of 'app-dropdown'.
1. If 'app-dropdown' is an Angular component and it has 'items' input, then verify that it is part of this module.
2. If 'app-dropdown' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("
    <p class="typ--geo-bold typ--caps">Dropdowns</p>
    <app-dropdown
      [ERROR ->][items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Default dropdown"

Вот базовый файл спецификации библиотеки:

import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LibraryComponent } from './library.component';

describe('LibraryComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule],
      declarations: [LibraryComponent],
    }).compileComponents();
  }));

  it('should create the component', () => {
    const fixture = TestBed.createComponent(LibraryComponent);
    const lib = fixture.debugElement.componentInstance;
    expect(lib).toBeTruthy();
  });
});

Ни то, ни другоеКомпонент Library или компонент Dropdown имеют связанные модули.Насколько я понимаю, эта ошибка может быть связана с тем, что я не импортировал компонент Dropdown в модуль Library или что-то еще, но

  • A) Я неконечно, как это сделать,
  • B) Тогда я не уверен, как включить этот модуль в приложение в целом, и
  • C) Я не уверен, какова ценность этого, кроме как заставить тест работать.

Есть ли способ заставить тест работать без преобразования этих компонентов в модули?Должны ли все компоненты маршрута быть модулями?

РЕДАКТИРОВАТЬ

Добавление выпадающего компонента, если необходимо:

<div
  class="dropdown"
  [ngClass]="{ open: open }"
  tab-index="-1"
  [class]="class"
  #dropdown
>
  <div class="dropdown__toggle" (click)="onTitleClick(dropdown)">
    <span class="dropdown__title">{{ finalTitle() }}</span>
    <span class="dropdown__icon icon-arrow-down"></span>
  </div>
  <div *ngIf="open" class="dropdown__menu">
    <ul>
      <li
        *ngFor="let item of items; let ind = index"
        class="dropdown__item"
        [ngClass]="{ selected: selectedIndex === ind }"
        (click)="onItemClick(dropdown, item, ind)"
      >
        <span class="dropdown__label">{{ item }}</span>
      </li>
    </ul>
  </div>
</div>

И:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html',
  styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnInit {

  @Input() items: Array<string>;
  @Input() public selectedIndex: number = null;
  @Input() public title = 'Select one';
  @Input() public open = false;
  @Input() public class = '';

  @Output() pick = new EventEmitter();

  constructor() { }

  ngOnInit() {}

  finalTitle () {
    return typeof this.selectedIndex === 'number'
      ? this.items[this.selectedIndex]
      : this.title;
  }

  onItemClick(dropdown, item, index) {
    this._blur(dropdown);
    this.selectedIndex = index;
    this.open = false;
    this.pick.emit({ item, index });
  }

  onTitleClick(dropdown) {
    this._blur(dropdown);
    this.open = !this.open;
  }

  _blur(dropdown) {
    if (dropdown && dropdown.blur) { dropdown.blur(); }
  }
}

Ответы [ 2 ]

0 голосов
/ 17 ноября 2018

Обновлено, чтобы более четко ответить на ваши 3 вопроса

A) Есть много способов сделать это без создания новых модулей.Вы можете просто импортировать DropdownComponent в свой тестовый модуль, как рекомендует Андре ниже.В этом ответе я обрисую еще один метод, который заглушает DropdownComponent, просто для тестирования LibraryComponent.Надеюсь, это ответит на вопрос «как это сделать».

B) Заглушка, которую я предлагаю, не является модулем - это всего лишь компонент.Нет необходимости включать заглушку в приложение в целом, единственная цель - протестировать LibraryComponent.

C) Ценность этой заглушки заключается только в проверке LibraryComponent, поэтому рекомендуется сохранить ее как можно более простой.

Это одна изспособ заставить тест работать без преобразования компонентов в модули.

Создание заглушки DropdownComponent:

Ниже приведен метод для заглушки DropdownComponent, который вы пытаетесь использовать внутри LibraryComponent.Ваша ошибка, которую вы подробно описали в своем вопросе, напрямую связана с тем, что у вас не определен селектор «app-dropdown», но ваш LibraryComponent пытается привязаться к нему.Предполагая, что вы хотите протестировать только LibraryComponent в файле library.component.spec.ts, я предлагаю добавить функциональность DropdownComponent, а не импортировать фактический компонент в тест.Я создал Stackblitz , чтобы показать, что я имею в виду.

Из Stackblitz приведен фрагмент из файла library.component.spec.ts:

@Component({
    selector: 'app-dropdown',
    template: `<h5>Dropdown</h5>`
})
class TestDropdownComponent {
    @Input() items;
    @Input() title;
    @Output() pick = new EventEmitter<any>();
}

describe('LibraryComponent', () => {
    let component: LibraryComponent;
    let fixture: ComponentFixture<LibraryComponent>;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [ RouterTestingModule ],
            declarations: [ 
                TestDropdownComponent,
                LibraryComponent
            ]
        }).compileComponents();
        fixture = TestBed.createComponent(LibraryComponent);
    }));
    it('should create the component', () => {
      const fixture = TestBed.createComponent(LibraryComponent);
      const lib = fixture.debugElement.componentInstance;
      expect(lib).toBeTruthy();
    });
});

Примечание. Если вы хотите воспроизвести ошибку, подробно описанную в вашем вопросе, в Stackblitz, просто закомментируйте строку для TestDropdownComponent в объявлениях TestBed, чтобы она выглядела так:

TestBed.configureTestingModule({
    imports: [ RouterTestingModule ],
    declarations: [ 
        // TestDropdownComponent,
        LibraryComponent
    ]
}).compileComponents();

И вы снова будете тестировать только свой компонент без макета / заглушки DropdownComponent для привязки.

0 голосов
/ 17 ноября 2018

Вам необходимо импортировать DropdowmComponent на ваш тестовый модуль в разделе объявлений

...