Enhancement: improve layout for custom fields dropdown (#6362)

This commit is contained in:
shamoon
2024-05-01 21:57:18 -07:00
parent 39b57f695a
commit 2de9d1b7ae
6 changed files with 544 additions and 259 deletions

View File

@@ -1,5 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { ToastService } from 'src/app/services/toast.service'
@@ -71,28 +75,33 @@ describe('CustomFieldsDropdownComponent', () => {
let addedField
component.added.subscribe((f) => (addedField = f))
component.documentId = 11
component.field = fields[0].id
component.addField()
component.addField({ field: fields[0].id } as any)
expect(addedField).not.toBeUndefined()
})
it('should clear field on open / close, updated unused fields', () => {
component.field = fields[1].id
component.onOpenClose()
expect(component.field).toBeUndefined()
expect(component.unusedFields).toEqual(fields)
const updateSpy = jest.spyOn(
CustomFieldsDropdownComponent.prototype as any,
'updateUnusedFields'
)
component.existingFields = [{ field: fields[1].id } as any]
component.onOpenClose()
expect(updateSpy).toHaveBeenCalled()
expect(component.unusedFields).toEqual([fields[0]])
it('should support filtering fields', () => {
const input = fixture.debugElement.query(By.css('input'))
input.nativeElement.value = 'Field 1'
input.triggerEventHandler('input', { target: input.nativeElement })
fixture.detectChanges()
expect(component.filteredFields.length).toEqual(1)
expect(component.filteredFields[0].name).toEqual('Field 1')
})
it('should support creating field, show error if necessary', () => {
it('should support update unused fields', () => {
component.existingFields = [{ field: fields[0].id } as any]
component['updateUnusedFields']()
expect(component['unusedFields'].length).toEqual(1)
expect(component['unusedFields'][0].name).toEqual('Field 2')
})
it('should support getting data type label', () => {
expect(component.getDataTypeLabel(CustomFieldDataType.Integer)).toEqual(
'Integer'
)
})
it('should support creating field, show error if necessary, then add', fakeAsync(() => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
const toastErrorSpy = jest.spyOn(toastService, 'showError')
@@ -101,8 +110,9 @@ describe('CustomFieldsDropdownComponent', () => {
CustomFieldsDropdownComponent.prototype as any,
'getFields'
)
const addFieldSpy = jest.spyOn(component, 'addField')
const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
const createButton = fixture.debugElement.queryAll(By.css('button'))[3]
createButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined()
@@ -115,9 +125,11 @@ describe('CustomFieldsDropdownComponent', () => {
// succeed
editDialog.succeeded.emit(fields[0])
tick(100)
expect(toastInfoSpy).toHaveBeenCalled()
expect(getFieldsSpy).toHaveBeenCalled()
})
expect(addFieldSpy).toHaveBeenCalled()
}))
it('should support creating field with name', () => {
let modal: NgbModalRef
@@ -128,4 +140,106 @@ describe('CustomFieldsDropdownComponent', () => {
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
expect(editDialog.object.name).toEqual('Foo bar')
})
it('should support arrow keyboard navigation', fakeAsync(() => {
fixture.nativeElement
.querySelector('button')
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
tick(100)
const filterInputEl: HTMLInputElement =
component.listFilterTextInput.nativeElement
expect(document.activeElement).toEqual(filterInputEl)
const itemButtons = Array.from(
(fixture.nativeElement as HTMLDivElement).querySelectorAll(
'.custom-fields-dropdown button'
)
).filter((b) => b.textContent.includes('Field'))
filterInputEl.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
)
expect(document.activeElement).toEqual(itemButtons[0])
itemButtons[0].dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
)
expect(document.activeElement).toEqual(itemButtons[1])
itemButtons[1].dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
)
expect(document.activeElement).toEqual(itemButtons[0])
itemButtons[0].dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
)
expect(document.activeElement).toEqual(filterInputEl)
filterInputEl.value = 'foo'
component.filterText = 'foo'
// dont move focus if we're traversing the field
filterInputEl.selectionStart = 1
expect(document.activeElement).toEqual(filterInputEl)
// now we're at end, so move focus
filterInputEl.selectionStart = 3
filterInputEl.dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
)
expect(document.activeElement).toEqual(itemButtons[0])
}))
it('should support arrow keyboard navigation after tab keyboard navigation', fakeAsync(() => {
fixture.nativeElement
.querySelector('button')
.dispatchEvent(new MouseEvent('click')) // open
fixture.detectChanges()
tick(100)
const filterInputEl: HTMLInputElement =
component.listFilterTextInput.nativeElement
expect(document.activeElement).toEqual(filterInputEl)
const itemButtons = Array.from(
(fixture.nativeElement as HTMLDivElement).querySelectorAll(
'.custom-fields-dropdown button'
)
).filter((b) => b.textContent.includes('Field'))
filterInputEl.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
)
itemButtons[0]['focus']() // normally handled by browser
itemButtons[0].dispatchEvent(
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
)
itemButtons[1]['focus']() // normally handled by browser
itemButtons[1].dispatchEvent(
new KeyboardEvent('keydown', {
key: 'Tab',
shiftKey: true,
bubbles: true,
})
)
itemButtons[0]['focus']() // normally handled by browser
itemButtons[0].dispatchEvent(
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
)
expect(document.activeElement).toEqual(itemButtons[1])
}))
it('should support enter keyboard navigation', fakeAsync(() => {
jest.spyOn(component, 'canCreateFields', 'get').mockReturnValue(true)
const addFieldSpy = jest.spyOn(component, 'addField')
const createFieldSpy = jest.spyOn(component, 'createField')
component.filterText = 'Field 1'
component.listFilterEnter()
expect(addFieldSpy).toHaveBeenCalled()
component.filterText = 'Field 3'
component.listFilterEnter()
expect(createFieldSpy).toHaveBeenCalledWith('Field 3')
addFieldSpy.mockClear()
createFieldSpy.mockClear()
component.filterText = undefined
component.listFilterEnter()
expect(createFieldSpy).not.toHaveBeenCalled()
expect(addFieldSpy).not.toHaveBeenCalled()
}))
})