[ACS-6140] migrate tests to harness (#9071)

* migrate aspect list tests to harness

* [ci:force] categories management

* [ci:force] fix keyboard handling

* [ci:force] fix memory leak

* migrate document list

* migrate new version uploader dialog

* migrate folder dialog

* bug fixes for keyboard, remove fake unit tests

* [ci:force] migrate and cleanup notification tests

* migrate app details cloud

* migrate app list cloud

* migrate group cloud component

* [ci:force] cleanup code based on reviews

* [ci:force] migrate people cloud

* [ci:force] migrate process list cloud

* [ci:force] migrate start process cloud

* [ci:force] task form cloud

* [ci:force] service task list cloud

* [ci:force] task list cloud

* [ci:force] process attachment list and apps list

* [ci:force] code review changes

* [ci:force] app list bug fixes and code cleanup

* [ci:force] fix incorrect/missing typings, fix tests

* [ci:force] code cleanup
This commit is contained in:
Denys Vuika
2023-11-09 09:24:56 +00:00
committed by GitHub
parent 479cc8b545
commit f0a11fdab0
35 changed files with 941 additions and 1231 deletions

View File

@@ -12,7 +12,7 @@
(change)="$event.stopPropagation()"
/>
<button mat-button
<button
matSuffix
mat-icon-button
[attr.aria-label]="'SELECT_FILTER.BUTTON.ARIA_LABEL' | translate"

View File

@@ -19,163 +19,67 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '../../../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { SelectFilterInputComponent } from './select-filter-input.component';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Component, ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ESCAPE } from '@angular/cdk/keycodes';
import { MatSelect } from '@angular/material/select';
@Component({
selector: 'adf-test-filter',
template: `
<mat-select [(ngModel)]="field.value" [compareWith]="compare" [multiple]="multiple">
<adf-select-filter-input *ngIf="showInputFilter" (change)="onChange($event)"></adf-select-filter-input>
<mat-option *ngFor="let opt of options"
[value]="opt"
[id]="opt.id">{{opt.name}}
</mat-option>
</mat-select>
`
})
export class TestComponent {
@ViewChild(SelectFilterInputComponent) filterInputComponent: SelectFilterInputComponent;
field: any = { value : '' };
showInputFilter = true;
multiple = false;
standardOptions = [
{ id: '1', name: 'one' },
{ id: '2', name: 'two' },
{ id: '3', name: 'three' }
];
options = this.standardOptions;
compare(obj1, obj2) {
if (!obj1 || !obj2) {
return false;
}
return obj1.id === obj2.id;
}
onChange(search: string) {
if (!search) {
this.options = this.standardOptions;
} else {
this.options = this.standardOptions.filter(({ name }) => name.includes(search));
}
}
}
describe('SelectFilterInputComponent', () => {
let testFixture: ComponentFixture<TestComponent>;
let testComponent: TestComponent;
let fixture: ComponentFixture<SelectFilterInputComponent>;
let component: SelectFilterInputComponent;
let matSelect: MatSelect;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
NoopAnimationsModule
],
declarations: [
TestComponent
],
providers: [
MatSelect
]
imports: [TranslateModule.forRoot(), CoreTestingModule],
providers: [MatSelect]
});
fixture = TestBed.createComponent(SelectFilterInputComponent);
component = fixture.componentInstance;
matSelect = TestBed.inject(MatSelect);
fixture.detectChanges();
});
describe('component', () => {
it('should focus input on initialization', async () => {
spyOn(component.selectFilterInput.nativeElement, 'focus');
matSelect.openedChange.next(true);
beforeEach(() => {
fixture = TestBed.createComponent(SelectFilterInputComponent);
component = fixture.componentInstance;
matSelect = TestBed.inject(MatSelect);
fixture.detectChanges();
});
it('should focus input on initialization', async () => {
spyOn(component.selectFilterInput.nativeElement, 'focus');
matSelect.openedChange.next(true);
fixture.detectChanges();
await fixture.whenStable();
expect(component.selectFilterInput.nativeElement.focus).toHaveBeenCalled();
});
it('should clear search term on close', async () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
matSelect.openedChange.next(false);
fixture.detectChanges();
await fixture.whenStable();
expect(component.term).toBe('');
});
it('should emit event when value changes', async () => {
spyOn(component.change, 'next');
component.onModelChange('some-search-term');
expect(component.change.next).toHaveBeenCalledWith('some-search-term');
});
it('should reset value on reset() event', () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
component.reset();
expect(component.term).toBe('');
});
it('should reset value on Escape event', () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
component.selectFilterInput.nativeElement.dispatchEvent(new KeyboardEvent('keydown', {keyCode: ESCAPE} as any));
fixture.detectChanges();
expect(component.term).toBe('');
});
fixture.detectChanges();
await fixture.whenStable();
expect(component.selectFilterInput.nativeElement.focus).toHaveBeenCalled();
});
describe('testComponent', () => {
beforeEach(() => {
testFixture = TestBed.createComponent(TestComponent);
testComponent = testFixture.componentInstance;
});
it('should clear search term on close', async () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
afterEach(() => testFixture.destroy());
matSelect.openedChange.next(false);
it('should preserve the values for multiple search', async () => {
const userSelection = [{ id: '3', name: 'three' }];
const preSelected = [
{ id: '1', name: 'one' },
{ id: '2', name: 'two' }
];
testComponent.field.value = preSelected;
testComponent.multiple = true;
testFixture.detectChanges();
fixture.detectChanges();
await fixture.whenStable();
const dropdown: HTMLElement = testFixture.nativeElement.querySelector('.mat-select-trigger');
dropdown.click();
await testFixture.whenStable();
testFixture.detectChanges();
expect(component.term).toBe('');
});
const filter = testFixture.debugElement.query(By.css('input'));
filter.triggerEventHandler('input', { target: { value: 'three' } });
testFixture.detectChanges();
it('should emit event when value changes', async () => {
spyOn(component.change, 'next');
component.onModelChange('some-search-term');
expect(component.change.next).toHaveBeenCalledWith('some-search-term');
});
const option = testFixture.debugElement.query(By.css('mat-option'));
option.triggerEventHandler('click', null);
testFixture.detectChanges();
it('should reset value on reset() event', () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
expect(testComponent.field.value).toEqual([...preSelected, ...userSelection]);
});
component.reset();
expect(component.term).toBe('');
});
it('should reset value on Escape event', () => {
component.onModelChange('some-search-term');
expect(component.term).toBe('some-search-term');
component.selectFilterInput.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape' } as any));
fixture.detectChanges();
expect(component.term).toBe('');
});
});

View File

@@ -16,7 +16,6 @@
*/
import { Component, ViewEncapsulation, ViewChild, ElementRef, OnDestroy, Inject, Output, EventEmitter, OnInit } from '@angular/core';
import { ESCAPE, TAB } from '@angular/cdk/keycodes';
import { MatSelect } from '@angular/material/select';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -43,46 +42,40 @@ export class SelectFilterInputComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.change
.pipe(takeUntil(this.onDestroy$))
.subscribe((val: string) => this.term = val );
this.change.pipe(takeUntil(this.onDestroy$)).subscribe((val: string) => (this.term = val));
this.matSelect.openedChange
.pipe(takeUntil(this.onDestroy$))
.subscribe((isOpened: boolean) => {
if (isOpened) {
this.selectFilterInput.nativeElement.focus();
} else {
this.change.next('');
}
});
this.matSelect.openedChange.pipe(takeUntil(this.onDestroy$)).subscribe((isOpened: boolean) => {
if (isOpened) {
this.selectFilterInput.nativeElement.focus();
} else {
this.change.next('');
}
});
if (this.matSelect.ngControl) {
this.previousSelected = this.matSelect.ngControl.value;
this.matSelect.ngControl.valueChanges
.pipe(takeUntil(this.onDestroy$))
.subscribe((values) => {
let restoreSelection = false;
if (this.matSelect.multiple && Array.isArray(this.previousSelected)) {
if (!Array.isArray(values)) {
values = [];
this.matSelect.ngControl.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((values) => {
let restoreSelection = false;
if (this.matSelect.multiple && Array.isArray(this.previousSelected)) {
if (!Array.isArray(values)) {
values = [];
}
const options = this.matSelect.options.map((option) => option.value);
this.previousSelected.forEach((previous) => {
const isSelected = [...values, ...options].some((current) => this.matSelect.compareWith(current, previous));
if (!isSelected) {
values.push(previous);
restoreSelection = true;
}
const options = this.matSelect.options.map(option => option.value);
this.previousSelected.forEach((previous) => {
const isSelected = [...values, ...options].some(current => this.matSelect.compareWith(current, previous));
if (!isSelected) {
values.push(previous);
restoreSelection = true;
}
});
}
});
}
this.previousSelected = values;
if (restoreSelection) {
// eslint-disable-next-line no-underscore-dangle
this.matSelect._onChange(values);
}
});
this.previousSelected = values;
if (restoreSelection) {
// eslint-disable-next-line no-underscore-dangle
this.matSelect._onChange(values);
}
});
}
}
@@ -97,13 +90,13 @@ export class SelectFilterInputComponent implements OnInit, OnDestroy {
handleKeydown($event: KeyboardEvent) {
if (this.term) {
if ($event.keyCode === ESCAPE) {
event.stopPropagation();
if ($event.code === 'Escape') {
$event.stopPropagation();
this.change.next('');
}
if (($event.target as HTMLInputElement).tagName === 'INPUT' && $event.keyCode === TAB) {
event.stopPropagation();
if (($event.target as HTMLInputElement).tagName === 'INPUT' && $event.code === 'Tab') {
$event.stopPropagation();
}
}
}

View File

@@ -15,25 +15,24 @@
* limitations under the License.
*/
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { OverlayModule } from '@angular/cdk/overlay';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatSnackBar, MatSnackBarConfig, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSnackBarConfig, MatSnackBarModule } from '@angular/material/snack-bar';
import { NotificationService } from './notification.service';
import { TranslationService } from '../../translation/translation.service';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatIconHarness } from '@angular/material/icon/testing';
import { MatSnackBarHarness } from '@angular/material/snack-bar/testing';
@Component({
template: '',
providers: [NotificationService]
})
class ProvidesNotificationServiceComponent {
constructor(public notificationService: NotificationService) {
}
constructor(public notificationService: NotificationService) {}
sendMessageWithoutConfig() {
return this.notificationService.openSnackMessage('Test notification', 1000);
@@ -44,7 +43,7 @@ class ProvidesNotificationServiceComponent {
}
sendMessageWithArgs() {
return this.notificationService.openSnackMessage('Test notification {{ arg }}', 1000, {arg: 'arg'});
return this.notificationService.openSnackMessage('Test notification {{ arg }}', 1000, { arg: 'arg' });
}
sendCustomMessage() {
@@ -72,7 +71,7 @@ class ProvidesNotificationServiceComponent {
sendMessageWithDecorativeIcon() {
const notificationConfig = new MatSnackBarConfig();
notificationConfig.duration = 1000;
notificationConfig.data = {decorativeIcon: 'info'};
notificationConfig.data = { decorativeIcon: 'info' };
return this.notificationService.openSnackMessage('with decorative icon', notificationConfig);
}
@@ -84,154 +83,97 @@ class ProvidesNotificationServiceComponent {
return this.notificationService.openSnackMessageAction('with decorative icon', 'TestWarn', notificationConfig);
}
}
describe('NotificationService', () => {
let loader: HarnessLoader;
let fixture: ComponentFixture<ProvidesNotificationServiceComponent>;
let translationService: TranslationService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
CoreTestingModule,
OverlayModule,
MatSnackBarModule
],
declarations: [ProvidesNotificationServiceComponent],
providers: [
MatSnackBar,
LiveAnnouncer
]
imports: [TranslateModule.forRoot(), CoreTestingModule, MatSnackBarModule],
declarations: [ProvidesNotificationServiceComponent]
});
translationService = TestBed.inject(TranslationService);
fixture = TestBed.createComponent(ProvidesNotificationServiceComponent);
fixture.detectChanges();
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});
it('should translate messages', (done) => {
it('should translate messages', () => {
spyOn(translationService, 'instant').and.callThrough();
const promise = fixture.componentInstance.sendMessage();
promise.afterDismissed().subscribe(() => {
expect(translationService.instant).toHaveBeenCalled();
done();
});
fixture.componentInstance.sendMessage();
fixture.detectChanges();
expect(translationService.instant).toHaveBeenCalled();
});
it('should translate messages with args', (done) => {
it('should translate messages with args', () => {
spyOn(translationService, 'instant').and.callThrough();
const promise = fixture.componentInstance.sendMessageWithArgs();
promise.afterDismissed().subscribe(() => {
expect(translationService.instant).toHaveBeenCalledWith('Test notification {{ arg }}', {arg: 'arg'});
done();
});
fixture.componentInstance.sendMessageWithArgs();
fixture.detectChanges();
expect(translationService.instant).toHaveBeenCalledWith('Test notification {{ arg }}', { arg: 'arg' });
});
it('should translate the action', (done) => {
it('should translate the action', () => {
spyOn(translationService, 'instant').and.callThrough();
const promise = fixture.componentInstance.sendMessageAction();
promise.afterDismissed().subscribe(() => {
expect(translationService.instant).toHaveBeenCalledTimes(2);
done();
});
fixture.componentInstance.sendMessageAction();
fixture.detectChanges();
expect(translationService.instant).toHaveBeenCalledTimes(2);
});
it('should open a message notification bar', (done) => {
const promise = fixture.componentInstance.sendMessage();
promise.afterDismissed().subscribe(() => {
done();
});
it('should open a message notification bar', async () => {
fixture.componentInstance.sendMessage();
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar without custom configuration', (done) => {
const promise = fixture.componentInstance.sendMessageWithoutConfig();
promise.afterDismissed().subscribe(() => {
done();
});
it('should open a message notification bar without custom configuration', async () => {
fixture.componentInstance.sendMessageWithoutConfig();
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar with custom configuration', (done) => {
const promise = fixture.componentInstance.sendCustomMessage();
promise.afterDismissed().subscribe(() => {
done();
});
it('should open a message notification bar with custom configuration', async () => {
fixture.componentInstance.sendCustomMessage();
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar with action', (done) => {
const promise = fixture.componentInstance.sendMessageAction();
promise.afterDismissed().subscribe(() => {
done();
});
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
it('should open a message notification bar with action', async () => {
fixture.componentInstance.sendMessageAction();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar with action and custom configuration', (done) => {
const promise = fixture.componentInstance.sendCustomMessageAction();
promise.afterDismissed().subscribe(() => {
done();
});
it('should open a message notification bar with action and custom configuration', async () => {
fixture.componentInstance.sendCustomMessageAction();
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar with action and no custom configuration', (done) => {
const promise = fixture.componentInstance.sendMessageActionWithoutConfig();
promise.afterDismissed().subscribe(() => {
done();
});
it('should open a message notification bar with action and no custom configuration', async () => {
fixture.componentInstance.sendMessageActionWithoutConfig();
fixture.detectChanges();
expect(document.querySelector('snack-bar-container')).not.toBeNull();
expect(await loader.hasHarness(MatSnackBarHarness)).toBe(true);
});
it('should open a message notification bar with a decorative icon', (done) => {
const promise = fixture.componentInstance.sendMessageWithDecorativeIcon();
promise.afterDismissed().subscribe(() => {
done();
});
fixture.detectChanges();
expect(document.querySelector('[data-automation-id="adf-snackbar-message-content"] mat-icon')).not.toBeNull();
it('should open a message notification bar with a decorative icon', async () => {
fixture.componentInstance.sendMessageWithDecorativeIcon();
expect(await loader.hasHarness(MatIconHarness.with({ ancestor: `[data-automation-id="adf-snackbar-message-content"]` }))).toBe(true);
});
it('should open a message notification bar with action and a decorative icon', (done) => {
const promise = fixture.componentInstance.sendMessageWithDecorativeIconAndAction();
promise.afterDismissed().subscribe(() => {
done();
});
fixture.detectChanges();
expect(document.querySelector('[data-automation-id="adf-snackbar-message-content"] mat-icon')).not.toBeNull();
});
it('should open a message notification bar with action and a decorative icon', async () => {
fixture.componentInstance.sendMessageWithDecorativeIconAndAction();
expect(await loader.hasHarness(MatIconHarness.with({ ancestor: `[data-automation-id="adf-snackbar-message-content"]` }))).toBe(true);
});
});