ngneat / until-destroy

🦊 RxJS operator that unsubscribe from observables on destroy
https://netbasal.com/
MIT License
1.74k stars 100 forks source link

Angular 14 ut could not run correctly with a minimal reproducible example #206

Closed constCD closed 2 years ago

constCD commented 2 years ago

test-until-destroy.directive.ts: `import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core'; import { Dropdown } from 'primeng/dropdown/dropdown'; import { fromEvent } from 'rxjs'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { debounceTime, filter } from 'rxjs/operators'; import { KeyBoardName } from '@type/common.types';

@UntilDestroy() @Directive({ selector: '[appTestUntilDestroy]', }) export class TestUntilDestroyDirective implements AfterViewInit { @Input() appDropdownAssistant: Dropdown; private mutationOb: MutationObserver;

constructor(private el: ElementRef) {}

ngAfterViewInit(): void { this.el.nativeElement.querySelector('div.s-p-dropdown input')?.removeAttribute('readonly'); this.registerInputEvents();

this.mutationOb = new MutationObserver(() => {
  if (this.appDropdownAssistant.overlayVisible) {
    this.callbackWhenOverlayVisible();
  }
});

this.mutationOb.observe(this.appDropdownAssistant.accessibleViewChild.nativeElement, {
  attributes: true,
  attributeFilter: ['aria-expanded'],
});

}

private callbackWhenOverlayVisible(): void { this.registerFilterInputEvents(); }

private registerFilterInputEvents(): void { if (!this.appDropdownAssistant.filter) { return; }

const filterInput = this.appDropdownAssistant.filterViewChild.nativeElement;

fromEvent(filterInput, 'input')
  .pipe(untilDestroyed(this), debounceTime(300))
  .subscribe((_event: Event) => {
    this.appDropdownAssistant.cd.detectChanges();
  });

fromEvent(filterInput, 'keydown')
  .pipe(
    untilDestroyed(this),
    filter((event: KeyboardEvent) => event.key === KeyBoardName.ENTER)
  )
  .subscribe((event: Event) => {
    event.stopPropagation();
    this.appDropdownAssistant.hide();
    this.el.nativeElement.querySelector('div.s-p-dropdown input')?.focus();
  });

}

private registerInputEvents(): void { const input = this.appDropdownAssistant.accessibleViewChild.nativeElement; input.removeAttribute('role');

fromEvent(input, 'keydown')
  .pipe(
    untilDestroyed(this),
    filter((event: KeyboardEvent) => (event.code === KeyBoardName.ENTER || event.key === KeyBoardName.ARROW_DOWN) && !event.repeat)
  )
  .subscribe(() => {
    this.appDropdownAssistant.show();
    this.appDropdownAssistant.cd.detectChanges();
  });

} } test-until-destroy.directive.spec.ts: import * as rxjs from 'rxjs'; import { KeyBoardName } from '@type/common.types'; import { ElementRef } from '@angular/core'; import { TestUntilDestroyDirective } from '@directive/accessibility/test-until-destroy.directive';

describe('TestUntilDestroyDirective', () => { let div: Element; let mockAppDropdownAssistant: any;

beforeEach(() => { let html = <div class="p-dropdown"><div class="p-hidden-accessible"><input aria-expanded="false" /></div></div>; html += <div class="p-dropdown-panel"><input class="p-dropdown-filter" /><ul class="p-dropdown-items">; html += <li class="p-dropdown-item" aria-label="item" aria-selected="true">item</li></ul></div>;

div = document.createElement('div');
div.innerHTML = html;

mockAppDropdownAssistant = {
  overlay: div.querySelector('.p-dropdown-panel'),
  overlayVisible: true,
  filter: false,
  accessibleViewChild: {
    nativeElement: div.querySelector('.p-hidden-accessible > input'),
  },
  filterViewChild: {
    nativeElement: div.querySelector('input.p-dropdown-filter'),
  },
  optionsToDisplay: [],
  onFilter: jest.fn(),
  show: jest.fn(),
  hide: jest.fn(),
  focus: jest.fn(),
  onInputFocus: jest.fn(),
  onInputBlur: jest.fn(),
  selectItem: jest.fn(),
  cd: {
    detectChanges: jest.fn(),
  },
  updateSelectedOption: jest.fn(),
};

jest.spyOn(rxjs, 'fromEvent').mockReturnValue(rxjs.of({}));

});

it('registerFilterInputEvents -> filter is true -> keydown event', async () => { jest.spyOn(rxjs, 'fromEvent').mockReturnValue(rxjs.of({ key: KeyBoardName.ENTER, stopPropagation: jest.fn() } as any));

mockAppDropdownAssistant.filter = true;
const directive = new TestUntilDestroyDirective(new ElementRef<any>(div));
directive.appDropdownAssistant = mockAppDropdownAssistant;
directive.ngAfterViewInit();

await div.querySelector('.p-hidden-accessible > input')?.setAttribute('aria-expanded', 'true');
expect(directive.appDropdownAssistant.hide).toHaveBeenCalled();

}); }); `