diff --git a/lib/process-services/src/lib/attachment/task-attachment-list.component.spec.ts b/lib/process-services/src/lib/attachment/task-attachment-list.component.spec.ts index cefe9b35ab..995bf21622 100644 --- a/lib/process-services/src/lib/attachment/task-attachment-list.component.spec.ts +++ b/lib/process-services/src/lib/attachment/task-attachment-list.component.spec.ts @@ -24,6 +24,9 @@ import { ProcessTestingModule } from '../testing/process.testing.module'; import { TranslateModule } from '@ngx-translate/core'; import { mockEmittedTaskAttachments, mockTaskAttachments } from '../mock/task/task-attachments.mock'; import { ProcessContentService } from '../form/services/process-content.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatMenuItemHarness } from '@angular/material/menu/testing'; describe('TaskAttachmentList', () => { @@ -35,6 +38,7 @@ describe('TaskAttachmentList', () => { let getFileRawContentSpy: jasmine.Spy; let getContentPreviewSpy: jasmine.Spy; let disposableSuccess: any; + let loader: HarnessLoader; beforeEach(() => { TestBed.configureTestingModule({ @@ -46,6 +50,7 @@ describe('TaskAttachmentList', () => { }); fixture = TestBed.createComponent(TaskAttachmentListComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); service = TestBed.inject(ProcessContentService); @@ -146,11 +151,12 @@ describe('TaskAttachmentList', () => { fixture.detectChanges(); await fixture.whenStable(); - const actionMenu = window.document.querySelectorAll('button.mat-menu-item').length; + + const actionMenuItems = await loader.getAllHarnesses(MatMenuItemHarness); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.VIEW_CONTENT"]')).not.toBeNull(); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.REMOVE_CONTENT"]')).not.toBeNull(); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.DOWNLOAD_CONTENT"]')).not.toBeNull(); - expect(actionMenu).toBe(3); + expect(actionMenuItems.length).toBe(3); }); it('should not display remove action if attachments are read only', async () => { @@ -166,11 +172,11 @@ describe('TaskAttachmentList', () => { fixture.detectChanges(); await fixture.whenStable(); - const actionMenu = window.document.querySelectorAll('button.mat-menu-item').length; + const actionMenuItems = await loader.getAllHarnesses(MatMenuItemHarness); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.VIEW_CONTENT"]')).not.toBeNull(); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.DOWNLOAD_CONTENT"]')).not.toBeNull(); expect(window.document.querySelector('[data-automation-id="ADF_TASK_LIST.MENU_ACTIONS.REMOVE_CONTENT"]')).toBeNull(); - expect(actionMenu).toBe(2); + expect(actionMenuItems.length).toBe(2); }); it('should show the empty list component when the attachments list is empty', async () => { diff --git a/lib/process-services/src/lib/form/form.component.visibility.spec.ts b/lib/process-services/src/lib/form/form.component.visibility.spec.ts index 34c33ad457..365586cd41 100644 --- a/lib/process-services/src/lib/form/form.component.visibility.spec.ts +++ b/lib/process-services/src/lib/form/form.component.visibility.spec.ts @@ -35,17 +35,16 @@ import { TranslateModule } from '@ngx-translate/core'; import { TaskService } from './services/task.service'; import { TaskFormService } from './services/task-form.service'; import { TaskRepresentation } from '@alfresco/js-api'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatSelectHarness } from '@angular/material/select/testing'; describe('FormComponent UI and visibility', () => { let component: FormComponent; let taskService: TaskService; let taskFormService: TaskFormService; let fixture: ComponentFixture; - - const openSelect = () => { - const dropdown = fixture.debugElement.nativeElement.querySelector('.mat-select-trigger'); - dropdown.click(); - }; + let loader: HarnessLoader; beforeEach(() => { TestBed.configureTestingModule({ @@ -54,6 +53,7 @@ describe('FormComponent UI and visibility', () => { }); fixture = TestBed.createComponent(FormComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); taskService = TestBed.inject(TaskService); taskFormService = TestBed.inject(TaskFormService); }); @@ -124,30 +124,23 @@ describe('FormComponent UI and visibility', () => { const change = new SimpleChange(null, 1, true); component.ngOnChanges({ taskId: change }); - fixture.detectChanges(); - await fixture.whenStable(); - openSelect(); - fixture.detectChanges(); - await fixture.whenStable(); - - const options = fixture.debugElement.queryAll(By.css('.mat-option-text')); + const dropdown = await loader.getHarness(MatSelectHarness); + await dropdown.open(); + const options = await dropdown.getOptions(); const optOne = options[1]; const optTwo = options[2]; const optThree = options[3]; - expect(optOne.nativeElement.innerText.trim()).toEqual('united kingdom'); - expect(optTwo.nativeElement.innerText.trim()).toEqual('italy'); - expect(optThree.nativeElement.innerText.trim()).toEqual('france'); + expect((await optOne.getText()).trim()).toEqual('united kingdom'); + expect((await optTwo.getText()).trim()).toEqual('italy'); + expect((await optThree.getText()).trim()).toEqual('france'); - optTwo.nativeElement.click(); + await optTwo.click(); - fixture.detectChanges(); - await fixture.whenStable(); - - const dropdown = fixture.debugElement.queryAll(By.css('#country')); - expect(dropdown[0].nativeElement.innerText.trim()).toEqual('italy'); + const dropdownCountries = fixture.debugElement.queryAll(By.css('#country')); + expect(dropdownCountries[0].nativeElement.innerText.trim()).toEqual('italy'); }); describe('Visibility conditions', () => { diff --git a/lib/process-services/src/lib/form/start-form.component.spec.ts b/lib/process-services/src/lib/form/start-form.component.spec.ts index 807a73c073..21f89da4e0 100644 --- a/lib/process-services/src/lib/form/start-form.component.spec.ts +++ b/lib/process-services/src/lib/form/start-form.component.spec.ts @@ -29,6 +29,11 @@ import { WidgetVisibilityService, FormModel, FormOutcomeModel } from '@alfresco/ import { TranslateService, TranslateModule } from '@ngx-translate/core'; import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessService } from '../process-list/services/process.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatSelectHarness } from '@angular/material/select/testing'; +import { MatCardHarness } from '@angular/material/card/testing'; +import { MatButtonHarness } from '@angular/material/button/testing'; describe('StartFormComponent', () => { @@ -38,6 +43,7 @@ describe('StartFormComponent', () => { let visibilityService: WidgetVisibilityService; let translate: TranslateService; let processService: ProcessService; + let loader: HarnessLoader; const exampleId1 = 'my:process1'; const exampleId2 = 'my:process2'; @@ -52,6 +58,7 @@ describe('StartFormComponent', () => { }); fixture = TestBed.createComponent(StartFormComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); processService = TestBed.inject(ProcessService); visibilityService = TestBed.inject(WidgetVisibilityService); translate = TestBed.inject(TranslateService); @@ -243,8 +250,8 @@ describe('StartFormComponent', () => { const dropdownField = formFields.find((field) => field.id === 'mockTypeDropDown'); const dropdownWidget = fixture.debugElement.nativeElement.querySelector('dropdown-widget'); const dropdownLabel = fixture.debugElement.nativeElement.querySelector('.adf-dropdown-widget .adf-label'); - const selectElement = fixture.debugElement.nativeElement.querySelector('.adf-select .mat-select-trigger'); - selectElement.click(); + const selectElement = await loader.getHarness(MatSelectHarness); + await selectElement.open(); expect(selectElement).toBeTruthy(); expect(dropdownWidget).toBeTruthy(); @@ -300,7 +307,7 @@ describe('StartFormComponent', () => { const formFieldsWidget = fixture.debugElement.nativeElement.querySelector('form-field'); const inputElement = fixture.debugElement.nativeElement.querySelector('.adf-input'); - const inputLabelElement = fixture.debugElement.nativeElement.querySelector('.mat-form-field-infix > .adf-label'); + const inputLabelElement = fixture.debugElement.nativeElement.querySelector('.adf-label'); const dateElement = fixture.debugElement.nativeElement.querySelector('#billdate'); const dateLabelElement = fixture.debugElement.nativeElement.querySelector('#billdate-label'); const selectElement = fixture.debugElement.nativeElement.querySelector('#claimtype'); @@ -322,14 +329,9 @@ describe('StartFormComponent', () => { component.showOutcomeButtons = true; component.showRefreshButton = true; component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) }); - fixture.detectChanges(); - await fixture.whenStable(); - const refreshElement = fixture.debugElement.nativeElement.querySelector('.mat-card-actions>button'); - refreshElement.click(); - - fixture.detectChanges(); - await fixture.whenStable(); + const refreshElement = await (await loader.getHarness(MatCardHarness)).getHarness(MatButtonHarness); + await refreshElement.click(); /* cspell:disable-next-line */ const selectElement = fixture.debugElement.nativeElement.querySelector('#claimtype'); @@ -370,17 +372,17 @@ describe('StartFormComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const titleElement = fixture.debugElement.nativeElement.querySelector('mat-card-title>h2'); - const actionButtons = fixture.debugElement.nativeElement.querySelectorAll('.mat-button'); + const cardTitle = await loader.getHarness(MatCardHarness); + const actionButtons = await loader.getAllHarnesses(MatButtonHarness); - expect(titleElement.innerText.trim()).toEqual('Mock Title'); + expect(await cardTitle.getTitleText()).toEqual('Mock Title'); expect(actionButtons.length).toBe(4); - expect(actionButtons[0].innerText.trim()).toBe('SAVE'); - expect(actionButtons[0].disabled).toBeFalsy(); - expect(actionButtons[1].innerText.trim()).toBe('APPROVE'); - expect(actionButtons[1].disabled).toBeTruthy(); - expect(actionButtons[2].innerText.trim()).toBe('COMPLETE'); - expect(actionButtons[2].disabled).toBeTruthy(); + expect(await actionButtons[0].getText()).toBe('SAVE'); + expect(await actionButtons[0].isDisabled()).toBeFalsy(); + expect(await actionButtons[1].getText()).toBe('APPROVE'); + expect(await actionButtons[1].isDisabled()).toBeTruthy(); + expect(await actionButtons[2].getText()).toBe('COMPLETE'); + expect(await actionButtons[2].isDisabled()).toBeTruthy(); }); }); diff --git a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts index 1bd0748556..dd2217a367 100644 --- a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts +++ b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts @@ -16,46 +16,34 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; import { Observable, of } from 'rxjs'; -import { - WidgetVisibilityService, - FormFieldOption, - FormFieldModel, - FormModel, - FormFieldTypes, - CoreTestingModule -} from '@alfresco/adf-core'; +import { WidgetVisibilityService, FormFieldOption, FormFieldModel, FormModel, FormFieldTypes, CoreTestingModule } from '@alfresco/adf-core'; import { DropdownWidgetComponent } from './dropdown.widget'; import { TranslateModule } from '@ngx-translate/core'; import { TaskFormService } from '../../services/task-form.service'; import { ProcessDefinitionService } from '../../services/process-definition.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatSelectHarness } from '@angular/material/select/testing'; describe('DropdownWidgetComponent', () => { - let taskFormService: TaskFormService; let processDefinitionService: ProcessDefinitionService; let widget: DropdownWidgetComponent; let visibilityService: WidgetVisibilityService; let fixture: ComponentFixture; let element: HTMLElement; - - const openSelect = () => { - const dropdown = fixture.debugElement.nativeElement.querySelector('.mat-select-trigger'); - dropdown.click(); - }; + let loader: HarnessLoader; const fakeOptionList: FormFieldOption[] = [ - {id: 'opt_1', name: 'option_1'}, - {id: 'opt_2', name: 'option_2'}, - {id: 'opt_3', name: 'option_3'}]; + { id: 'opt_1', name: 'option_1' }, + { id: 'opt_2', name: 'option_2' }, + { id: 'opt_3', name: 'option_3' } + ]; beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - CoreTestingModule - ] + imports: [TranslateModule.forRoot(), CoreTestingModule] }); fixture = TestBed.createComponent(DropdownWidgetComponent); widget = fixture.componentInstance; @@ -64,6 +52,7 @@ describe('DropdownWidgetComponent', () => { visibilityService = TestBed.inject(WidgetVisibilityService); processDefinitionService = TestBed.inject(ProcessDefinitionService); widget.field = new FormFieldModel(new FormModel()); + loader = TestbedHarnessEnvironment.loader(fixture); }); it('should require field with restUrl', () => { @@ -73,7 +62,7 @@ describe('DropdownWidgetComponent', () => { widget.ngOnInit(); expect(taskFormService.getRestFieldValues).not.toHaveBeenCalled(); - widget.field = new FormFieldModel(null, {restUrl: null}); + widget.field = new FormFieldModel(null, { restUrl: null }); widget.ngOnInit(); expect(taskFormService.getRestFieldValues).not.toHaveBeenCalled(); }); @@ -102,14 +91,17 @@ describe('DropdownWidgetComponent', () => { }); it('should preserve empty option when loading fields', () => { - const restFieldValue: FormFieldOption = {id: '1', name: 'Option1'} as FormFieldOption; - spyOn(taskFormService, 'getRestFieldValues').and.callFake(() => new Observable((observer) => { - observer.next([restFieldValue]); - observer.complete(); - })); + const restFieldValue: FormFieldOption = { id: '1', name: 'Option1' } as FormFieldOption; + spyOn(taskFormService, 'getRestFieldValues').and.callFake( + () => + new Observable((observer) => { + observer.next([restFieldValue]); + observer.complete(); + }) + ); - const form = new FormModel({taskId: ''}); - const emptyOption: FormFieldOption = {id: 'empty', name: 'Empty'} as FormFieldOption; + const form = new FormModel({ taskId: '' }); + const emptyOption: FormFieldOption = { id: 'empty', name: 'Empty' } as FormFieldOption; widget.field = new FormFieldModel(form, { id: '', restUrl: '/some/url/address', @@ -125,9 +117,8 @@ describe('DropdownWidgetComponent', () => { }); describe('when is required', () => { - beforeEach(() => { - widget.field = new FormFieldModel(new FormModel({taskId: ''}), { + widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { type: FormFieldTypes.DROPDOWN, required: true }); @@ -167,37 +158,31 @@ describe('DropdownWidgetComponent', () => { }); describe('when template is ready', () => { - describe('and dropdown is populated via taskId', () => { - beforeEach(() => { spyOn(visibilityService, 'refreshVisibility').and.stub(); spyOn(taskFormService, 'getRestFieldValues').and.callFake(() => of(fakeOptionList)); - widget.field = new FormFieldModel(new FormModel({taskId: 'fake-task-id'}), { + widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { id: 'dropdown-id', name: 'date-name', type: 'dropdown', readOnly: 'false', restUrl: 'fake-rest-url' }); - widget.field.emptyOption = {id: 'empty', name: 'Choose one...'}; + widget.field.emptyOption = { id: 'empty', name: 'Choose one...' }; widget.field.isVisible = true; fixture.detectChanges(); }); it('should show visible dropdown widget', async () => { - expect(element.querySelector('#dropdown-id')).toBeDefined(); - expect(element.querySelector('#dropdown-id')).not.toBeNull(); + const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' })); + await dropdown.open(); + const options = await dropdown.getOptions(); - openSelect(); - - const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); - const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]')); - const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]')); - - expect(optOne).not.toBeNull(); - expect(optTwo).not.toBeNull(); - expect(optThree).not.toBeNull(); + expect(await options[0].getText()).toBe(widget.field.emptyOption.name); + expect(await options[1].getText()).toBe(fakeOptionList[0].name); + expect(await options[2].getText()).toBe(fakeOptionList[1].name); + expect(await options[3].getText()).toBe(fakeOptionList[2].name); }); it('should select the default value when an option is chosen as default', async () => { @@ -216,13 +201,7 @@ describe('DropdownWidgetComponent', () => { widget.field.value = 'empty'; widget.ngOnInit(); - fixture.detectChanges(); - await fixture.whenStable(); - - openSelect(); - - fixture.detectChanges(); - await fixture.whenStable(); + await (await loader.getHarness(MatSelectHarness)).open(); const dropDownElement: any = element.querySelector('#dropdown-id'); expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty'); @@ -230,35 +209,30 @@ describe('DropdownWidgetComponent', () => { }); describe('and dropdown is populated via processDefinitionId', () => { - beforeEach(() => { spyOn(visibilityService, 'refreshVisibility').and.stub(); spyOn(processDefinitionService, 'getRestFieldValuesByProcessId').and.callFake(() => of(fakeOptionList)); - widget.field = new FormFieldModel(new FormModel({processDefinitionId: 'fake-process-id'}), { + widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), { id: 'dropdown-id', name: 'date-name', type: 'dropdown', readOnly: 'false', restUrl: 'fake-rest-url' }); - widget.field.emptyOption = {id: 'empty', name: 'Choose one...'}; + widget.field.emptyOption = { id: 'empty', name: 'Choose one...' }; widget.field.isVisible = true; fixture.detectChanges(); }); - it('should show visible dropdown widget', () => { - expect(element.querySelector('#dropdown-id')).toBeDefined(); - expect(element.querySelector('#dropdown-id')).not.toBeNull(); + it('should show visible dropdown widget', async () => { + const dropdown = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown-id' })); + await dropdown.open(); + const options = await dropdown.getOptions(); - openSelect(); - - const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); - const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]')); - const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]')); - - expect(optOne).not.toBeNull(); - expect(optTwo).not.toBeNull(); - expect(optThree).not.toBeNull(); + expect(await options[0].getText()).toBe(widget.field.emptyOption.name); + expect(await options[1].getText()).toBe(fakeOptionList[0].name); + expect(await options[2].getText()).toBe(fakeOptionList[1].name); + expect(await options[3].getText()).toBe(fakeOptionList[2].name); }); it('should select the default value when an option is chosen as default', async () => { @@ -276,21 +250,14 @@ describe('DropdownWidgetComponent', () => { it('should select the empty value when no default is chosen', async () => { widget.field.value = 'empty'; widget.ngOnInit(); - - fixture.detectChanges(); - await fixture.whenStable(); - - openSelect(); - - fixture.detectChanges(); - await fixture.whenStable(); + await (await loader.getHarness(MatSelectHarness)).open(); const dropDownElement: any = element.querySelector('#dropdown-id'); expect(dropDownElement.attributes['ng-reflect-model'].value).toBe('empty'); }); it('should be disabled when the field is readonly', async () => { - widget.field = new FormFieldModel(new FormModel({processDefinitionId: 'fake-process-id'}), { + widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), { id: 'dropdown-id', name: 'date-name', type: 'dropdown', @@ -307,25 +274,18 @@ describe('DropdownWidgetComponent', () => { }); it('should show the option value when the field is readonly', async () => { - widget.field = new FormFieldModel(new FormModel({processDefinitionId: 'fake-process-id'}), { + widget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), { id: 'dropdown-id', name: 'date-name', type: 'readonly', value: 'FakeValue', readOnly: true, - params: {field: {name: 'date-name', type: 'dropdown'}} + params: { field: { name: 'date-name', type: 'dropdown' } } }); - openSelect(); + const select = await loader.getHarness(MatSelectHarness); - fixture.detectChanges(); - await fixture.whenStable(); - - const options = fixture.debugElement.queryAll(By.css('.mat-option-text')); - expect(options.length).toBe(1); - - const option = options[0].nativeElement; - expect(option.innerText).toEqual('FakeValue'); + expect(await select.getValueText()).toEqual('FakeValue'); }); }); }); diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.html b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.html index 12d0aaf91b..5b701feccd 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.html +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.html @@ -13,4 +13,5 @@ {{opt.name}} + {{opt | json}} diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts index bca57e3524..abbd30a4ee 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/dropdown/dropdown.editor.spec.ts @@ -16,31 +16,33 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { Observable, throwError } from 'rxjs'; -import { - AlfrescoApiService, - CoreTestingModule, - FormFieldModel, - FormModel, - FormService -} from '@alfresco/adf-core'; +import { Observable, of, throwError } from 'rxjs'; +import { FormFieldModel, FormModel, FormService, TranslationMock, TranslationService } from '@alfresco/adf-core'; import { DynamicTableColumnOption } from '../models/dynamic-table-column-option.model'; import { DynamicTableColumn } from '../models/dynamic-table-column.model'; import { DynamicTableRow } from '../models/dynamic-table-row.model'; import { DynamicTableModel } from '../models/dynamic-table.widget.model'; import { DropdownEditorComponent } from './dropdown.editor'; -import { TranslateModule } from '@ngx-translate/core'; import { TaskFormService } from '../../../../services/task-form.service'; import { ProcessDefinitionService } from '../../../../services/process-definition.service'; +import { MatSelectHarness } from '@angular/material/select/testing'; +import { FormModule } from '../../../../form.module'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatOptionModule } from '@angular/material/core'; describe('DropdownEditorComponent', () => { - + let fixture: ComponentFixture; let component: DropdownEditorComponent; + let loader: HarnessLoader; let formService: FormService; let taskFormService: TaskFormService; let processDefinitionService: ProcessDefinitionService; - let alfrescoApiService: AlfrescoApiService; let form: FormModel; let table: DynamicTableModel; let column: DynamicTableColumn; @@ -50,254 +52,211 @@ describe('DropdownEditorComponent', () => { TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot(), - CoreTestingModule - ] + CommonModule, + NoopAnimationsModule, + MatFormFieldModule, + MatSelectModule, + MatOptionModule, + FormModule + ], + providers: [{ provide: TranslationService, useClass: TranslationMock }] }); - alfrescoApiService = TestBed.inject(AlfrescoApiService); + formService = TestBed.inject(FormService); + taskFormService = TestBed.inject(TaskFormService); + processDefinitionService = TestBed.inject(ProcessDefinitionService); - formService = new FormService(); - taskFormService = new TaskFormService(alfrescoApiService, null); - processDefinitionService = new ProcessDefinitionService(alfrescoApiService, null); + fixture = TestBed.createComponent(DropdownEditorComponent); + component = fixture.componentInstance; - row = {value: {dropdown: 'one'}} as DynamicTableRow; + row = { value: { dropdown: 'one' } } as DynamicTableRow; column = { id: 'dropdown', options: [ - {id: '1', name: 'one'}, - {id: '2', name: 'two'} - ] + { id: '1', name: 'one' }, + { id: '2', name: 'two' } + ], + editable: true } as DynamicTableColumn; - form = new FormModel({taskId: ''}); - table = new DynamicTableModel(new FormFieldModel(form, {id: ''}), formService); + form = new FormModel({ taskId: '' }); + table = new DynamicTableModel(new FormFieldModel(form, { id: '', isVisible: true }), formService); table.rows.push(row); table.columns.push(column); - component = new DropdownEditorComponent(formService, taskFormService, processDefinitionService, null); component.table = table; component.row = row; component.column = column; + loader = TestbedHarnessEnvironment.loader(fixture); }); - it('should require table field to setup', () => { - table.field = null; - component.ngOnInit(); - expect(component.value).toBeNull(); - expect(component.options).toEqual([]); - }); + describe('dropdown is populated manually', () => { + beforeEach(() => { + column = { + id: 'dropdown', + options: [ + { id: '1', name: 'one' }, + { id: '2', name: 'two' } + ] + } as DynamicTableColumn; - it('should setup with manual mode', () => { - row.value[column.id] = 'two'; - component.ngOnInit(); - expect(component.options).toEqual(column.options); - expect(component.value).toBe(row.value[column.id]); - }); + component.column = column; + }); - it('should setup empty columns for manual mode', () => { - column.options = null; - component.ngOnInit(); - expect(component.options).toEqual([]); - }); - - it('should setup with REST mode', () => { - column.optionType = 'rest'; - row.value[column.id] = 'twelve'; - - const restResults: DynamicTableColumnOption[] = [ - {id: '11', name: 'eleven'}, - {id: '12', name: 'twelve'} - ]; - - spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue( - new Observable((observer) => { - observer.next(restResults); - observer.complete(); - }) - ); - - component.ngOnInit(); - - expect(taskFormService.getRestFieldValuesColumn).toHaveBeenCalledWith( - form.taskId, - table.field.id, - column.id - ); - - expect(column.options).toEqual(restResults); - expect(component.options).toEqual(restResults); - expect(component.value).toBe(row.value[column.id]); - }); - - it('should create empty options array on REST response', () => { - column.optionType = 'rest'; - - spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue( - new Observable((observer) => { - observer.next(null); - observer.complete(); - }) - ); - - component.ngOnInit(); - - expect(taskFormService.getRestFieldValuesColumn).toHaveBeenCalledWith( - form.taskId, - table.field.id, - column.id - ); - - expect(column.options).toEqual([]); - expect(component.options).toEqual([]); - expect(component.value).toBe(row.value[column.id]); - }); - - it('should handle REST error getting options with task id', () => { - column.optionType = 'rest'; - const error = 'error'; - - spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue( - throwError(error) - ); - spyOn(component, 'handleError').and.stub(); - - component.ngOnInit(); - expect(component.handleError).toHaveBeenCalledWith(error); - }); - - it('should handle REST error getting option with processDefinitionId', () => { - column.optionType = 'rest'; - const procForm = new FormModel({processDefinitionId: ''}); - const procTable = new DynamicTableModel(new FormFieldModel(procForm, {id: ''}), formService); - component.table = procTable; - const error = 'error'; - - spyOn(processDefinitionService, 'getRestFieldValuesColumnByProcessId').and.returnValue( - throwError(error) - ); - spyOn(component, 'handleError').and.stub(); - - component.ngOnInit(); - expect(component.handleError).toHaveBeenCalledWith(error); - }); - - it('should update row on value change', () => { - const event = {value: 'two'}; - component.onValueChanged(row, column, event); - expect(row.value[column.id]).toBe(column.options[1]); - }); - - describe('when template is ready', () => { - let dropDownEditorComponent: DropdownEditorComponent; - let fixture: ComponentFixture; - let element: HTMLElement; - let dynamicTable: DynamicTableModel; - - const openSelect = () => { - const dropdown = fixture.debugElement.query(By.css('.mat-select-trigger')); - dropdown.triggerEventHandler('click', null); + it('should require table field to setup', () => { + table.field = null; fixture.detectChanges(); - }; + expect(component.value).toBeNull(); + expect(component.options).toEqual([]); + }); + + it('should setup with manual mode', () => { + row.value[column.id] = 'two'; + fixture.detectChanges(); + expect(component.options).toEqual(column.options); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should setup empty columns for manual mode', () => { + column.options = null; + fixture.detectChanges(); + expect(component.options).toEqual([]); + }); + + it('should setup with REST mode', () => { + column.optionType = 'rest'; + row.value[column.id] = 'twelve'; + + const restResults: DynamicTableColumnOption[] = [ + { id: '11', name: 'eleven' }, + { id: '12', name: 'twelve' } + ]; + + spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue( + new Observable((observer) => { + observer.next(restResults); + observer.complete(); + }) + ); + + fixture.detectChanges(); + + expect(taskFormService.getRestFieldValuesColumn).toHaveBeenCalledWith(form.taskId, table.field.id, column.id); + + expect(column.options).toEqual(restResults); + expect(component.options).toEqual(restResults); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should create empty options array on REST response', () => { + column.optionType = 'rest'; + + spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue( + new Observable((observer) => { + observer.next(null); + observer.complete(); + }) + ); + + fixture.detectChanges(); + + expect(taskFormService.getRestFieldValuesColumn).toHaveBeenCalledWith(form.taskId, table.field.id, column.id); + + expect(column.options).toEqual([]); + expect(component.options).toEqual([]); + expect(component.value).toBe(row.value[column.id]); + }); + + it('should handle REST error getting options with task id', () => { + column.optionType = 'rest'; + const error = 'error'; + + spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue(throwError(error)); + spyOn(component, 'handleError').and.stub(); + + component.ngOnInit(); + expect(component.handleError).toHaveBeenCalledWith(error); + }); + + it('should handle REST error getting option with processDefinitionId', () => { + column.optionType = 'rest'; + const procForm = new FormModel({ processDefinitionId: '' }); + const procTable = new DynamicTableModel(new FormFieldModel(procForm, { id: '' }), formService); + component.table = procTable; + const error = 'error'; + + spyOn(processDefinitionService, 'getRestFieldValuesColumnByProcessId').and.returnValue(throwError(error)); + spyOn(component, 'handleError').and.stub(); + + fixture.detectChanges(); + expect(component.handleError).toHaveBeenCalledWith(error); + }); + + it('should update row on value change', () => { + const event = { value: 'two' }; + component.onValueChanged(row, column, event); + expect(row.value[column.id]).toBe(column.options[1]); + }); + }); + + describe('dropdown is populated via taskId', () => { + let getRestFieldValuesColumnSpy: jasmine.Spy; + + beforeEach(async () => { + form = new FormModel({ taskId: '' }); + table = new DynamicTableModel(new FormFieldModel(form, { id: '' }), formService); + component.table = table; + component.table.field = new FormFieldModel(form, { + id: 'dropdown-id', + name: 'date-name', + type: 'dropdown', + readOnly: 'false', + restUrl: 'fake-rest-url' + }); + component.column.optionType = 'rest'; + component.table.field.isVisible = true; + getRestFieldValuesColumnSpy = spyOn(taskFormService, 'getRestFieldValuesColumn').and.returnValue(of(column.options)); + }); + + it('should show visible dropdown widget', async () => { + const select = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown' })); + await select.open(); + const options = await select.getOptions(); + + expect(getRestFieldValuesColumnSpy).toHaveBeenCalled(); + expect(component.options.length).toBe(2); + expect(options.length).toBe(3); + }); + }); + + describe('dropdown is populated via processDefinitionId', () => { + let getRestFieldValuesColumnByProcessId: jasmine.Spy; beforeEach(() => { - fixture = TestBed.createComponent(DropdownEditorComponent); - dropDownEditorComponent = fixture.componentInstance; - element = fixture.nativeElement; + form = new FormModel({ processDefinitionId: '' }); + table = new DynamicTableModel(new FormFieldModel(form, { id: '' }), formService); + component.table = table; + component.table.field = new FormFieldModel(form, { + id: 'dropdown-id', + name: 'date-name', + type: 'dropdown', + readOnly: 'false', + restUrl: 'fake-rest-url' + }); + component.column.optionType = 'rest'; + component.table.field.isVisible = true; + getRestFieldValuesColumnByProcessId = spyOn(processDefinitionService, 'getRestFieldValuesColumnByProcessId').and.returnValue( + of(column.options) + ); }); - afterEach(() => { - fixture.destroy(); - }); + it('should show visible dropdown widget', async () => { + const select = await loader.getHarness(MatSelectHarness.with({ selector: '#dropdown' })); + await select.open(); - describe('and dropdown is populated via taskId', () => { - - beforeEach(() => { - row = {value: {dropdown: 'one'}} as DynamicTableRow; - column = { - id: 'column-id', - optionType: 'rest', - options: [ - {id: '1', name: 'one'}, - {id: '2', name: 'two'} - ] - } as DynamicTableColumn; - form = new FormModel({taskId: ''}); - dynamicTable = new DynamicTableModel(new FormFieldModel(form, {id: ''}), formService); - dynamicTable.rows.push(row); - dynamicTable.columns.push(column); - dropDownEditorComponent.table = dynamicTable; - dropDownEditorComponent.column = column; - dropDownEditorComponent.row = row; - dropDownEditorComponent.table.field = new FormFieldModel(form, { - id: 'dropdown-id', - name: 'date-name', - type: 'dropdown', - readOnly: 'false', - restUrl: 'fake-rest-url' - }); - dropDownEditorComponent.table.field.isVisible = true; - fixture.detectChanges(); - }); - - it('should show visible dropdown widget', () => { - expect(element.querySelector('#column-id')).toBeDefined(); - expect(element.querySelector('#column-id')).not.toBeNull(); - - openSelect(); - - const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); - const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]')); - const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]')); - - expect(optOne).not.toBeNull(); - expect(optTwo).not.toBeNull(); - expect(optThree).not.toBeNull(); - }); - }); - - describe('and dropdown is populated via processDefinitionId', () => { - - beforeEach(() => { - row = {value: {dropdown: 'one'}} as DynamicTableRow; - column = { - id: 'column-id', - optionType: 'rest', - options: [ - {id: '1', name: 'one'}, - {id: '2', name: 'two'} - ] - } as DynamicTableColumn; - form = new FormModel({processDefinitionId: ''}); - dynamicTable = new DynamicTableModel(new FormFieldModel(form, {id: ''}), formService); - dynamicTable.rows.push(row); - dynamicTable.columns.push(column); - dropDownEditorComponent.table = dynamicTable; - dropDownEditorComponent.column = column; - dropDownEditorComponent.row = row; - dropDownEditorComponent.table.field = new FormFieldModel(form, { - id: 'dropdown-id', - name: 'date-name', - type: 'dropdown', - readOnly: 'false', - restUrl: 'fake-rest-url' - }); - dropDownEditorComponent.table.field.isVisible = true; - fixture.detectChanges(); - }); - - it('should show visible dropdown widget', () => { - expect(element.querySelector('#column-id')).toBeDefined(); - expect(element.querySelector('#column-id')).not.toBeNull(); - - openSelect(); - - const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]')); - const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]')); - const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]')); - - expect(optOne).not.toBeNull(); - expect(optTwo).not.toBeNull(); - expect(optThree).not.toBeNull(); - }); + const options = await select.getOptions(); + expect(getRestFieldValuesColumnByProcessId).toHaveBeenCalled(); + expect(component.options.length).toBe(2); + expect(options.length).toBe(3); }); }); }); diff --git a/lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.spec.ts b/lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.spec.ts index 2aaeb4ae98..baecc5fd9d 100644 --- a/lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.spec.ts +++ b/lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.spec.ts @@ -17,15 +17,7 @@ import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { Observable, of } from 'rxjs'; -import { - FormService, - ContainerModel, - FormFieldTypes, - FormFieldOption, - FormFieldModel, - FormModel, - CoreTestingModule -} from '@alfresco/adf-core'; +import { FormService, ContainerModel, FormFieldTypes, FormFieldOption, FormFieldModel, FormModel, CoreTestingModule } from '@alfresco/adf-core'; import { RadioButtonsWidgetComponent } from './radio-buttons.widget'; import { MatIconModule } from '@angular/material/icon'; import { MatRadioModule } from '@angular/material/radio'; @@ -33,9 +25,12 @@ import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { TaskFormService } from '../../services/task-form.service'; import { ProcessDefinitionService } from '../../services/process-definition.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatRadioButtonHarness, MatRadioGroupHarness } from '@angular/material/radio/testing'; +import { MatTooltipHarness } from '@angular/material/tooltip/testing'; describe('RadioButtonsWidgetComponent', () => { - let formService: FormService; let widget: RadioButtonsWidgetComponent; let taskFormService: TaskFormService; @@ -43,20 +38,14 @@ describe('RadioButtonsWidgetComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - CoreTestingModule, - MatRadioModule, - FormsModule, - MatIconModule - ] + imports: [TranslateModule.forRoot(), CoreTestingModule, MatRadioModule, FormsModule, MatIconModule] }); taskFormService = TestBed.inject(TaskFormService); processDefinitionService = TestBed.inject(ProcessDefinitionService); formService = new FormService(); widget = new RadioButtonsWidgetComponent(formService, taskFormService, processDefinitionService, null); - widget.field = new FormFieldModel(new FormModel(), {restUrl: ''}); + widget.field = new FormFieldModel(new FormModel(), { restUrl: '' }); }); it('should request field values from service', () => { @@ -72,10 +61,12 @@ describe('RadioButtonsWidgetComponent', () => { restUrl: '' }); - spyOn(taskFormService, 'getRestFieldValues').and.returnValue(new Observable((observer) => { - observer.next(null); - observer.complete(); - })); + spyOn(taskFormService, 'getRestFieldValues').and.returnValue( + new Observable((observer) => { + observer.next(null); + observer.complete(); + }) + ); widget.ngOnInit(); expect(taskFormService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId); }); @@ -95,10 +86,12 @@ describe('RadioButtonsWidgetComponent', () => { const field = widget.field; spyOn(field, 'updateForm').and.stub(); - spyOn(taskFormService, 'getRestFieldValues').and.returnValue(new Observable((observer) => { - observer.next(null); - observer.complete(); - })); + spyOn(taskFormService, 'getRestFieldValues').and.returnValue( + new Observable((observer) => { + observer.next(null); + observer.complete(); + }) + ); widget.ngOnInit(); expect(field.updateForm).toHaveBeenCalled(); }); @@ -115,10 +108,12 @@ describe('RadioButtonsWidgetComponent', () => { id: fieldId, restUrl: '' }); - spyOn(taskFormService, 'getRestFieldValues').and.returnValue(new Observable((observer) => { - observer.next(null); - observer.complete(); - })); + spyOn(taskFormService, 'getRestFieldValues').and.returnValue( + new Observable((observer) => { + observer.next(null); + observer.complete(); + }) + ); const field = widget.field; widget.field = null; @@ -146,6 +141,8 @@ describe('RadioButtonsWidgetComponent', () => { let radioButtonWidget: RadioButtonsWidgetComponent; let fixture: ComponentFixture; let element: HTMLElement; + let loader: HarnessLoader; + const restOption: FormFieldOption[] = [ { id: 'opt-1', @@ -154,12 +151,14 @@ describe('RadioButtonsWidgetComponent', () => { { id: 'opt-2', name: 'opt-name-2' - }]; + } + ]; beforeEach(() => { fixture = TestBed.createComponent(RadioButtonsWidgetComponent); radioButtonWidget = fixture.componentInstance; element = fixture.nativeElement; + loader = TestbedHarnessEnvironment.loader(fixture); }); it('should show radio buttons as text when is readonly', async () => { @@ -170,8 +169,7 @@ describe('RadioButtonsWidgetComponent', () => { readOnly: true }); fixture.detectChanges(); - await fixture.whenStable(); - fixture.detectChanges(); + expect(element.querySelector('display-text-widget')).toBeDefined(); }); @@ -197,26 +195,21 @@ describe('RadioButtonsWidgetComponent', () => { options: restOption, restUrl: null }); + fixture.detectChanges(); - fixture.detectChanges(); - await fixture.whenStable(); - fixture.detectChanges(); const widgetLabel = element.querySelector('label'); expect(widgetLabel.innerText).toBe('radio-name-label*'); expect(radioButtonWidget.field.isValid).toBe(false); - const option = element.querySelector('#radio-id-opt-1 label'); - option.click(); + const option = await loader.getHarness(MatRadioButtonHarness.with({ selector: '#radio-id-opt-1' })); + await option.check(); - fixture.detectChanges(); - await fixture.whenStable(); - fixture.detectChanges(); - const selectedOption = element.querySelector('[class*="mat-radio-checked"]'); - expect(selectedOption.innerText).toBe('opt-name-1'); + const selectedOption = await loader.getHarness(MatRadioButtonHarness.with({ checked: true })); + expect(await selectedOption.getLabelText()).toBe('opt-name-1'); expect(radioButtonWidget.field.isValid).toBe(true); }); - it('should be able to set another Radio Button widget as required', () => { + it('should be able to set another Radio Button widget as required', async () => { radioButtonWidget.field = new FormFieldModel(new FormModel({}), { id: 'radio-id', name: 'radio-name-label', @@ -228,10 +221,10 @@ describe('RadioButtonsWidgetComponent', () => { restUrl: null, value: 'opt-name-2' }); - fixture.detectChanges(); - const selectedOption = element.querySelector('[class*="mat-radio-checked"]'); - expect(selectedOption.innerText).toBe('opt-name-2'); + + const selectedOption = await loader.getHarness(MatRadioButtonHarness.with({ checked: true })); + expect(await selectedOption.getLabelText()).toBe('opt-name-2'); expect(radioButtonWidget.field.isValid).toBe(true); }); @@ -249,19 +242,16 @@ describe('RadioButtonsWidgetComponent', () => { }); fixture.detectChanges(); - await fixture.whenStable(); - const radioButtonsElement: any = element.querySelector('#radio-id-opt-1'); - const tooltip = radioButtonsElement.getAttribute('ng-reflect-message'); - - expect(tooltip).toEqual(radioButtonWidget.field.tooltip); + const tooltip = await loader.getHarness(MatTooltipHarness.with({ selector: '#radio-id-opt-1' })); + await tooltip.show(); + expect(await tooltip.getTooltipText()).toEqual(radioButtonWidget.field.tooltip); }); describe('and radioButton is populated via taskId', () => { - beforeEach(() => { spyOn(taskFormService, 'getRestFieldValues').and.returnValue(of(restOption)); - radioButtonWidget.field = new FormFieldModel(new FormModel({taskId: 'task-id'}), { + radioButtonWidget.field = new FormFieldModel(new FormModel({ taskId: 'task-id' }), { id: 'radio-id', name: 'radio-name', type: FormFieldTypes.RADIO_BUTTONS, @@ -293,37 +283,38 @@ describe('RadioButtonsWidgetComponent', () => { })); describe('and radioButton is readonly', () => { - beforeEach(() => { radioButtonWidget.field.readOnly = true; fixture.detectChanges(); }); - it('should show radio buttons disabled', () => { - expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-1"]')).toBeDefined(); - expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-1"]')).not.toBeNull(); - expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-2"]')).toBeDefined(); - expect(element.querySelector('.mat-radio-disabled[ng-reflect-id="radio-id-opt-2"]')).not.toBeNull(); + it('should show radio buttons disabled', async () => { + const radioButtons = await ( + await loader.getHarness(MatRadioGroupHarness.with({ selector: '.adf-radio-group' })) + ).getRadioButtons(); + expect(await radioButtons[0].isDisabled()).toBe(true); + expect(await radioButtons[1].isDisabled()).toBe(true); }); describe('and a value is selected', () => { - beforeEach(() => { radioButtonWidget.field.value = restOption[0].id; fixture.detectChanges(); }); - it('should check the selected value', () => { - expect(element.querySelector('.mat-radio-checked')).toBe(element.querySelector('mat-radio-button[ng-reflect-id="radio-id-opt-1"]')); + it('should check the selected value', async () => { + const checkedRadioButton = await ( + await loader.getHarness(MatRadioGroupHarness.with({ selector: '.adf-radio-group' })) + ).getCheckedRadioButton(); + expect(await checkedRadioButton.getLabelText()).toBe(restOption[0].name); }); }); }); }); describe('and radioButton is populated via processDefinitionId', () => { - beforeEach(() => { - radioButtonWidget.field = new FormFieldModel(new FormModel({processDefinitionId: 'proc-id'}), { + radioButtonWidget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'proc-id' }), { id: 'radio-id', name: 'radio-name', type: FormFieldTypes.RADIO_BUTTONS, diff --git a/lib/process-services/src/lib/process-list/components/process-instance-details.component.spec.ts b/lib/process-services/src/lib/process-list/components/process-instance-details.component.spec.ts index 96cd3d5c15..0ccef964b3 100644 --- a/lib/process-services/src/lib/process-list/components/process-instance-details.component.spec.ts +++ b/lib/process-services/src/lib/process-list/components/process-instance-details.component.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DebugElement, NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; +import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { of } from 'rxjs'; @@ -28,12 +28,16 @@ import { ProcessInstanceDetailsComponent } from './process-instance-details.comp import { ProcessTestingModule } from '../../testing/process.testing.module'; import { FormModule } from '../../form'; import { TranslateModule } from '@ngx-translate/core'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatCardHarness } from '@angular/material/card/testing'; describe('ProcessInstanceDetailsComponent', () => { let service: ProcessService; let component: ProcessInstanceDetailsComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let getProcessSpy: jasmine.Spy; beforeEach(() => { @@ -48,6 +52,7 @@ describe('ProcessInstanceDetailsComponent', () => { }); fixture = TestBed.createComponent(ProcessInstanceDetailsComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); service = fixture.debugElement.injector.get(ProcessService); const commentService = fixture.debugElement.injector.get(CommentProcessService); @@ -76,12 +81,8 @@ describe('ProcessInstanceDetailsComponent', () => { fixture.detectChanges(); component.ngOnChanges({ processInstanceId: new SimpleChange(null, '123', true) }); - fixture.detectChanges(); - await fixture.whenStable(); - - const headerEl: DebugElement = fixture.debugElement.query(By.css('.mat-card-title ')); - expect(headerEl).not.toBeNull(); - expect(headerEl.nativeElement.innerText).toBe('Process 123'); + const headerEl = await loader.getHarness(MatCardHarness); + expect(await headerEl.getTitleText()).toBe('Process 123'); }); it('should display default details when the process instance has no name', async () => { @@ -90,12 +91,8 @@ describe('ProcessInstanceDetailsComponent', () => { fixture.detectChanges(); component.ngOnChanges({ processInstanceId: new SimpleChange(null, '123', true) }); - fixture.detectChanges(); - await fixture.whenStable(); - - const headerEl: DebugElement = fixture.debugElement.query(By.css('.mat-card-title ')); - expect(headerEl).not.toBeNull(); - expect(headerEl.nativeElement.innerText).toBe('My Process - Nov 10, 2016, 3:37:30 AM'); + const headerEl = await loader.getHarness(MatCardHarness); + expect(await headerEl.getTitleText()).toBe('My Process - Nov 10, 2016, 3:37:30 AM'); }); it('should enable diagram button if the process is running', async () => { diff --git a/lib/process-services/src/lib/process-list/components/process-instance-tasks.component.spec.ts b/lib/process-services/src/lib/process-list/components/process-instance-tasks.component.spec.ts index 7cb1626bda..66cf2a4203 100644 --- a/lib/process-services/src/lib/process-list/components/process-instance-tasks.component.spec.ts +++ b/lib/process-services/src/lib/process-list/components/process-instance-tasks.component.spec.ts @@ -26,48 +26,42 @@ import { ProcessService } from './../services/process.service'; import { ProcessInstanceTasksComponent } from './process-instance-tasks.component'; import { ProcessTestingModule } from '../../testing/process.testing.module'; import { TranslateModule } from '@ngx-translate/core'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatListItemHarness } from '@angular/material/list/testing'; describe('ProcessInstanceTasksComponent', () => { - let component: ProcessInstanceTasksComponent; let fixture: ComponentFixture; - let service: ProcessService; - // let getProcessTasksSpy: jasmine.Spy; + let loader: HarnessLoader; + let processService: ProcessService; const exampleProcessInstance = new ProcessInstance({ id: '123' }); beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - ProcessTestingModule - ] + imports: [TranslateModule.forRoot(), ProcessTestingModule] }); fixture = TestBed.createComponent(ProcessInstanceTasksComponent); + processService = TestBed.inject(ProcessService); component = fixture.componentInstance; - service = TestBed.inject(ProcessService); + loader = TestbedHarnessEnvironment.loader(fixture); - spyOn(service, 'getProcessTasks').and.returnValue(of([new TaskDetailsModel(taskDetailsMock)])); + spyOn(processService, 'getProcessTasks').and.returnValue(of([new TaskDetailsModel(taskDetailsMock)])); }); - afterEach(() => { - fixture.destroy(); - }); - - it('should initially render message about no active tasks if no process instance ID provided', async () => { + it('should initially render message about no active tasks if no process instance ID provided', () => { component.processInstanceDetails = undefined; fixture.detectChanges(); - await fixture.whenStable(); const msgEl = fixture.debugElement.query(By.css('[data-automation-id="active-tasks-none"]')); expect(msgEl).not.toBeNull(); }); - it('should initially render message about no completed tasks if no process instance ID provided', async () => { + it('should initially render message about no completed tasks if no process instance ID provided', () => { component.processInstanceDetails = undefined; fixture.detectChanges(); - await fixture.whenStable(); const msgEl = fixture.debugElement.query(By.css('[data-automation-id="completed-tasks-none"]')); expect(msgEl).not.toBeNull(); @@ -92,13 +86,8 @@ describe('ProcessInstanceTasksComponent', () => { fixture.detectChanges(); component.ngOnChanges({ processInstanceDetails: change }); - fixture.detectChanges(); - await fixture.whenStable(); - - component.ngOnChanges({ processInstanceDetails: change }); - const listEl = fixture.debugElement.query(By.css('[data-automation-id="active-tasks"]')); - expect(listEl).not.toBeNull(); - expect(listEl.queryAll(By.css('mat-list-item')).length).toBe(1); + const items = await loader.getAllHarnesses(MatListItemHarness.with({ ancestor: '[data-automation-id="active-tasks"]' })); + expect(items.length).toBe(1); }); it('should display completed tasks', async () => { @@ -106,51 +95,42 @@ describe('ProcessInstanceTasksComponent', () => { fixture.detectChanges(); component.ngOnChanges({ processInstanceDetails: change }); - fixture.detectChanges(); - await fixture.whenStable(); - - const listEl = fixture.debugElement.query(By.css('[data-automation-id="completed-tasks"]')); - expect(listEl).not.toBeNull(); - expect(listEl.queryAll(By.css('mat-list-item')).length).toBe(1); + const items = await loader.getAllHarnesses(MatListItemHarness.with({ ancestor: '[data-automation-id="completed-tasks"]' })); + expect(items.length).toBe(1); }); describe('task reloading', () => { - beforeEach(() => { component.processInstanceDetails = exampleProcessInstance; }); - it('should render a refresh button by default', async () => { + it('should render a refresh button by default', () => { fixture.detectChanges(); - await fixture.whenStable(); expect(fixture.debugElement.query(By.css('.process-tasks-refresh'))).not.toBeNull(); }); - it('should render a refresh button if configured to', async () => { + it('should render a refresh button if configured to', () => { component.showRefreshButton = true; fixture.detectChanges(); - await fixture.whenStable(); expect(fixture.debugElement.query(By.css('.process-tasks-refresh'))).not.toBeNull(); }); - it('should NOT render a refresh button if configured not to', async () => { + it('should NOT render a refresh button if configured not to', () => { component.showRefreshButton = false; fixture.detectChanges(); - await fixture.whenStable(); expect(fixture.debugElement.query(By.css('.process-tasks-refresh'))).toBeNull(); }); - it('should call service to get tasks when reload button clicked', async () => { + it('should call service to get tasks when reload button clicked', () => { fixture.detectChanges(); - await fixture.whenStable(); component.onRefreshClicked(); - expect(service.getProcessTasks).toHaveBeenCalled(); + expect(processService.getProcessTasks).toHaveBeenCalled(); }); }); }); diff --git a/lib/process-services/src/lib/process-list/components/process-list.component.spec.ts b/lib/process-services/src/lib/process-list/components/process-list.component.spec.ts index a141e5038e..bc960a3b9c 100644 --- a/lib/process-services/src/lib/process-list/components/process-list.component.spec.ts +++ b/lib/process-services/src/lib/process-list/components/process-list.component.spec.ts @@ -33,11 +33,16 @@ import { import { ProcessService } from '../services/process.service'; import { ProcessTestingModule } from '../../testing/process.testing.module'; import { TranslateModule } from '@ngx-translate/core'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; +import { MatMenuItemHarness } from '@angular/material/menu/testing'; describe('ProcessInstanceListComponent', () => { let fixture: ComponentFixture; let component: ProcessInstanceListComponent; + let loader: HarnessLoader; let service: ProcessService; let getProcessInstancesSpy: jasmine.Spy; let appConfig: AppConfigService; @@ -59,6 +64,7 @@ describe('ProcessInstanceListComponent', () => { }); fixture = TestBed.createComponent(ProcessInstanceListComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); appConfig = TestBed.inject(AppConfigService); service = TestBed.inject(ProcessService); @@ -68,11 +74,9 @@ describe('ProcessInstanceListComponent', () => { }; }); - it('should display loading spinner', () => { + it('should display loading spinner', async () => { component.isLoading = true; - - const spinner = fixture.debugElement.query(By.css('.mat-progress-spinner')); - expect(spinner).toBeDefined(); + await loader.getHarness(MatProgressSpinnerHarness); }); it('should use the default schemaColumn as default', () => { @@ -619,6 +623,7 @@ describe('ProcessListContextMenuComponent', () => { let customComponent: ProcessListContextMenuComponent; let processService: ProcessService; let element: HTMLElement; + let loader: HarnessLoader; beforeEach(() => { TestBed.configureTestingModule({ @@ -631,6 +636,7 @@ describe('ProcessListContextMenuComponent', () => { fixture = TestBed.createComponent(ProcessListContextMenuComponent); customComponent = fixture.componentInstance; element = fixture.nativeElement; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); processService = TestBed.inject(ProcessService); customComponent.appId = 12345; spyOn(processService, 'getProcesses').and.returnValue(of(fakeProcessInstance)); @@ -645,15 +651,13 @@ describe('ProcessListContextMenuComponent', () => { const contextMenu = element.querySelector(`[data-automation-id="text_${fakeProcessInstance.data[0].name}"]`); const contextActionSpy = spyOn(customComponent.contextAction, 'emit').and.callThrough(); contextMenu.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true })); - fixture.detectChanges(); - await fixture.whenStable(); - const contextActions = document.querySelectorAll('.mat-menu-item'); + const contextActions = await loader.getAllHarnesses(MatMenuItemHarness); expect(contextActions.length).toBe(2); - expect(contextActions[0]['disabled']).toBe(false, 'View Process Details action not enabled'); - expect(contextActions[1]['disabled']).toBe(false, 'Cancel Process action not enabled'); - contextActions[0].dispatchEvent(new Event('click')); - fixture.detectChanges(); + expect(await contextActions[0].isDisabled()).toBe(false, 'View Process Details action not enabled'); + expect(await contextActions[1].isDisabled()).toBe(false, 'Cancel Process action not enabled'); + + await contextActions[0].click(); expect(contextActionSpy).toHaveBeenCalled(); }); }); diff --git a/lib/process-services/src/lib/process-list/components/start-process.component.spec.ts b/lib/process-services/src/lib/process-list/components/start-process.component.spec.ts index 18e84bdc33..01ab86bfb0 100644 --- a/lib/process-services/src/lib/process-list/components/start-process.component.spec.ts +++ b/lib/process-services/src/lib/process-list/components/start-process.component.spec.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { DebugElement, SimpleChange } from '@angular/core'; +import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AppConfigService } from '@alfresco/adf-core'; import { AppsProcessService } from '../../app-list/services/apps-process.service'; @@ -26,18 +26,22 @@ import { ProcessService } from '../services/process.service'; import { newProcess, taskFormMock, testProcessDef, testMultipleProcessDefs, testProcessDefWithForm, testProcessDefinitions } from '../../mock'; import { StartProcessInstanceComponent } from './start-process.component'; import { ProcessTestingModule } from '../../testing/process.testing.module'; -import { By } from '@angular/platform-browser'; import { TranslateModule } from '@ngx-translate/core'; import { deployedApps } from '../../mock/apps-list.mock'; import { ProcessNamePipe } from '../../pipes/process-name.pipe'; import { ProcessInstance } from '../models/process-instance.model'; import { ActivitiContentService } from '../../form/services/activiti-alfresco.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { MatFormFieldHarness } from '@angular/material/form-field/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing'; describe('StartProcessComponent', () => { let appConfig: AppConfigService; let activitiContentService: ActivitiContentService; let component: StartProcessInstanceComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let processService: ProcessService; let appsProcessService: AppsProcessService; let getDefinitionsSpy: jasmine.Spy; @@ -52,16 +56,13 @@ describe('StartProcessComponent', () => { }); }); - const selectOptionByName = (name: string) => { + const selectOptionByName = async (name: string) => { const selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); selectElement.click(); - fixture.detectChanges(); - const options: any = fixture.debugElement.queryAll(By.css('.mat-option-text')); - const currentOption = options.find((option: DebugElement) => option.nativeElement.innerHTML.trim() === name); - - if (currentOption) { - currentOption.nativeElement.click(); - } + const autocompleteDropdown = await loader.getHarness(MatAutocompleteHarness); + const options = await autocompleteDropdown.getOptions({ text: name }); + expect(options.length).toBe(1); + await options[0].click(); }; /** @@ -80,6 +81,7 @@ describe('StartProcessComponent', () => { activitiContentService = TestBed.inject(ActivitiContentService); fixture = TestBed.createComponent(StartProcessInstanceComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); processService = TestBed.inject(ProcessService); appsProcessService = TestBed.inject(AppsProcessService); @@ -175,11 +177,8 @@ describe('StartProcessComponent', () => { component.processDefinitionInput.setValue('My Default Name'); component.processNameInput.setValue('claim'); - fixture.detectChanges(); - await fixture.whenStable(); - - const inputLabelsNodes = document.querySelectorAll('.adf-start-process .adf-process-input-container mat-label'); - expect(inputLabelsNodes.length).toBe(2); + const inputLabels = await loader.getAllHarnesses(MatFormFieldHarness.with({ ancestor: '.adf-start-process', selector: '.adf-process-input-container' })); + expect(inputLabels.length).toBe(2); }); it('should have floating labels for process name and type', async () => { @@ -313,13 +312,10 @@ describe('StartProcessComponent', () => { const selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); selectElement.click(); - fixture.detectChanges(); - await fixture.whenStable(); - const options: any = fixture.debugElement.queryAll(By.css('.mat-option-text')); - + const options = await (await loader.getHarness(MatAutocompleteHarness)).getOptions(); expect(options.length).toBe(2); - expect(options[0].nativeElement.innerText).toBe('My Process 1'); - expect(options[1].nativeElement.innerText).toBe('My Process 2'); + expect(await options[0].getText()).toBe('My Process 1'); + expect(await options[1].getText()).toBe('My Process 2'); }); it('should show no process available message when no process definition is loaded', async () => { @@ -511,24 +507,21 @@ describe('StartProcessComponent', () => { expect(startProcessEmitterSpy).toHaveBeenCalledWith(newProcess); }); - it('should emit processDefinitionSelection event when a process definition is selected', () => { + it('should emit processDefinitionSelection event when a process definition is selected', async () => { const processDefinitionSelectionSpy = spyOn(component.processDefinitionSelection, 'emit'); - fixture.detectChanges(); - selectOptionByName(testProcessDef.name); + await selectOptionByName(testProcessDef.name); expect(processDefinitionSelectionSpy).toHaveBeenCalledWith(testProcessDef); }); - it('should set the process name using the processName pipe when a process definition gets selected', () => { + it('should set the process name using the processName pipe when a process definition gets selected', async () => { const processNamePipe = TestBed.inject(ProcessNamePipe); const processNamePipeTransformSpy = spyOn(processNamePipe, 'transform').and.returnValue('fake-transformed-name'); const expectedProcessInstanceDetails = new ProcessInstance({ processDefinitionName: testProcessDef.name }); getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); - changeAppId(123); - fixture.detectChanges(); - selectOptionByName(testProcessDef.name); + await selectOptionByName(testProcessDef.name); expect(processNamePipeTransformSpy).toHaveBeenCalledWith(component.name, expectedProcessInstanceDetails); expect(component.nameController.dirty).toBe(true); @@ -570,16 +563,16 @@ describe('StartProcessComponent', () => { getDeployedApplicationsSpy = spyOn(appsProcessService, 'getDeployedApplications').and.returnValue(of(deployedApps)); }); - it('Should be able to show application drop-down if showSelectApplicationDropdown set to true', () => { + it('Should be able to show application drop-down if showSelectApplicationDropdown set to true', async () => { getDefinitionsSpy.and.returnValue(of(testMultipleProcessDefs)); changeAppId(3); fixture.detectChanges(); const appsSelector = fixture.nativeElement.querySelector('[data-automation-id="adf-start-process-apps-drop-down"]'); - const labelElement = fixture.nativeElement.querySelector('.adf-start-process-app-list .mat-form-field-label'); + const labelText = await (await loader.getHarness(MatFormFieldHarness.with({ selector: '.adf-start-process-app-list' }))).getLabel(); expect(appsSelector).not.toBeNull(); - expect(labelElement.innerText).toEqual('ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.SELECT_APPLICATION'); + expect(labelText).toEqual('ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.SELECT_APPLICATION'); expect(getDeployedApplicationsSpy).toHaveBeenCalled(); expect(component.applications.length).toBe(6); diff --git a/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts b/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts index 172528faee..04b48f8284 100644 --- a/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts +++ b/lib/process-services/src/lib/process-user-info/process-user-info.component.spec.ts @@ -22,15 +22,18 @@ import { MatMenuModule } from '@angular/material/menu'; import { By } from '@angular/platform-browser'; import { TranslateModule } from '@ngx-translate/core'; import { BpmUserModel } from '../common/models/bpm-user.model'; - import { ProcessUserInfoComponent } from './process-user-info.component'; import { fakeBpmUser } from './mocks/bpm-user.service.mock'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatTabGroupHarness, MatTabHarness } from '@angular/material/tabs/testing'; describe('ProcessUserInfoComponent', () => { const profilePictureUrl = 'alfresco-logo.svg'; let component: ProcessUserInfoComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let element: HTMLElement; const openUserInfo = () => { @@ -56,6 +59,7 @@ describe('ProcessUserInfoComponent', () => { }); fixture = TestBed.createComponent(ProcessUserInfoComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); element = fixture.nativeElement; spyOn(window, 'requestAnimationFrame').and.returnValue(1); @@ -120,9 +124,8 @@ describe('ProcessUserInfoComponent', () => { it('should not show the tabs', async () => { await whenFixtureReady(); openUserInfo(); - await fixture.whenStable(); - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('#tab-group-env')).classes['adf-hide-tab']).toBeTruthy(); + const tabGroupHost = await (await loader.getHarness(MatTabGroupHarness.with({ selector: '#tab-group-env' }))).host(); + expect(await tabGroupHost.hasClass('adf-hide-tab')).toBeTruthy(); }); }); @@ -138,19 +141,16 @@ describe('ProcessUserInfoComponent', () => { it('should show the tabs', async () => { await whenFixtureReady(); openUserInfo(); - await fixture.whenStable(); - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('#tab-group-env')).classes['adf-hide-tab']).toBeFalsy(); + const tabGroupHost = await (await loader.getHarness(MatTabGroupHarness.with({ selector: '#tab-group-env' }))).host(); + expect(await tabGroupHost.hasClass('adf-hide-tab')).toBeFalsy(); }); it('should get the bpm user information', async () => { spyOn(component, 'getBpmUserImage').and.returnValue(profilePictureUrl); await whenFixtureReady(); openUserInfo(); - const bpmTab = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label'))[1]; - bpmTab.triggerEventHandler('click', null); - fixture.detectChanges(); - await fixture.whenStable(); + const bpmTab = await loader.getHarness(MatTabHarness.with({ label: 'USER_PROFILE.TAB.PS' })); + await bpmTab.select(); const bpmUsername = fixture.debugElement.query(By.css('#bpm-username')); const bpmImage = fixture.debugElement.query(By.css('#bpm-user-detail-image')); expect(element.querySelector('#userinfo_container')).not.toBeNull(); @@ -198,24 +198,19 @@ describe('ProcessUserInfoComponent', () => { it('should show the tabs for the env', async () => { await whenFixtureReady(); openUserInfo(); - const tabGroup = fixture.debugElement.query(By.css('#tab-group-env')); - const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label')); + const tabGroup = await loader.getHarness(MatTabGroupHarness.with({ selector: '#tab-group-env' })); + const tabs = await tabGroup.getTabs(); - expect(tabGroup).not.toBeNull(); - expect(tabGroup.classes['adf-hide-tab']).toBeFalsy(); + expect(await (await tabGroup.host()).hasClass('adf-hide-tab')).toBeFalsy(); expect(tabs.length).toBe(2); }); it('should not close the menu when a tab is clicked', async () => { await whenFixtureReady(); openUserInfo(); - const tabGroup = fixture.debugElement.query(By.css('#tab-group-env')); + const bpmTab = await loader.getHarness(MatTabHarness.with({ label: 'USER_PROFILE.TAB.PS' })); - const tabs = fixture.debugElement.queryAll(By.css('#tab-group-env .mat-tab-labels .mat-tab-label')); - - expect(tabGroup).not.toBeNull(); - tabs[1].triggerEventHandler('click', null); - fixture.detectChanges(); + bpmTab.select(); expect(fixture.debugElement.query(By.css('#user-profile-lists'))).not.toBeNull(); }); }); diff --git a/lib/process-services/src/lib/task-list/components/start-task.component.spec.ts b/lib/process-services/src/lib/task-list/components/start-task.component.spec.ts index 6522155115..9e24516487 100644 --- a/lib/process-services/src/lib/task-list/components/start-task.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/start-task.component.spec.ts @@ -24,11 +24,14 @@ import { ProcessTestingModule } from '../../testing/process.testing.module'; import { taskDetailsMock } from '../../mock/task/task-details.mock'; import { TaskDetailsModel } from '../models/task-details.model'; import { TranslateModule } from '@ngx-translate/core'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatButtonHarness } from '@angular/material/button/testing'; describe('StartTaskComponent', () => { - let component: StartTaskComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let service: TaskListService; let logService: LogService; let element: HTMLElement; @@ -51,14 +54,12 @@ describe('StartTaskComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - ProcessTestingModule - ] + imports: [TranslateModule.forRoot(), ProcessTestingModule] }); fixture = TestBed.createComponent(StartTaskComponent); component = fixture.componentInstance; element = fixture.nativeElement; + loader = TestbedHarnessEnvironment.loader(fixture); service = TestBed.inject(TaskListService); logService = TestBed.inject(LogService); @@ -81,16 +82,15 @@ describe('StartTaskComponent', () => { }); describe('create task', () => { - beforeEach(() => { - createNewTaskSpy = spyOn(service, 'createNewTask').and.returnValue(of( - { + createNewTaskSpy = spyOn(service, 'createNewTask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: null, assignee: null - } as any - )); + } as any) + ); }); it('should create new task when start is clicked', () => { @@ -140,25 +140,25 @@ describe('StartTaskComponent', () => { describe('attach form', () => { beforeEach(() => { - spyOn(service, 'createNewTask').and.returnValue(of( - { + spyOn(service, 'createNewTask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: null, assignee: null - } as any - )); + } as any) + ); }); it('should attach form to the task when a form is selected', () => { - spyOn(service, 'attachFormToATask').and.returnValue(of( - { + spyOn(service, 'attachFormToATask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: 1204, assignee: null - } - )); + }) + ); const successSpy = spyOn(component.success, 'emit'); component.taskForm.controls['name'].setValue('fakeName'); component.taskForm.controls['formKey'].setValue(1204); @@ -176,14 +176,14 @@ describe('StartTaskComponent', () => { }); it('should not attach form to the task when a no form is selected', () => { - spyOn(service, 'attachFormToATask').and.returnValue(of( - { + spyOn(service, 'attachFormToATask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: null, assignee: null - } - )); + }) + ); const successSpy = spyOn(component.success, 'emit'); component.taskForm.controls['name'].setValue('fakeName'); component.taskForm.controls['formKey'].setValue(null); @@ -203,30 +203,30 @@ describe('StartTaskComponent', () => { describe('assign user', () => { beforeEach(() => { - spyOn(service, 'createNewTask').and.returnValue(of( - { + spyOn(service, 'createNewTask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: null, assignee: null - } as any - )); - spyOn(service, 'attachFormToATask').and.returnValue(of( - { + } as any) + ); + spyOn(service, 'attachFormToATask').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: 1204, assignee: null - } - )); - spyOn(service, 'assignTaskByUserId').and.returnValue(of( - { + }) + ); + spyOn(service, 'assignTaskByUserId').and.returnValue( + of({ id: 91, name: 'fakeName', formKey: 1204, assignee: testUser - } as any - )); + } as any) + ); }); it('should assign task when an assignee is selected', () => { @@ -285,7 +285,7 @@ describe('StartTaskComponent', () => { it('should not attach a form when a form id is not selected', () => { const attachFormToATask = spyOn(service, 'attachFormToATask').and.returnValue(of([])); - spyOn(service, 'createNewTask').and.returnValue(of(new TaskDetailsModel({ id: 'task-id'}))); + spyOn(service, 'createNewTask').and.returnValue(of(new TaskDetailsModel({ id: 'task-id' }))); component.taskForm.controls['name'].setValue('fakeName'); fixture.detectChanges(); const createTaskButton = element.querySelector('#button-start'); @@ -301,9 +301,11 @@ describe('StartTaskComponent', () => { expect(element.querySelector('#button-start').textContent).toContain('ADF_TASK_LIST.START_TASK.FORM.ACTION.START'); }); - it('should render start task button with primary color', () => { + it('should render start task button with primary color', async () => { fixture.detectChanges(); - expect(element.querySelector('#button-start').classList.contains('mat-primary')).toBeTruthy(); + + const buttonEl = await (await loader.getHarness(MatButtonHarness.with({ selector: '#button-start' }))).host(); + expect(await buttonEl.getAttribute('color')).toBe('primary'); }); it('should render task buttons with uppercase text', () => { diff --git a/lib/process-services/src/lib/task-list/components/task-details.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-details.component.spec.ts index 266fef636a..b4243f30f8 100644 --- a/lib/process-services/src/lib/task-list/components/task-details.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-details.component.spec.ts @@ -21,13 +21,7 @@ import { By } from '@angular/platform-browser'; import { of, throwError } from 'rxjs'; import { FormModel, FormOutcomeEvent, FormOutcomeModel, CommentModel, User } from '@alfresco/adf-core'; import { TaskDetailsModel } from '../models/task-details.model'; -import { - noDataMock, - taskDetailsMock, - taskFormMock, - tasksMock, - taskDetailsWithOutAssigneeMock -} from '../../mock'; +import { noDataMock, taskDetailsMock, taskFormMock, tasksMock, taskDetailsWithOutAssigneeMock } from '../../mock'; import { TaskListService } from './../services/tasklist.service'; import { TaskDetailsComponent } from './task-details.component'; import { ProcessTestingModule } from '../../testing/process.testing.module'; @@ -37,6 +31,9 @@ import { TaskFormService } from '../../form/services/task-form.service'; import { TaskCommentsService } from '../../task-comments/services/task-comments.service'; import { UserProcessModel } from '../../common/models/user-process.model'; import { PeopleProcessService } from '../../common/services/people-process.service'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { MatDialogHarness } from '@angular/material/dialog/testing'; const fakeUser = new UserProcessModel({ id: 'fake-id', @@ -58,6 +55,7 @@ describe('TaskDetailsComponent', () => { let taskFormService: TaskFormService; let component: TaskDetailsComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let getTaskDetailsSpy: jasmine.Spy; let getCommentsSpy: jasmine.Spy; let getTasksSpy: jasmine.Spy; @@ -67,10 +65,7 @@ describe('TaskDetailsComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot(), - ProcessTestingModule - ], + imports: [TranslateModule.forRoot(), ProcessTestingModule], schemas: [NO_ERRORS_SCHEMA] }); peopleProcessService = TestBed.inject(PeopleProcessService); @@ -92,15 +87,18 @@ describe('TaskDetailsComponent', () => { assignTaskSpy = spyOn(taskListService, 'assignTask').and.returnValue(of(fakeTaskAssignResponse)); taskCommentsService = TestBed.inject(TaskCommentsService); - getCommentsSpy = spyOn(taskCommentsService, 'get').and.returnValue(of([ - new CommentModel({ message: 'Test1', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }), - new CommentModel({ message: 'Test2', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }), - new CommentModel({ message: 'Test3', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }) - ])); + getCommentsSpy = spyOn(taskCommentsService, 'get').and.returnValue( + of([ + new CommentModel({ message: 'Test1', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }), + new CommentModel({ message: 'Test2', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }), + new CommentModel({ message: 'Test3', created: new Date(), createdBy: new User({ firstName: 'Admin', lastName: 'User' }) }) + ]) + ); fixture = TestBed.createComponent(TaskDetailsComponent); peopleProcessService = TestBed.inject(PeopleProcessService); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); }); afterEach(() => { @@ -122,7 +120,7 @@ describe('TaskDetailsComponent', () => { it('should send a claim task event when a task is claimed', () => { let lastValue: string; - component.claimedTask.subscribe((taskId) => lastValue = taskId); + component.claimedTask.subscribe((taskId) => (lastValue = taskId)); component.onClaimAction('FAKE-TASK-CLAIM'); expect(lastValue).toBe('FAKE-TASK-CLAIM'); }); @@ -182,7 +180,6 @@ describe('TaskDetailsComponent', () => { })); describe('change detection', () => { - let change; let nullChange; @@ -209,7 +206,6 @@ describe('TaskDetailsComponent', () => { }); describe('Form events', () => { - beforeEach(() => { component.taskId = '123'; fixture.detectChanges(); @@ -289,15 +285,13 @@ describe('TaskDetailsComponent', () => { expect(emitSpy).toHaveBeenCalled(); }); - it('should display a dialog to the user when a form error occurs', () => { - let dialogEl = window.document.querySelector('mat-dialog-content'); + it('should display a dialog to the user when a form error occurs', async () => { + const dialogEl = await loader.getHarnessOrNull(MatDialogHarness); expect(dialogEl).toBeNull(); component.onFormError({}); - fixture.detectChanges(); - dialogEl = window.document.querySelector('mat-dialog-content'); - expect(dialogEl).not.toBeNull(); + await loader.getHarness(MatDialogHarness); }); it('should emit a task created event when checklist task is created', () => { @@ -306,10 +300,9 @@ describe('TaskDetailsComponent', () => { component.onChecklistTaskCreated(mockTask); expect(emitSpy).toHaveBeenCalled(); }); - }); + }); describe('Comments', () => { - it('should comments be readonly if the task is complete and no user are involved', () => { component.showComments = true; component.showHeaderContent = true; @@ -381,27 +374,31 @@ describe('TaskDetailsComponent', () => { }); describe('assign task to user', () => { - beforeEach(() => { component.taskId = '123'; fixture.detectChanges(); }); it('should return an observable with user search results', () => { - spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([{ - id: 1, - firstName: 'fake-test-1', - lastName: 'fake-last-1', - email: 'fake-test-1@test.com' - }, { - id: 2, - firstName: 'fake-test-2', - lastName: 'fake-last-2', - email: 'fake-test-2@test.com' - }])); + spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue( + of([ + { + id: 1, + firstName: 'fake-test-1', + lastName: 'fake-last-1', + email: 'fake-test-1@test.com' + }, + { + id: 2, + firstName: 'fake-test-2', + lastName: 'fake-last-2', + email: 'fake-test-2@test.com' + } + ]) + ); let lastValue: UserProcessModel[]; - component.peopleSearch.subscribe((users) => lastValue = users); + component.peopleSearch.subscribe((users) => (lastValue = users)); component.searchUser('fake-search-word'); expect(lastValue.length).toBe(2); @@ -415,7 +412,7 @@ describe('TaskDetailsComponent', () => { spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([])); let lastValue: UserProcessModel[]; - component.peopleSearch.subscribe((users) => lastValue = users); + component.peopleSearch.subscribe((users) => (lastValue = users)); component.searchUser('fake-search-word'); expect(lastValue.length).toBe(0); diff --git a/lib/process-services/src/lib/task-list/components/task-list.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-list.component.spec.ts index cd8ec39896..d65a66475b 100644 --- a/lib/process-services/src/lib/task-list/components/task-list.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-list.component.spec.ts @@ -30,12 +30,17 @@ import { } from '../../mock'; import { TranslateService, TranslateModule } from '@ngx-translate/core'; import { of, Subject } from 'rxjs'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { MatCheckboxHarness } from '@angular/material/checkbox/testing'; +import { MatMenuItemHarness } from '@angular/material/menu/testing'; declare let jasmine: any; describe('TaskListComponent', () => { let component: TaskListComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; let appConfig: AppConfigService; let taskListService: TaskListService; @@ -71,27 +76,19 @@ describe('TaskListComponent', () => { component.selectionMode = selectionMode; } component.ngOnChanges({ sort: state }); - fixture.detectChanges(); - await fixture.whenStable(); - const selectTask1 = fixture.nativeElement.querySelector('[data-automation-id="datatable-row-0"] .mat-checkbox-inner-container'); - const selectTask2 = fixture.nativeElement.querySelector('[data-automation-id="datatable-row-1"] .mat-checkbox-inner-container'); - selectTask1.dispatchEvent(new MouseEvent('click', { bubbles: true })); - selectTask1.dispatchEvent(new MouseEvent('click', { bubbles: true })); - selectTask2.dispatchEvent(new MouseEvent('click', { bubbles: true })); - - fixture.detectChanges(); - await fixture.whenStable(); + const selectTask1 = await loader.getHarness(MatCheckboxHarness.with({ ancestor: '[data-automation-id="datatable-row-0"]' })); + const selectTask2 = await loader.getHarness(MatCheckboxHarness.with({ ancestor: '[data-automation-id="datatable-row-1"]' })); + await selectTask1.toggle(); + await selectTask1.toggle(); + await selectTask2.toggle(); let selectRow1 = fixture.nativeElement.querySelector('[class*="adf-is-selected"][data-automation-id="datatable-row-0"]'); let selectRow2 = fixture.nativeElement.querySelector('[class*="adf-is-selected"][data-automation-id="datatable-row-1"]'); expect(selectRow1).toBeDefined(); expect(selectRow2).toBeDefined(); expect(component.selectedInstances.length).toBe(2); - selectTask2.dispatchEvent(new MouseEvent('click', { bubbles: true })); - - fixture.detectChanges(); - await fixture.whenStable(); + await selectTask2.toggle(); expect(component.selectedInstances.length).toBe(1); selectRow1 = fixture.nativeElement.querySelector('[class*="adf-is-selected"][data-automation-id="datatable-row-0"]'); @@ -112,6 +109,7 @@ describe('TaskListComponent', () => { fixture = TestBed.createComponent(TaskListComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); taskListService = TestBed.inject(TaskListService); appConfig.config = Object.assign(appConfig.config, { @@ -135,25 +133,16 @@ describe('TaskListComponent', () => { it('should display loading spinner', () => { component.isLoading = true; - const spinner = fixture.debugElement.query(By.css('.mat-progress-spinner')); + const spinner = fixture.debugElement.query(By.css('.adf-task-list-loading-margin')); expect(spinner).toBeDefined(); }); it('should hide loading spinner upon loading complete', async () => { component.isLoading = true; - fixture.detectChanges(); - await fixture.whenStable(); - - let spinner = fixture.debugElement.query(By.css('.mat-progress-spinner')); - expect(spinner).toBeDefined(); + expect(fixture.debugElement.query(By.css('.adf-task-list-loading-margin'))).toBeDefined(); component.isLoading = false; - - fixture.detectChanges(); - await fixture.whenStable(); - - spinner = fixture.debugElement.query(By.css('.mat-progress-spinner')); - expect(spinner).toBeNull(); + expect(fixture.debugElement.query(By.css('.adf-task-list-loading-margin'))).toBeNull(); }); it('should use the default schemaColumn as default', () => { @@ -579,23 +568,14 @@ describe('TaskListComponent', () => { component.ngOnChanges({ sort: state }); - fixture.detectChanges(); - await fixture.whenStable(); - - const selectAllCheckbox = fixture.nativeElement.querySelector('div[class*="adf-datatable-cell-header adf-datatable-checkbox"] .mat-checkbox-inner-container'); - selectAllCheckbox.click(); - - fixture.detectChanges(); - await fixture.whenStable(); + const selectAllCheckbox = await loader.getHarness(MatCheckboxHarness.with({ ancestor: '.adf-datatable-cell-header' })); + await selectAllCheckbox.toggle(); expect(component.selectedInstances.length).toBe(2); expect(component.selectedInstances[0].obj.name).toBe('nameFake1'); expect(component.selectedInstances[1].obj.description).toBe('descriptionFake2'); - selectAllCheckbox.click(); - - fixture.detectChanges(); - await fixture.whenStable(); + await selectAllCheckbox.toggle(); expect(component.selectedInstances.length).toBe(0); }); @@ -622,16 +602,13 @@ describe('TaskListComponent', () => { component.selectionMode = 'single'; component.ngOnChanges({ sort: state }); - fixture.detectChanges(); - const selectTask1 = fixture.nativeElement.querySelector('[data-automation-id="datatable-row-0"] .mat-checkbox-inner-container'); - const selectTask2 = fixture.nativeElement.querySelector('[data-automation-id="datatable-row-1"] .mat-checkbox-inner-container'); - selectTask1.dispatchEvent(new MouseEvent('click', { bubbles: true })); - selectTask1.dispatchEvent(new MouseEvent('click', { bubbles: true })); - selectTask2.dispatchEvent(new MouseEvent('click', { bubbles: true })); + const selectTask1 = await loader.getHarness(MatCheckboxHarness.with({ ancestor: '[data-automation-id="datatable-row-0"]' })); + const selectTask2 = await loader.getHarness(MatCheckboxHarness.with({ ancestor: '[data-automation-id="datatable-row-1"]' })); + await selectTask1.toggle(); + await selectTask1.toggle(); + await selectTask2.toggle(); - fixture.detectChanges(); - await fixture.whenStable(); expect(component.selectedInstances.length).toBe(2); }); @@ -825,6 +802,7 @@ class TaskListContextMenuComponent implements OnInit { describe('TaskListContextMenuComponent', () => { let fixture: ComponentFixture; let customComponent: TaskListContextMenuComponent; + let loader: HarnessLoader; let taskListService: TaskListService; let element: HTMLElement; @@ -840,6 +818,7 @@ describe('TaskListContextMenuComponent', () => { }); fixture = TestBed.createComponent(TaskListContextMenuComponent); customComponent = fixture.componentInstance; + loader = TestbedHarnessEnvironment.documentRootLoader(fixture); element = fixture.nativeElement; taskListService = TestBed.inject(TaskListService); spyOn(taskListService, 'findTasksByState').and.returnValues(of(fakeGlobalTask)); @@ -854,15 +833,12 @@ describe('TaskListContextMenuComponent', () => { const contextMenu = element.querySelector(`[data-automation-id="text_${fakeGlobalTask.data[0].name}"]`); const contextActionSpy = spyOn(customComponent.contextAction, 'emit').and.callThrough(); contextMenu.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true })); - fixture.detectChanges(); - await fixture.whenStable(); - const contextActions = document.querySelectorAll('.mat-menu-item'); + const contextActions = await loader.getAllHarnesses(MatMenuItemHarness); expect(contextActions.length).toBe(2); - expect(contextActions[0]['disabled']).toBe(false, 'View Task Details action not enabled'); - expect(contextActions[1]['disabled']).toBe(false, 'Cancel Task action not enabled'); - contextActions[0].dispatchEvent(new Event('click')); - fixture.detectChanges(); + expect(await contextActions[0].isDisabled()).toBe(false, 'View Task Details action not enabled'); + expect(await contextActions[1].isDisabled()).toBe(false, 'Cancel Task action not enabled'); + await contextActions[0].click(); expect(contextActionSpy).toHaveBeenCalled(); }); });