diff --git a/src/app/modules/angular-slickgrid/editorValidators/floatValidator.ts b/src/app/modules/angular-slickgrid/editorValidators/floatValidator.ts deleted file mode 100644 index 5ae9b4d69..000000000 --- a/src/app/modules/angular-slickgrid/editorValidators/floatValidator.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Constants } from '../constants'; -import { EditorValidatorOutput } from '../models/editorValidatorOutput.interface'; -import { EditorValidator } from '../models/editorValidator.interface'; - -interface FloatValidatorOptions { - editorArgs: any; - decimal?: number; - errorMessage?: string; - minValue?: string | number; - maxValue?: string | number; - operatorConditionalType?: 'inclusive' | 'exclusive'; - required?: boolean; - validator?: EditorValidator; -} - -export function floatValidator(inputValue: any, options: FloatValidatorOptions): EditorValidatorOutput { - const floatNumber = !isNaN(inputValue as number) ? parseFloat(inputValue) : null; - const decPlaces = options.decimal || 0; - const isRequired = options.required; - const minValue = options.minValue; - const maxValue = options.maxValue; - const operatorConditionalType = options.operatorConditionalType || 'inclusive'; - const errorMsg = options.errorMessage; - const mapValidation = { - '{{minValue}}': minValue, - '{{maxValue}}': maxValue, - '{{minDecimal}}': 0, - '{{maxDecimal}}': decPlaces - }; - let isValid = true; - let outputMsg = ''; - - if (typeof options.validator === 'function') { - return options.validator(inputValue, options.editorArgs); - } else if (isRequired && inputValue === '') { - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_REQUIRED_FIELD; - } else if (inputValue !== '' && (isNaN(inputValue as number) || (decPlaces === 0 && !/^[-+]?(\d*(\.)?(\d)*)$/.test(inputValue)))) { - // when decimal value is 0 (which is the default), we accept 0 or more decimal values - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_VALID_NUMBER; - } else if (minValue !== undefined && maxValue !== undefined && floatNumber !== null && ((operatorConditionalType === 'exclusive' && (floatNumber <= minValue || floatNumber >= maxValue)) || (operatorConditionalType === 'inclusive' && (floatNumber < minValue || floatNumber > maxValue)))) { - // MIN & MAX Values provided - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_NUMBER_BETWEEN.replace(/{{minValue}}|{{maxValue}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (minValue !== undefined && floatNumber !== null && ((operatorConditionalType === 'exclusive' && floatNumber <= minValue) || (operatorConditionalType === 'inclusive' && floatNumber < minValue))) { - // MIN VALUE ONLY - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_NUMBER_MIN_INCLUSIVE : Constants.VALIDATION_EDITOR_NUMBER_MIN; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{minValue}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (maxValue !== undefined && floatNumber !== null && ((operatorConditionalType === 'exclusive' && floatNumber >= maxValue) || (operatorConditionalType === 'inclusive' && floatNumber > maxValue))) { - // MAX VALUE ONLY - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_NUMBER_MAX_INCLUSIVE : Constants.VALIDATION_EDITOR_NUMBER_MAX; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{maxValue}}/gi, (matched) => (mapValidation as any)[matched]); - } else if ((decPlaces > 0 && !new RegExp(`^[-+]?(\\d*(\\.)?(\\d){0,${decPlaces}})$`).test(inputValue))) { - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_DECIMAL_BETWEEN.replace(/{{minDecimal}}|{{maxDecimal}}/gi, (matched) => (mapValidation as any)[matched]); - } - - return { valid: isValid, msg: outputMsg }; -} diff --git a/src/app/modules/angular-slickgrid/editorValidators/index.ts b/src/app/modules/angular-slickgrid/editorValidators/index.ts deleted file mode 100644 index f2d11a399..000000000 --- a/src/app/modules/angular-slickgrid/editorValidators/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './floatValidator'; -export * from './integerValidator'; -export * from './sliderValidator'; -export * from './textValidator'; diff --git a/src/app/modules/angular-slickgrid/editorValidators/integerValidator.ts b/src/app/modules/angular-slickgrid/editorValidators/integerValidator.ts deleted file mode 100644 index b43e14c70..000000000 --- a/src/app/modules/angular-slickgrid/editorValidators/integerValidator.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Constants } from '../constants'; -import { EditorValidatorOutput } from '../models/editorValidatorOutput.interface'; -import { EditorValidator } from '../models/editorValidator.interface'; - -interface IntegerValidatorOptions { - editorArgs: any; - errorMessage?: string; - minValue?: string | number; - maxValue?: string | number; - operatorConditionalType?: 'inclusive' | 'exclusive'; - required?: boolean; - validator?: EditorValidator; -} - -export function integerValidator(inputValue: any, options: IntegerValidatorOptions): EditorValidatorOutput { - let intNumber = !isNaN(inputValue as number) ? parseInt(inputValue, 10) : null; - if (intNumber !== null && isNaN(intNumber)) { - intNumber = null; - } - const errorMsg = options.errorMessage; - const isRequired = options.required; - const minValue = options.minValue; - const maxValue = options.maxValue; - const operatorConditionalType = options.operatorConditionalType || 'inclusive'; - const mapValidation = { - '{{minValue}}': minValue, - '{{maxValue}}': maxValue - }; - let isValid = true; - let outputMsg = ''; - - if (options.validator) { - return options.validator(inputValue, options.editorArgs); - } else if (isRequired && inputValue === '') { - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_REQUIRED_FIELD; - } else if (inputValue !== '' && ((isNaN(inputValue as number) || !/^[+-]?\d+$/.test(inputValue)))) { - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_VALID_INTEGER; - } else if (minValue !== undefined && maxValue !== undefined && intNumber !== null && ((operatorConditionalType === 'exclusive' && (intNumber <= minValue || intNumber >= maxValue)) || (operatorConditionalType === 'inclusive' && (intNumber < minValue || intNumber > maxValue)))) { - // MIN & MAX Values provided (between) - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_INTEGER_BETWEEN.replace(/{{minValue}}|{{maxValue}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (minValue !== undefined && intNumber !== null && ((operatorConditionalType === 'exclusive' && intNumber <= minValue) || (operatorConditionalType === 'inclusive' && intNumber !== null && intNumber < minValue))) { - // MIN VALUE ONLY - // when decimal value has to be higher then provided minValue - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_INTEGER_MIN_INCLUSIVE : Constants.VALIDATION_EDITOR_INTEGER_MIN; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{minValue}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (maxValue !== undefined && intNumber !== null && ((operatorConditionalType === 'exclusive' && intNumber >= maxValue) || (operatorConditionalType === 'inclusive' && intNumber !== null && intNumber > maxValue))) { - // MAX VALUE ONLY - // when decimal value has to be lower then provided maxValue - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_INTEGER_MAX_INCLUSIVE : Constants.VALIDATION_EDITOR_INTEGER_MAX; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{maxValue}}/gi, (matched) => (mapValidation as any)[matched]); - } - - return { valid: isValid, msg: outputMsg }; -} diff --git a/src/app/modules/angular-slickgrid/editorValidators/sliderValidator.ts b/src/app/modules/angular-slickgrid/editorValidators/sliderValidator.ts deleted file mode 100644 index 8e76477c7..000000000 --- a/src/app/modules/angular-slickgrid/editorValidators/sliderValidator.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Constants } from '../constants'; -import { EditorValidatorOutput } from '../models/editorValidatorOutput.interface'; -import { EditorValidator } from '../models/editorValidator.interface'; - -interface SliderValidatorOptions { - editorArgs: any; - errorMessage?: string; - minValue?: string | number; - maxValue?: string | number; - required?: boolean; - validator?: EditorValidator; -} - -export function sliderValidator(inputValue: any, options: SliderValidatorOptions): EditorValidatorOutput { - const isRequired = options.required; - const minValue = options.minValue; - const maxValue = options.maxValue; - const errorMsg = options.errorMessage; - const mapValidation = { - '{{minValue}}': minValue, - '{{maxValue}}': maxValue - }; - - if (options.validator) { - return options.validator(inputValue, options.editorArgs); - } else if (isRequired && inputValue === '') { - return { - valid: false, - msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD - }; - } else if (minValue !== undefined && maxValue !== undefined && inputValue !== null && (inputValue < minValue || inputValue > maxValue)) { - // when decimal value is bigger than 0, we only accept the decimal values as that value set - // for example if we set decimalPlaces to 2, we will only accept numbers between 0 and 2 decimals - return { - valid: false, - msg: errorMsg || Constants.VALIDATION_EDITOR_NUMBER_BETWEEN.replace(/{{minValue}}|{{maxValue}}/gi, (matched) => { - return (mapValidation as any)[matched]; - }) - }; - } - - return { valid: true, msg: null }; -} diff --git a/src/app/modules/angular-slickgrid/editorValidators/textValidator.ts b/src/app/modules/angular-slickgrid/editorValidators/textValidator.ts deleted file mode 100644 index d73d7f7a2..000000000 --- a/src/app/modules/angular-slickgrid/editorValidators/textValidator.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Constants } from '../constants'; -import { EditorValidatorOutput } from '../models/editorValidatorOutput.interface'; -import { EditorValidator } from '../models/editorValidator.interface'; - -interface TextValidatorOptions { - editorArgs: any; - errorMessage?: string; - minLength?: number; - maxLength?: number; - operatorConditionalType?: 'inclusive' | 'exclusive'; - required?: boolean; - validator?: EditorValidator; -} - -export function textValidator(inputValue: any, options: TextValidatorOptions): EditorValidatorOutput { - const errorMsg = options.errorMessage; - const isRequired = options.required; - const minLength = options.minLength; - const maxLength = options.maxLength; - const operatorConditionalType = options.operatorConditionalType || 'inclusive'; - const mapValidation = { - '{{minLength}}': minLength, - '{{maxLength}}': maxLength - }; - let isValid = true; - let outputMsg = ''; - const inputValueLength = inputValue && inputValue.length || 0; - - if (options.validator) { - return options.validator(inputValue, options.editorArgs); - } - - // by default the editor is almost always valid (except when it's required but not provided) - if (isRequired && inputValue === '') { - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_REQUIRED_FIELD; - } else if (minLength !== undefined && maxLength !== undefined && ((operatorConditionalType === 'exclusive' && (inputValueLength <= minLength || inputValueLength >= maxLength)) || (operatorConditionalType === 'inclusive' && (inputValueLength < minLength || inputValueLength > maxLength)))) { - // MIN & MAX Length provided - // make sure text length is between minLength and maxLength - isValid = false; - outputMsg = errorMsg || Constants.VALIDATION_EDITOR_TEXT_LENGTH_BETWEEN.replace(/{{minLength}}|{{maxLength}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (minLength !== undefined && inputValueLength !== null && ((operatorConditionalType === 'exclusive' && inputValueLength <= minLength) || (operatorConditionalType === 'inclusive' && inputValueLength !== null && inputValueLength < minLength))) { - // MIN Length ONLY - // when text length is shorter than minLength - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_TEXT_MIN_LENGTH_INCLUSIVE : Constants.VALIDATION_EDITOR_TEXT_MIN_LENGTH; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{minLength}}/gi, (matched) => (mapValidation as any)[matched]); - } else if (maxLength !== undefined && inputValueLength !== null && ((operatorConditionalType === 'exclusive' && inputValueLength >= maxLength) || (operatorConditionalType === 'inclusive' && inputValueLength !== null && inputValueLength > maxLength))) { - // MAX Length ONLY - // when text length is longer than minLength - isValid = false; - const defaultErrorMsg = operatorConditionalType === 'inclusive' ? Constants.VALIDATION_EDITOR_TEXT_MAX_LENGTH_INCLUSIVE : Constants.VALIDATION_EDITOR_TEXT_MAX_LENGTH; - outputMsg = errorMsg || defaultErrorMsg.replace(/{{maxLength}}/gi, (matched) => (mapValidation as any)[matched]); - } - - return { valid: isValid, msg: outputMsg }; -} diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/autoCompleteEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/autoCompleteEditor.spec.ts deleted file mode 100644 index 5eb964c4f..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/autoCompleteEditor.spec.ts +++ /dev/null @@ -1,763 +0,0 @@ -import { Editors } from '../index'; -import { AutoCompleteEditor } from '../autoCompleteEditor'; -import { AutocompleteOption, Column, EditorArgs, EditorArguments, GridOption, KeyCode, FieldType, ColumnEditor } from '../../models'; - -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; - -// define a
container to simulate the grid container -const template = `
`; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getActiveCell: jest.fn(), - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('AutoCompleteEditor', () => { - let divContainer: HTMLDivElement; - let editor: AutoCompleteEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.autoComplete }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new AutoCompleteEditor(null); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - - it('should throw an error when collection is not a valid array', (done) => { - try { - // @ts-ignore - (mockColumn.internalColumnEditor as ColumnEditor).collection = { hello: 'world' }; - editor = new AutoCompleteEditor(editorArguments); - } catch (e) { - expect(e.toString()).toContain(`The "collection" passed to the Autocomplete Editor is not a valid array.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 123, gender: 'male', isActive: true }; - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.autoComplete }, internalColumnEditor: {} } as Column; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new AutoCompleteEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-gender').length; - const autocompleteUlElms = document.body.querySelectorAll('ul.ui-autocomplete'); - - expect(editor.instance).toBeTruthy(); - expect(autocompleteUlElms.length).toBe(1); - expect(editorCount).toBe(1); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new AutoCompleteEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-gender').length; - const autocompleteUlElms = document.body.querySelectorAll('ul.ui-autocomplete'); - - expect(autocompleteUlElms.length).toBe(1); - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-gender'); - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-gender'); - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "setValue" and expect the DOM element to have the same value when calling "getValue"', () => { - editor = new AutoCompleteEditor(editorArguments); - editor.setValue('male'); - - expect(editor.getValue()).toBe('male'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getValue()).toBe('male'); - }); - - it('should define an item datacontext containing a complex object as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - mockItemData = { id: 123, gender: { value: 'male', label: 'Male' }, isActive: true }; - mockColumn.field = 'gender.value'; - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getValue()).toBe('male'); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should render the DOM element with different key/value pair when user provide its own customStructure', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ option: 'male', text: 'Male' }, { option: 'female', text: 'Female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).customStructure = { value: 'option', label: 'text' }; - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: 109, bubbles: true, cancelable: true }); - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(editor.elementCollection).toEqual([{ value: 'male', label: 'Male', labelPrefix: '', labelSuffix: '' }, { value: 'female', label: 'Female', labelPrefix: '', labelSuffix: '' }]); - }); - - it('should return True when calling "isValueChanged()" method with previously dispatched keyboard event being char "a"', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new AutoCompleteEditor(editorArguments); - editor.setValue('z'); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when calling "isValueChanged()" method with previously dispatched keyboard event is same char as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editor.loadValue({ id: 123, gender: 'a', isActive: true }); - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when calling "isValueChanged()" method with previously dispatched keyboard event as ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).alwaysSaveOnEnterKey = true; - - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should call "focus()" method and expect the DOM element to be focused and selected', () => { - editor = new AutoCompleteEditor(editorArguments); - const editorElm = editor.editorDomElement; - const spy = jest.spyOn(editorElm, 'focus'); - editor.focus(); - - expect(spy).toHaveBeenCalled(); - }); - - describe('collectionOverride callback option', () => { - it('should create the editor and expect a different collection outputed when using the override', () => { - mockColumn.internalColumnEditor = { - collection: [{ value: 'other', label: 'Other' }, { value: 'male', label: 'Male' }, { value: 'female', label: 'Female' }], - collectionOverride: (inputCollection) => inputCollection.filter(item => item.value !== 'other') - }; - - editor = new AutoCompleteEditor(editorArguments); - editor.destroy(); - editor.init(); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-gender').length; - - expect(editorCount).toBe(1); - expect(editor.elementCollection).toEqual([{ value: 'male', label: 'Male', labelPrefix: '', labelSuffix: '' }, { value: 'female', label: 'Female', labelPrefix: '', labelSuffix: '' }]); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the gender property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null; - mockItemData = { id: 123, gender: 'female', isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.applyValue(mockItemData, { value: 'female', label: 'female' }); - - expect(mockItemData).toEqual({ id: 123, gender: { value: 'female', label: 'female' }, isActive: true }); - }); - - it('should apply the value to the gender property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null; - mockColumn.field = 'user.gender'; - mockItemData = { id: 1, user: { gender: 'female' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.applyValue(mockItemData, { value: 'female', label: 'female' }); - - expect(mockItemData).toEqual({ id: 1, user: { gender: { value: 'female', label: 'female' } }, isActive: true }); - }); - - it('should return override the item data as a string found from the collection that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null; - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 123, gender: 'female', isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.applyValue(mockItemData, 'female'); - - expect(mockItemData).toEqual({ id: 123, gender: 'female', isActive: true }); - }); - - it('should return item data with an empty string in its value when calling "applyValue" which fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any, args: EditorArgs) => { - if (value.label.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 123, gender: 'female', isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.applyValue(mockItemData, 'female'); - - expect(mockItemData).toEqual({ id: 123, gender: '', isActive: true }); - }); - }); - - describe('forceUserInput flag', () => { - it('should return DOM element value when "forceUserInput" is enabled and loaded value length is greater then minLength defined when calling "serializeValue"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { forceUserInput: true, }; - mockItemData = { id: 123, gender: { value: 'male', label: 'Male' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('Female'); - const output = editor.serializeValue(); - - expect(output).toBe('Female'); - }); - - it('should return DOM element value when "forceUserInput" is enabled and loaded value length is greater then custom minLength defined when calling "serializeValue"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { forceUserInput: true, minLength: 2 } as AutocompleteOption; - mockItemData = { id: 123, gender: { value: 'male', label: 'Male' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('Female'); - const output = editor.serializeValue(); - - expect(output).toBe('Female'); - }); - - it('should return loaded value when "forceUserInput" is enabled and loaded value length is lower than minLength defined when calling "serializeValue"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { forceUserInput: true, } as AutocompleteOption; - mockItemData = { id: 123, gender: { value: 'male', label: 'Male' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('F'); - const output = editor.serializeValue(); - - expect(output).toBe('male'); - }); - }); - - describe('openSearchListOnFocus flag', () => { - it('should open the search list by calling the AutoComplete "search" event with an empty string when there are no search term provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { openSearchListOnFocus: true, } as AutocompleteOption; - - const event = new (window.window as any).KeyboardEvent('click', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - - editor = new AutoCompleteEditor(editorArguments); - const autoCompleteSpy = jest.spyOn(editor.editorDomElement, 'autocomplete'); - - const editorElm = divContainer.querySelector('input.editor-gender'); - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(autoCompleteSpy).toHaveBeenCalledWith('search', ''); - }); - - it('should open the search list by calling the AutoComplete "search" event with the same search term string that was provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { openSearchListOnFocus: true, } as AutocompleteOption; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - const event = new (window.window as any).KeyboardEvent('click', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - - editor = new AutoCompleteEditor(editorArguments); - const autoCompleteSpy = jest.spyOn(editor.editorDomElement, 'autocomplete'); - editor.loadValue(mockItemData); - editor.setValue('Female'); - - const editorElm = divContainer.querySelector('input.editor-gender'); - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(autoCompleteSpy).toHaveBeenCalledWith('search', 'Female'); - }); - }); - - describe('serializeValue method', () => { - it('should return correct object value even when defining a "customStructure" when calling "serializeValue"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ option: 'male', text: 'Male' }, { option: 'female', text: 'Female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).customStructure = { value: 'option', label: 'text' }; - mockItemData = { id: 123, gender: { option: 'female', text: 'Female' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('female'); - }); - - it('should return an object output when calling "serializeValue" with its column definition set to "FieldType.object" with default label/value', () => { - mockColumn.type = FieldType.object; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual({ value: 'f', label: 'Female' }); - }); - - it('should return an object output when calling "serializeValue" with its column definition set to "FieldType.object" with custom dataKey/labelKey pair', () => { - mockColumn.type = FieldType.object; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - mockColumn.dataKey = 'id'; - mockColumn.labelKey = 'name'; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual({ id: 'f', name: 'Female' }); - }); - }); - - describe('save method', () => { - it('should call "getEditorLock" when "hasAutoCommitEdit" is enabled after calling "save()" method', () => { - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new AutoCompleteEditor(editorArguments); - editor.setValue('a'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" when "hasAutoCommitEdit" is disabled after calling "save()" method', () => { - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new AutoCompleteEditor(editorArguments); - editor.setValue('a'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is lower than a minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is at least 5 character(s)' }); - }); - - it('should return False when field is lower than a minLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is more than 5 character(s)' }); - }); - - it('should return True when field is equal to the minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is greater than a maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - - it('should return False when field is greater than a maxLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 10 characters' }); - }); - - it('should return True when field is equal to the maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is equal to the maxLength defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to the maxLength defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 16 characters' }); - }); - - it('should return False when field is not between minLength & maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text length is between 0 and 10 characters' }); - }); - - it('should return True when field is is equal to maxLength defined when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is is equal to minLength defined when "operatorType" is set to "inclusive" and both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 15; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to maxLength but "operatorType" is set to "exclusive" when both min/max lengths are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new AutoCompleteEditor(editorArguments); - const validation1 = editor.validate('text is 16 chars'); - const validation2 = editor.validate('text'); - - expect(validation1).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - expect(validation2).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new AutoCompleteEditor(editorArguments); - const validation = editor.validate('Task is longer than 10 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - }); - - describe('handleSelect method', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should expect "setValue" to have been called but not "autoCommitEdit" when "autoCommitEdit" is disabled', () => { - const spyCommitEdit = jest.spyOn(gridStub, 'getEditorLock'); - gridOptionMock.autoCommitEdit = false; - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 123, gender: 'female', isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - const spySetValue = jest.spyOn(editor, 'setValue'); - const output = editor.handleSelect(null, { item: mockItemData.gender }); - - expect(output).toBe(false); - expect(spyCommitEdit).not.toHaveBeenCalled(); - expect(spySetValue).toHaveBeenCalledWith('female'); - }); - - it('should expect "setValue" and "autoCommitEdit" to have been called with a string when item provided is a string', (done) => { - const spyCommitEdit = jest.spyOn(gridStub, 'getEditorLock'); - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 123, gender: 'female', isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - const spySetValue = jest.spyOn(editor, 'setValue'); - const output = editor.handleSelect(null, { item: mockItemData.gender }); - - // HOW DO WE TRIGGER the jQuery UI autocomplete select event? The following works only on "autocompleteselect" - // but that doesn't trigger the "select" (handleSelect) directly - // const editorElm = editor.editorDomElement; - // editorElm.on('autocompleteselect', (event, ui) => console.log(ui)); - // editorElm[0].dispatchEvent(new (window.window as any).CustomEvent('autocompleteselect', { detail: { item: 'female' }, bubbles: true, cancelable: true })); - - setTimeout(() => { - expect(output).toBe(false); - expect(spyCommitEdit).toHaveBeenCalled(); - expect(spySetValue).toHaveBeenCalledWith('female'); - done(); - }); - }); - - it('should expect "setValue" and "autoCommitEdit" to have been called with the string label when item provided is an object', () => { - const spyCommitEdit = jest.spyOn(gridStub, 'getEditorLock'); - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - editor = new AutoCompleteEditor(editorArguments); - const spySetValue = jest.spyOn(editor, 'setValue'); - const output = editor.handleSelect(null, { item: mockItemData.gender }); - - expect(output).toBe(false); - expect(spyCommitEdit).toHaveBeenCalled(); - expect(spySetValue).toHaveBeenCalledWith('Female'); - }); - - it('should expect the "handleSelect" method to be called when the callback method is triggered when user provide his own filterOptions', () => { - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { source: [], minLength: 3 } as AutocompleteOption; - - const event = new CustomEvent('change'); - editor = new AutoCompleteEditor(editorArguments); - const spy = jest.spyOn(editor, 'handleSelect'); - editor.autoCompleteOptions.select!(event, { item: 'fem' }); - - expect(spy).toHaveBeenCalledWith(event, { item: 'fem' }); - }); - - it('should expect the "handleSelect" method to be called when the callback method is triggered', () => { - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - const event = new CustomEvent('change'); - editor = new AutoCompleteEditor(editorArguments); - const spy = jest.spyOn(editor, 'handleSelect'); - editor.autoCompleteOptions.select!(event, { item: 'fem' }); - - expect(spy).toHaveBeenCalledWith(event, { item: 'fem' }); - }); - - it('should initialize the editor with editorOptions and expect the "handleSelect" method to be called when the callback method is triggered', (done) => { - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'm', label: 'Male' }, { value: 'f', label: 'Female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - mockItemData = { id: 123, gender: { value: 'f', label: 'Female' }, isActive: true }; - - const event = new CustomEvent('change'); - editor = new AutoCompleteEditor(editorArguments); - const handleSelectSpy = jest.spyOn(editor, 'handleSelect'); - const focusSpy = jest.spyOn(editor, 'focus'); - editor.autoCompleteOptions.select!(event, { item: 'fem' }); - - expect(handleSelectSpy).toHaveBeenCalledWith(event, { item: 'fem' }); - setTimeout(() => { - expect(focusSpy).toHaveBeenCalled(); - done(); - }, 51); - }); - - it('should expect the "onSelect" method to be called when defined and the callback method is triggered when user provide his own filterOptions', () => { - gridOptionMock.autoCommitEdit = true; - const mockOnSelect = jest.fn(); - const activeCellMock = { row: 1, cell: 0 }; - jest.spyOn(gridStub, 'getActiveCell').mockReturnValue(activeCellMock); - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { source: [], minLength: 3, onSelect: mockOnSelect } as AutocompleteOption; - - const event = new CustomEvent('change'); - editor = new AutoCompleteEditor(editorArguments); - const handleSelectSpy = jest.spyOn(editor, 'handleSelect'); - editor.autoCompleteOptions.select!(event, { item: 'fem' }); - - expect(handleSelectSpy).toHaveBeenCalledWith(event, { item: 'fem' }); - expect(mockOnSelect).toHaveBeenCalledWith(event, { item: 'fem' }, activeCellMock.row, activeCellMock.cell, mockColumn, mockItemData); - }); - }); - - describe('renderItem callback method', () => { - it('should be able to override any jQuery UI callback method', () => { - const mockCallback = (ul: HTMLElement, item: any) => { - return $('
  • ') - .data('item.autocomplete', item) - .append(`
    Hello World`) - .appendTo(ul); - }; - mockColumn.internalColumnEditor = { - editorOptions: { - source: [], - classes: { 'ui-autocomplete': 'autocomplete-custom-four-corners' }, - } as AutocompleteOption, - callbacks: { _renderItem: mockCallback }, - }; - - editor = new AutoCompleteEditor(editorArguments); - - expect(editor.instance).toBeTruthy(); - expect(editor.instance._renderItem).toEqual(mockCallback); - }); - - it('should provide "renderItem" in the "filterOptions" and expect the jQueryUI "_renderItem" to be overriden', () => { - const mockTemplateString = `
    Hello World
    `; - const mockTemplateCallback = (item) => mockTemplateString; - mockColumn.internalColumnEditor = { - editorOptions: { - source: [], - renderItem: { - layout: 'fourCorners', - templateCallback: mockTemplateCallback - }, - } as AutocompleteOption, - }; - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - editor = new AutoCompleteEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-gender'); - const autoCompleteSpy = jest.spyOn(editor.editorDomElement, 'autocomplete'); - editorElm.focus(); - editorElm.dispatchEvent(event); - - expect(editor.editorDomElement).toBeTruthy(); - expect(editor.instance).toBeTruthy(); - expect(editor.autoCompleteOptions).toEqual(expect.objectContaining({ classes: { 'ui-autocomplete': 'autocomplete-custom-four-corners' } })); - expect(autoCompleteSpy).toHaveBeenCalledWith('instance'); - expect(editor.instance._renderItem).toEqual(expect.any(Function)); - - const ulElm = document.createElement('ul'); - editor.instance._renderItem(ulElm, { name: 'John' }); - - const liElm = ulElm.querySelector('li'); - expect(liElm.innerHTML).toBe(mockTemplateString); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/checkboxEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/checkboxEditor.spec.ts deleted file mode 100644 index 8ca15b160..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/checkboxEditor.spec.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { Editors } from '../index'; -import { CheckboxEditor } from '../checkboxEditor'; -import { AutocompleteOption, Column, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; -import { ColumnEditor } from '../../../../../../dist/public_api'; - -const KEY_CHAR_SPACE = 32; -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('CheckboxEditor', () => { - let divContainer: HTMLDivElement; - let editor: CheckboxEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'isActive', field: 'isActive', editable: true, editor: { model: Editors.checkbox }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new CheckboxEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - mockColumn = { id: 'isActive', field: 'isActive', editable: true, editor: { model: Editors.checkbox }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new CheckboxEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-checkbox.editor-isActive').length; - expect(editorCount).toBe(1); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new CheckboxEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-checkbox.editor-isActive').length; - - expect(editorCount).toBe(1); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new CheckboxEditor(editorArguments); - const editorDomElm = editor.editorDomElement; - const editorElm = divContainer.querySelector('input.editor-checkbox.editor-isActive') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - expect(editorDomElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - title: 'test title', - }; - - editor = new CheckboxEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" with true and expect the DOM element value to return true (representing checked)', () => { - editor = new CheckboxEditor(editorArguments); - editor.setValue(true); - - expect(editor.getValue()).toBe(true); - }); - - it('should call "setValue" with any value and still expect the DOM element value to return true (representing checked)', () => { - editor = new CheckboxEditor(editorArguments); - editor.setValue('anything'); - - expect(editor.getValue()).toBe(true); - }); - - it('should call "setValue" with false and expect the DOM element value to return false (representing unchecked)', () => { - editor = new CheckboxEditor(editorArguments); - editor.setValue(false); - - expect(editor.getValue()).toBe(false); - }); - - it('should call "preClick" and expect the "checked" attribute to be toggled', () => { - editor = new CheckboxEditor(editorArguments); - const previousValue = editor.getValue(); - editor.preClick(); - const newValue = editor.getValue(); - - expect(previousValue).not.toEqual(newValue); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getValue()).toBe(true); - }); - - describe('isValueChanged method', () => { - it('should return True when previous event is a click event', () => { - gridOptionMock.autoCommitEdit = true; - editor = new CheckboxEditor(editorArguments); - const saveSpy = jest.spyOn(editor, 'save'); - editor.loadValue({ id: 2, title: 'task 1', isActive: true }); - - editor.editorDomElement.checked = false; - editor.editorDomElement.dispatchEvent(new (window.window as any).Event('click')); - - expect(editor.isValueChanged()).toBe(true); - expect(saveSpy).toHaveBeenCalledTimes(1); - }); - - it('should return False when previous event is not a click event', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_SPACE, bubbles: true, cancelable: true }); - - editor = new CheckboxEditor(editorArguments); - editor.loadValue({ id: 1, title: 'task 1', isActive: true }); - editor.focus(); - editor.editorDomElement.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the isActive property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new CheckboxEditor(editorArguments); - editor.applyValue(mockItemData, false); - - expect(mockItemData).toEqual({ id: 1, title: 'task 1', isActive: false }); - }); - - it('should apply the value to the title property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.isActive'; - mockItemData = { id: 1, part: { isActive: true } }; - - editor = new CheckboxEditor(editorArguments); - editor.applyValue(mockItemData, false); - - expect(mockItemData).toEqual({ id: 1, part: { isActive: false } }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a boolean true when provided a true boolean input', () => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(true); - }); - - it('should return serialized value as a boolean true when provided a true string input', () => { - mockItemData = { id: 1, title: 'task 1', isActive: 'true' }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(true); - }); - - it('should return serialized value as a boolean False when provided a false boolean input', () => { - mockItemData = { id: 1, title: 'task 1', isActive: false }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(false); - }); - - it('should return serialized value as a boolean True when provided any string', () => { - mockItemData = { id: 1, title: 'task 1', isActive: 'checked' }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(true); - }); - - it('should return serialized value as False when item value is null', () => { - mockItemData = { id: 1, title: null, isActive: null }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(false); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'task.isActive'; - mockItemData = { id: 1, task: { isActive: true } }; - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(true); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, title: 'task', isActive: false }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(true); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, title: 'task', isActive: false }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(true); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should not call anything when the input value is false but is required', () => { - mockItemData = { id: 1, title: 'task 1', isActive: false }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(false); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should not call anything when the input value is null but is required', () => { - mockItemData = { id: 1, title: 'task 1', isActive: null }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new CheckboxEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(false); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should not save when custom validation fails', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (!value) { - return { valid: false, msg: 'This must be accepted' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, title: 'task 1', isActive: false }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new CheckboxEditor(editorArguments); - editor.applyValue(mockItemData, false); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty, null or false', () => { - const expectation = { valid: false, msg: 'Field is required' }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new CheckboxEditor(editorArguments); - const validation1 = editor.validate(''); - const validation2 = editor.validate(null); - const validation3 = editor.validate(false); - - expect(validation1).toEqual(expectation); - expect(validation2).toEqual(expectation); - expect(validation3).toEqual(expectation); - }); - - it('should return True when field is required and input is provided with True', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new CheckboxEditor(editorArguments); - const validation = editor.validate(true); - - expect(validation).toEqual({ valid: true, msg: null }); - }); - - it('should return True when field is required and input is provided with any text', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new CheckboxEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: null }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/dateEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/dateEditor.spec.ts deleted file mode 100644 index 961ee3d08..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/dateEditor.spec.ts +++ /dev/null @@ -1,475 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { Editors } from '../index'; -import { DateEditor } from '../dateEditor'; -import { Column, EditorArguments, GridOption, FieldType, ColumnEditor } from '../../models'; -import * as moment from 'moment-mini'; - -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, - i18n: null, -} as unknown as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - navigateNext: jest.fn(), - navigatePrev: jest.fn(), - render: jest.fn(), -}; - -describe('DateEditor', () => { - let divContainer: HTMLDivElement; - let editor: DateEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - let translate: TranslateService; - - beforeEach(async () => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - await TestBed.configureTestingModule({ - providers: [], - imports: [TranslateModule.forRoot()] - }); - translate = TestBed.inject(TranslateService); - - translate.setTranslation('en', { - CANCEL: 'Cancel', - SAVE: 'Save', - }); - translate.setTranslation('fr', { - CANCEL: 'Annuler', - SAVE: 'Sauvegarder', - }); - translate.setDefaultLang('en'); - translate.use('en'); - - mockColumn = { id: 'startDate', field: 'startDate', editable: true, editor: { model: Editors.date }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new DateEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, startDate: '2001-01-02T11:02:02.000Z', isActive: true }; - mockColumn = { id: 'startDate', field: 'startDate', editable: true, editor: { model: Editors.date }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - gridOptionMock.i18n = translate; - editor = new DateEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-startDate').length; - expect(editorCount).toBe(1); - }); - - it('should initialize the editor and focus on the element after a small delay', (done) => { - const focusSpy = jest.spyOn(editor, 'focus'); - const showSpy = jest.spyOn(editor, 'focus'); - editor = new DateEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-startDate').length; - - setTimeout(() => { - expect(editorCount).toBe(1); - expect(focusSpy).toHaveBeenCalled(); - expect(showSpy).toHaveBeenCalled(); - done(); - }, 51); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new DateEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-startDate') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new DateEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-startDate') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - }; - - editor = new DateEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same string when calling "getValue"', () => { - editor = new DateEditor(editorArguments); - editor.setValue('2001-01-02T11:02:02.000Z'); - - expect(editor.getValue()).toBe('2001-01-02T11:02:02.000Z'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - mockItemData = { id: 1, startDate: '2001-01-02T11:02:02.000Z', isActive: true }; - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('2001-01-02T11:02:02.000Z'); - expect(editorElm.defaultValue).toBe('2001-01-02T11:02:02.000Z'); - }); - - it('should hide the DOM element when the "hide" method is called', () => { - editor = new DateEditor(editorArguments); - const spy = jest.spyOn(editor.flatInstance, 'close'); - const calendarElm = document.body.querySelector('.flatpickr-calendar'); - editor.hide(); - - expect(calendarElm).toBeTruthy(); - expect(spy).toHaveBeenCalled(); - }); - - it('should show the DOM element when the "show" method is called', () => { - editor = new DateEditor(editorArguments); - const spy = jest.spyOn(editor.flatInstance, 'open'); - const calendarElm = document.body.querySelector('.flatpickr-calendar'); - editor.show(); - editor.focus(); - - expect(calendarElm).toBeTruthy(); - expect(spy).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when date is changed in the picker', () => { - // change to allow input value only for testing purposes & use the regular flatpickr input to test that one too - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { allowInput: true, altInput: false }; - mockItemData = { id: 1, startDate: '2001-01-02T11:02:02.000Z', isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.focus(); - const editorInputElm = divContainer.querySelector('.flatpickr input') as HTMLInputElement; - editorInputElm.value = '2024-04-02T16:02:02.239Z'; - editorInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true })); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return True when date is reset by the clear date button', () => { - // change to allow input value only for testing purposes & use the regular flatpickr input to test that one too - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { allowInput: true, altInput: false }; - mockItemData = { id: 1, startDate: '2001-01-02T11:02:02.000Z', isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.focus(); - const clearBtnElm = divContainer.querySelector('.btn.icon-clear') as HTMLInputElement; - const editorInputElm = divContainer.querySelector('.flatpickr input') as HTMLInputElement; - clearBtnElm.click(); - editorInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true })); - - expect(editorInputElm.value).toBe(''); - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when date in the picker is the same as the current date', () => { - mockItemData = { id: 1, startDate: '2001-01-02T11:02:02.000Z', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { allowInput: true }; // change to allow input value only for testing purposes - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const editorInputElm = divContainer.querySelector('input.flatpickr-alt-input') as HTMLInputElement; - editorInputElm.value = '2001-01-02T11:02:02.000Z'; - editorInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true })); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when input date is invalid', () => { - mockItemData = { id: 1, startDate: '1900-02-32', isActive: true }; - mockColumn.type = FieldType.dateUs; - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { allowInput: true }; // change to allow input value only for testing purposes - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const editorInputElm = divContainer.querySelector('input.flatpickr-alt-input') as HTMLInputElement; - editorInputElm.value = '1900-02-32'; - editorInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true })); - - expect(editor.isValueChanged()).toBe(false); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the startDate property with ISO format when no "outputType" is defined and when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.type = FieldType.date; - mockItemData = { id: 1, startDate: '2001-04-05T11:33:42.000Z', isActive: true }; - - const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0)); - editor = new DateEditor(editorArguments); - editor.applyValue(mockItemData, newDate); - - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD'), isActive: true }); - }); - - it('should apply the value to the startDate property with "outputType" format with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.type = FieldType.date; - mockColumn.outputType = FieldType.dateTimeIsoAmPm; - mockColumn.field = 'employee.startDate'; - mockItemData = { id: 1, employee: { startDate: '2001-04-05T11:33:42.000Z' }, isActive: true }; - - const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0)); - editor = new DateEditor(editorArguments); - editor.applyValue(mockItemData, newDate); - - expect(mockItemData).toEqual({ id: 1, employee: { startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a') }, isActive: true }); - }); - - it('should apply the value to the startDate property with output format defined by "saveOutputType" when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.type = FieldType.date; - mockColumn.saveOutputType = FieldType.dateTimeIsoAmPm; - mockItemData = { id: 1, startDate: '2001-04-05T11:33:42.000Z', isActive: true }; - - const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0)); - editor = new DateEditor(editorArguments); - editor.applyValue(mockItemData, newDate); - - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a'), isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length > 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, startDate: '2001-04-05T11:33:42.000Z', isActive: true }; - - editor = new DateEditor(editorArguments); - editor.applyValue(mockItemData, '2001-01-02T16:02:02.000+05:00'); - - expect(mockItemData).toEqual({ id: 1, startDate: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a date string', () => { - mockColumn.type = FieldType.dateIso; - mockItemData = { id: 1, startDate: '2001-01-02T16:02:02.000+05:00', isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('2001-01-02'); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, startDate: '', isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, startDate: null, isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as a date string when using a dot (.) notation for complex object', () => { - mockColumn.type = FieldType.dateIso; - mockColumn.field = 'employee.startDate'; - mockItemData = { id: 1, employee: { startDate: '2001-01-02T16:02:02.000+05:00' }, isActive: true }; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('2001-01-02'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, startDate: '2001-01-02T16:02:02.000+05:00', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('2022-03-02T16:02:02.000+05:00'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, startDate: '2001-01-02T16:02:02.000+05:00', isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('2022-03-02T16:02:02.000+05:00'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should not call anything when the input value is empty but is required', () => { - mockItemData = { id: 1, startDate: '', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should not throw any error when date is invalid when lower than required "minDate" defined in the "editorOptions" and "autoCommitEdit" is enabled', () => { - // change to allow input value only for testing purposes & use the regular flatpickr input to test that one too - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minDate: 'today', altInput: true }; - mockItemData = { id: 1, startDate: '500-01-02T11:02:02.000Z', isActive: true }; - gridOptionMock.autoCommitEdit = true; - gridOptionMock.autoEdit = true; - gridOptionMock.editable = true; - - editor = new DateEditor(editorArguments); - editor.loadValue(mockItemData); - editor.flatInstance.toggle(); - const editorInputElm = divContainer.querySelector('.flatpickr input') as HTMLInputElement; - - expect(editor.pickerOptions).toBeTruthy(); - expect(editorInputElm.value).toBe(''); - expect(editor.serializeValue()).toBe(''); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new DateEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new DateEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: null }); - }); - }); - - describe('with different locale', () => { - it('should display a console warning when locale is not previously imported', (done) => { - const consoleSpy = jest.spyOn(global.console, 'warn').mockReturnValue(); - - gridOptionMock.i18n = translate; - translate.use('zz-yy'); // will be trimmed to 2 chars "zz" - editor = new DateEditor(editorArguments); - setTimeout(() => { - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining(`[Angular-Slickgrid] Flatpickr missing locale imports (zz), will revert to English as the default locale.`)); - done(); - }); - }); - - it('should display text in new locale', async () => { - await (await import('flatpickr/dist/l10n/fr')).French; - - gridOptionMock.i18n = translate; - translate.use('fr'); - editor = new DateEditor(editorArguments); - - const spy = jest.spyOn(editor.flatInstance, 'open'); - const calendarElm = document.body.querySelector('.flatpickr-calendar') as HTMLDivElement; - const selectonOptionElms = calendarElm.querySelectorAll(' .flatpickr-monthDropdown-months option'); - - editor.show(); - - expect(calendarElm).toBeTruthy(); - expect(selectonOptionElms.length).toBe(12); - expect(selectonOptionElms[0].textContent).toBe('janvier'); - expect(spy).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/dualInputEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/dualInputEditor.spec.ts deleted file mode 100644 index e8c460b83..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/dualInputEditor.spec.ts +++ /dev/null @@ -1,824 +0,0 @@ -import { Editors } from '../index'; -import { DualInputEditor } from '../dualInputEditor'; -import { Column, ColumnEditor, ColumnEditorDualInput, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; - -declare const Slick: any; -const KEY_CHAR_0 = 48; -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; - -jest.useFakeTimers(); - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - onValidationError: new Slick.Event(), - render: jest.fn(), -}; - -describe('DualInputEditor', () => { - let divContainer: HTMLDivElement; - let editor: DualInputEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.text }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new DualInputEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-Slickgrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - - it('should throw an error when initialize the editor without the requires params leftInput/rightInput', (done) => { - try { - // @ts-ignore - editor = new DualInputEditor({ grid: gridStub }); - } catch (e) { - expect(e.toString()).toContain(`[Angular-Slickgrid] Please make sure that your Combo Input Editor has params defined with "leftInput" and "rightInput"`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - const editorParams = { leftInput: { field: 'from', type: 'float' }, rightInput: { field: 'to', type: 'float' } } as ColumnEditorDualInput; - mockItemData = { id: 1, from: 1, to: 22, isActive: true }; - mockColumn = { - id: 'range', field: 'range', editable: true, internalColumnEditor: { params: editorParams }, - editor: { model: Editors.dualInput, params: editorParams }, - } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new DualInputEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.dual-editor-text.editor-range').length; - expect(editorCount).toBe(2); - }); - - it('should have a placeholder on the left input when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.placeholder = testValue; - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.dual-editor-text.editor-range.left') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a placeholder on the right input when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.placeholder = testValue; - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.dual-editor-text.editor-range.right') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) on left input when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.title = testValue; - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.dual-editor-text.editor-range.left') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should have a title (tooltip) on right input when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.title = testValue; - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.dual-editor-text.editor-range.right') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should have a left input as type number and right input as readonly text input when that right input is set to "readonly"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.type = 'float'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'readonly'; - - editor = new DualInputEditor(editorArguments); - const editorLeftElm = divContainer.querySelector('input.dual-editor-text.editor-range.left') as HTMLInputElement; - const editorRightElm = divContainer.querySelector('input.dual-editor-text.editor-range.right') as HTMLInputElement; - - expect(editorLeftElm.type).toBe('number'); - expect(editorRightElm.type).toBe('text'); - expect(editorLeftElm.readOnly).toBe(false); - expect(editorRightElm.readOnly).toBe(true); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { - leftInput: { - field: 'from', - placeholder: 'test placeholder', - range: 'test title', - alwaysSaveOnEnterKey: false, - }, - rightInput: { - field: 'to' - } - }; - - editor = new DualInputEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same string when calling "getValue"', () => { - editor = new DualInputEditor(editorArguments); - editor.setValues([12, 34]); - - expect(editor.getValues()).toEqual({ from: 12, to: 34 }); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editorElm).toBeTruthy(); - expect(editor.getValues()).toEqual({ from: 1, to: 22 }); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-range') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-range') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char 0', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new DualInputEditor(editorArguments); - editor.setValues(['9', '9']); - const editorElm = divContainer.querySelector('input.editor-range') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-range') as HTMLInputElement; - - editor.loadValue({ id: 1, range: '1-22', from: 0, to: 22, isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new DualInputEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-range') as HTMLInputElement; - - editor.loadValue({ id: 1, range: '1-22', from: '0', to: '22', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when left input last dispatched keyboard event is ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.alwaysSaveOnEnterKey = true; - - editor = new DualInputEditor(editorArguments); - const editorLeftElm = divContainer.querySelector('input.editor-range.left') as HTMLInputElement; - - editor.focus(); - editorLeftElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return True when right input last dispatched keyboard event is ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.alwaysSaveOnEnterKey = true; - - editor = new DualInputEditor(editorArguments); - const editorRightElm = divContainer.querySelector('input.editor-range.right') as HTMLInputElement; - - editor.focus(); - editorRightElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the range property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.validator = null; - mockItemData = { id: 1, range: '1-22', from: 0, to: 22, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.applyValue(mockItemData, { id: 1, from: 33, to: 78 }); - - expect(mockItemData).toEqual({ id: 1, range: '1-22', from: 33, to: 78, isActive: true }); - }); - - it('should apply the value to the range property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.validator = null; - mockColumn.field = 'part.from'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.field = 'part.from'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.field = 'part.to'; - mockItemData = { id: 1, part: { range: '1-22', from: 0, to: 44 }, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.applyValue(mockItemData, { id: 1, range: '1-22', from: 33, to: 78 }); - - expect(mockItemData).toEqual({ id: 1, part: { range: '1-22', from: 33, to: 78 }, isActive: true }); - }); - - it('should return item data with an empty string in its left input value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.validator = (value: any, args: EditorArgs) => { - if (+value < 10) { - return { valid: false, msg: 'From value must be over 10.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, range: '1-22', from: 22, to: 78, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.applyValue(mockItemData, { id: 1, range: '1-22', from: 4, to: 5 }); - - expect(mockItemData).toEqual({ id: 1, range: '1-22', from: '', to: 5, isActive: true }); - }); - - it('should return item data with an empty string in its right input value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.validator = (value: any, args: EditorArgs) => { - if (+value > 150) { - return { valid: false, msg: 'To value must be below 150.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, range: '1-22', from: 22, to: 78, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.applyValue(mockItemData, { id: 1, range: '1-22', from: 4, to: 155 }); - - expect(mockItemData).toEqual({ id: 1, range: '1-22', from: 4, to: '', isActive: true }); - }); - - it('should return item data with an empty strings when the shared validator fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (values: any) => { - if (values.from < 10 || values.to > 200) { - return { valid: false, msg: '"From" value must be over 10 and "To" value below 200.' }; - } - return { valid: true, msg: '' }; - }; - - mockItemData = { id: 1, range: '1-22', from: 22, to: 78, isActive: true }; - editor = new DualInputEditor(editorArguments); - const validateSpy = jest.spyOn(editor, 'validate'); - - editor.setValues([4, 5]); - editor.applyValue(mockItemData, { id: 1, range: '1-22', from: 4, to: 5 }); - - expect(mockItemData).toEqual({ id: 1, range: '1-22', from: '', to: '', isActive: true }); - expect(validateSpy).toHaveReturnedWith({ valid: false, msg: '"From" value must be over 10 and "To" value below 200.' }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a number', () => { - mockItemData = { id: 1, range: '13-22', from: 13, to: 22, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(0); - expect(editor.getDecimalPlaces('rightInput')).toBe(0); - expect(output).toEqual({ from: 13, to: 22 }); - }); - - it('should return serialized value as a float number when "decimal" is set to 2', () => { - mockItemData = { id: 1, range: '32.789-45.67', from: 32.789, to: 45.67, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.decimal = 1; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.decimal = 3; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(1); - expect(editor.getDecimalPlaces('rightInput')).toBe(3); - expect(output).toEqual({ from: 32.8, to: 45.67 }); - }); - - it('should return serialized value as a number even when the item property value is a number in a string', () => { - mockItemData = { id: 1, range: '1-33', from: '1', to: '33', isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(0); - expect(editor.getDecimalPlaces('rightInput')).toBe(0); - expect(output).toEqual({ from: 1, to: 33 }); - }); - - it('should return a rounded number when a float is provided without any decimal place defined', () => { - mockItemData = { id: 1, range: '2-32.7', from: '2', to: '32.7', isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(0); - expect(editor.getDecimalPlaces('rightInput')).toBe(0); - expect(output).toEqual({ from: 2, to: 33 }); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, range: '', from: '', to: 2, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(0); - expect(editor.getDecimalPlaces('rightInput')).toBe(0); - expect(output).toEqual({ from: '', to: 2 }); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, range: null, from: null, to: 2, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces('leftInput')).toBe(0); - expect(editor.getDecimalPlaces('rightInput')).toBe(0); - expect(output).toEqual({ from: '', to: 2 }); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'part.from'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.field = 'part.from'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.field = 'part.to'; - mockItemData = { id: 1, part: { range: '5-44', from: 5, to: 44 }, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual({ part: { from: 5, to: 44 } }); - }); - }); - - describe('getInputDecimalSteps method', () => { - it('should return decimal step as 1 increment when decimal is not set', () => { - mockItemData = { id: 1, range: '2-33', from: 2, to: 33, isActive: true }; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps('leftInput')).toBe('1'); - expect(editor.getInputDecimalSteps('rightInput')).toBe('1'); - }); - - it('should return decimal step as 0.1 increment when decimal is set to 1 decimal', () => { - mockItemData = { id: 1, range: '2-32.7', from: 2, to: 32.7, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.decimal = 1; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.decimal = 2; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps('leftInput')).toBe('0.1'); - expect(editor.getInputDecimalSteps('rightInput')).toBe('0.01'); - }); - - it('should return decimal step as 0.01 increment when decimal is set to 2 decimal', () => { - mockItemData = { id: 1, range: '2-32.7', from: 2, to: 32.7, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.decimal = 1; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.decimal = 2; - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps('leftInput')).toBe('0.1'); - expect(editor.getInputDecimalSteps('rightInput')).toBe('0.01'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, range: '3-32', from: 3, to: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValues([2, 35]); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, range: '3-32', from: 3, to: 32, isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValues([2, 35]); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - // it('should not call anything when the input value is not a valid float number', () => { - // mockItemData = { id: 1, range: null, from: null, to: null, isActive: true }; - // gridOptionMock.autoCommitEdit = true; - // const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - // editor = new DualInputEditor(editorArguments); - // editor.loadValue(mockItemData); - // editor.setValue(['-.', '-.']); - // editor.save(); - - // expect(spy).not.toHaveBeenCalled(); - // }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the left input event "focusout" is triggered', () => { - mockItemData = { id: 1, range: '3-32', from: 3, to: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyGetEditor = jest.spyOn(gridStub, 'getEditorLock'); - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValues([2, 35]); - const spySave = jest.spyOn(editor, 'save'); - const editorLeftElm = editor.editorDomElement.leftInput; - - editorLeftElm.dispatchEvent(new (window.window as any).Event('focusout')); - jest.runAllTimers(); // fast-forward timer - - expect(spyGetEditor).toHaveBeenCalled(); - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the right input event "focusout" is triggered', () => { - mockItemData = { id: 1, range: '3-32', from: 3, to: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyGetEditor = jest.spyOn(gridStub, 'getEditorLock'); - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new DualInputEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValues([2, 35]); - const spySave = jest.spyOn(editor, 'save'); - const editorRightElm = editor.editorDomElement.rightInput; - - editorRightElm.dispatchEvent(new (window.window as any).Event('focusout')); - jest.runAllTimers(); // fast-forward timer - - expect(spyGetEditor).toHaveBeenCalled(); - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should set isValueSaveCalled to true when grid object triggered an "onValidationError"', () => { - editor = new DualInputEditor(editorArguments); - jest.spyOn(editor.eventHandler, 'subscribe'); - - expect(editor.eventHandler).toBeTruthy(); - expect(editor.isValueSaveCalled).toBe(false); - gridStub.onValidationError.notify({ row: 0, cell: 0, validationResults: { valid: false, msg: 'Field is required' } }); - expect(editor.isValueSaveCalled).toBe(true); - }); - - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: '' }); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when left input field is required and its value is empty when set by "setValues"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.required = true; - editor = new DualInputEditor(editorArguments); - editor.setValues(['', 3]); - const validation = editor.validate(); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when left input field is required and its value is empty when set by "setValues"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - editor.setValues([2, '']); - const validation = editor.validate(); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when editor is float but its field is not a valid float number', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 'abc' }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number' }); - }); - - it('should return False when editor is integer but its field is not a valid integer number', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'integer'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 'abc' }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number' }); - }); - - it('should return False when editor is a required text input but its text value is not provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'text'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: '' }); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when editor is a required password input but its text value is not provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'password'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: '' }); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when field is lower than a minValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.minValue = 10.2; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is greater than or equal to 10.2' }); - }); - - it('should return False when field is lower than a minValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.minValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.operatorConditionalType = 'exclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is greater than 10.2' }); - }); - - it('should return True when field is equal to the minValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.minValue = 10.2; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 10.2 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 10.2; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10.22 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is lower than or equal to 10.2' }); - }); - - it('should return False when field is greater than a maxValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.operatorConditionalType = 'exclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10.22 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is lower than 10.2' }); - }); - - it('should return True when field is equal to the maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'float'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.maxValue = 10.2; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 10.2 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when type is set to float and its field is equal to the maxValue defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'float'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.operatorConditionalType = 'inclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10.2 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when type is set to integer and its field is equal to the maxValue defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.type = 'integer'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 11; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.operatorConditionalType = 'inclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 11 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when type is set to float and its field is equal to the maxValue defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'float'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.maxValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.operatorConditionalType = 'exclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 10.2 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is lower than 10.2' }); - }); - - it('should return False when type is set to float and its field is equal to the maxValue defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.type = 'integer'; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.maxValue = 11; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.operatorConditionalType = 'exclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 11 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number that is lower than 11' }); - }); - - it('should return False when type is set to float and its field is not between minValue & maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.minValue = 10.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 99.5; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 99.6 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number between 10.5 and 99.5' }); - }); - - it('should return False when type is set to integer and its field is not between minValue & maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.type = 'integer'; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.minValue = 11; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 99; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 100 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number between 11 and 99' }); - }); - - it('should return True when field is is equal to maxValue defined when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.minValue = 10.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.maxValue = 99.5; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 99.5 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is is equal to minValue defined when "operatorType" is set to "inclusive" and both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.minValue = 10.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.maxValue = 99.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.operatorConditionalType = 'inclusive'; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 10.5 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to maxValue but "operatorType" is set to "exclusive" when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.minValue = 10.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.maxValue = 99.5; - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.operatorConditionalType = 'exclusive'; - editor = new DualInputEditor(editorArguments); - const validation1 = editor.validate({ position: 'rightInput', inputValue: 99.5 }); - const validation2 = editor.validate({ position: 'rightInput', inputValue: 10.5 }); - - expect(validation1).toEqual({ valid: false, msg: 'Please enter a valid number between 10.5 and 99.5' }); - expect(validation2).toEqual({ valid: false, msg: 'Please enter a valid number between 10.5 and 99.5' }); - }); - - it('should return False when field has more decimals than the "decimal" which is the maximum decimal allowed', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.decimal = 2; - - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 99.6433 }); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number with a maximum of 2 decimals' }); - }); - - it('should return True when field has less decimals than the "decimal" which is valid', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.decimal = 2; - - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'rightInput', inputValue: 99.6 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field has same number of decimals than the "decimal" which is also valid', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.leftInput.decimal = 2; - - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 99.65 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is required and field is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params.rightInput.required = true; - editor = new DualInputEditor(editorArguments); - const validation = editor.validate({ position: 'leftInput', inputValue: 2.5 }); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/floatEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/floatEditor.spec.ts deleted file mode 100644 index da80b9f1a..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/floatEditor.spec.ts +++ /dev/null @@ -1,548 +0,0 @@ -import { Editors } from '../index'; -import { FloatEditor } from '../floatEditor'; -import { Column, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; -import { ColumnEditor } from '../../../../../../dist/public_api'; - -const KEY_CHAR_0 = 48; -const containerId = 'demo-container'; - -jest.useFakeTimers(); - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('FloatEditor', () => { - let divContainer: HTMLDivElement; - let editor: FloatEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.float }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new FloatEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, price: 213, isActive: true }; - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.float }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new FloatEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-price').length; - - expect(editorCount).toBe(1); - expect(editor.inputType).toBe('number'); - }); - - it('should initialize the editor and focus on the element after a small delay', () => { - editor = new FloatEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-price').length; - jest.runAllTimers(); // fast-forward timer - - expect(editorCount).toBe(1); - expect(editor.inputType).toBe('number'); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - alwaysSaveOnEnterKey: false, - }; - - editor = new FloatEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same but as a string when calling "getValue"', () => { - editor = new FloatEditor(editorArguments); - editor.setValue(123); - - expect(editor.getValue()).toBe('123'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('213'); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char 0', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new FloatEditor(editorArguments); - editor.setValue(9); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.loadValue({ id: 1, price: 0, isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.loadValue({ id: 1, price: '0', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when previously dispatched keyboard event as ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).alwaysSaveOnEnterKey = true; - - editor = new FloatEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the price property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, price: 456, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, price: 78, isActive: true }); - }); - - it('should apply the value to the price property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 456 }, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, part: { price: 78 }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (+value < 10) { - return { valid: false, msg: 'Value must be over 10.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, price: 32, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.applyValue(mockItemData, 4); - - expect(mockItemData).toEqual({ id: 1, price: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a number', () => { - mockItemData = { id: 1, price: 33, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces()).toBe(0); - expect(output).toBe(33); - }); - - it('should return serialized value as a float number when "decimalPlaces" is set to 2', () => { - mockItemData = { id: 1, price: 32.7, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 2 }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces()).toBe(2); - expect(output).toBe(32.7); - }); - - it('should return serialized value as a number even when the item property value is a number in a string', () => { - mockItemData = { id: 1, price: '33', isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(33); - }); - - it('should return a rounded number when a float is provided without any decimal place defined', () => { - mockItemData = { id: 1, price: '32.7', isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces()).toBe(0); - expect(output).toBe(33); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, price: '', isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(editor.getDecimalPlaces()).toBe(0); - expect(output).toBe(''); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 5 }, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(5); - }); - }); - - describe('getInputDecimalSteps method', () => { - it('should return decimal step as 1 increment when decimalPlaces is not set', () => { - mockItemData = { id: 1, price: 33, isActive: true }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps()).toBe('1'); - }); - - it('should return decimal step as 0.1 increment when decimalPlaces is set to 1 decimal', () => { - mockItemData = { id: 1, price: 32.7, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 1 }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps()).toBe('0.1'); - }); - - it('should return decimal step as 0.01 increment when decimalPlaces is set to 2 decimal', () => { - mockItemData = { id: 1, price: 32.7, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 2 }; - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - - expect(editor.getInputDecimalSteps()).toBe('0.01'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitCurrentEdit" even when the input value is not a valid float number', () => { - mockItemData = { id: 1, price: null, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('-.'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "focusout" is triggered', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyGetEditor = jest.spyOn(gridStub, 'getEditorLock'); - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new FloatEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).Event('focusout')); - jest.runAllTimers(); // fast-forward timer - - expect(spyGetEditor).toHaveBeenCalled(); - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when field is not a valid float number', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new FloatEditor(editorArguments); - const validation = editor.validate('abc'); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number' }); - }); - - it('should return False when field is lower than a minValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10.2; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(10); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is greater than or equal to 10.2' }); - }); - - it('should return False when field is lower than a minValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(10); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is greater than 10.2' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 10.2; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(10.22); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is lower than or equal to 10.2' }); - }); - - it('should return False when field is greater than a maxValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 10.2; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(10.22); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number that is lower than 10.2' }); - }); - - it('should return False when field is not between minValue & maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10.5; - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 99.5; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(99.6); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number between 10.5 and 99.5' }); - }); - - it('should return False when field has more decimals than the "decimalPlaces" which is the maximum decimal allowed', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 2 }; - - editor = new FloatEditor(editorArguments); - const validation = editor.validate(99.6433); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number with a maximum of 2 decimals' }); - }); - - it('should return True when field has less decimals than the "decimalPlaces" which is valid', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 2 }; - - editor = new FloatEditor(editorArguments); - const validation = editor.validate(99.6); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field has same number of decimals than the "decimalPlaces" which is also valid', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { decimalPlaces: 2 }; - - editor = new FloatEditor(editorArguments); - const validation = editor.validate(99.65); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is required and field is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new FloatEditor(editorArguments); - const validation = editor.validate(2.5); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is required and field is a valid decimal value without 0 suffix', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new FloatEditor(editorArguments); - const validation = editor.validate('.5'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/inputEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/inputEditor.spec.ts deleted file mode 100644 index d67ecf783..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/inputEditor.spec.ts +++ /dev/null @@ -1,502 +0,0 @@ -import { Editors } from '../index'; -import { InputEditor } from '../inputEditor'; -import { AutocompleteOption, Column, ColumnEditor, EditorArguments, GridOption, KeyCode } from '../../models'; - -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; -jest.useFakeTimers(); - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('InputEditor (TextEditor)', () => { - let divContainer: HTMLDivElement; - let editor: InputEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.text }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new InputEditor(null as any, 'text'); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.text }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new InputEditor(editorArguments, 'text'); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - expect(editorCount).toBe(1); - }); - - it('should initialize the editor and focus on the element after a small delay', () => { - editor = new InputEditor(editorArguments, 'text'); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - - jest.runAllTimers(); // fast-forward timer - - expect(editorCount).toBe(1); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new InputEditor(editorArguments, 'text'); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-text.editor-title') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-text.editor-title') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - alwaysSaveOnEnterKey: false, - }; - - editor = new InputEditor(editorArguments, 'text'); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same string when calling "getValue"', () => { - editor = new InputEditor(editorArguments, 'text'); - editor.setValue('task 1'); - - expect(editor.getValue()).toBe('task 1'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('task 1'); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char "a"', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new InputEditor(editorArguments, 'text'); - editor.setValue('z'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.loadValue({ id: 1, title: 'a', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when previously dispatched keyboard event as ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).alwaysSaveOnEnterKey = true; - - editor = new InputEditor(editorArguments, 'text'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the title property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: 'task 2', isActive: true }); - }); - - it('should apply the value to the title property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.title'; - mockItemData = { id: 1, part: { title: 'task 1' }, isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, part: { title: 'task 2' }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, title: '', isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, title: null, isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'task.title'; - mockItemData = { id: 1, task: { title: 'task 1' }, isActive: true }; - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should not call anything when the input value is empty but is required', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - editor.setValue(''); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "focusout" is triggered', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputEditor(editorArguments, 'text'); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).Event('focusout')); - jest.runAllTimers(); // fast-forward timer - - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is lower than a minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is at least 5 character(s)' }); - }); - - it('should return False when field is lower than a minLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is more than 5 character(s)' }); - }); - - it('should return True when field is equal to the minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is greater than a maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - - it('should return False when field is greater than a maxLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 10 characters' }); - }); - - it('should return True when field is equal to the maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is equal to the maxLength defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to the maxLength defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 16 characters' }); - }); - - it('should return False when field is not between minLength & maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text length is between 0 and 10 characters' }); - }); - - it('should return True when field is is equal to maxLength defined when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is is equal to minLength defined when "operatorType" is set to "inclusive" and both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 15; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to maxLength but "operatorType" is set to "exclusive" when both min/max lengths are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputEditor(editorArguments, 'text'); - const validation1 = editor.validate('text is 16 chars'); - const validation2 = editor.validate('text'); - - expect(validation1).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - expect(validation2).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputEditor(editorArguments, 'text'); - const validation = editor.validate('Task is longer than 10 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/inputPasswordEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/inputPasswordEditor.spec.ts deleted file mode 100644 index bb4d8666a..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/inputPasswordEditor.spec.ts +++ /dev/null @@ -1,502 +0,0 @@ -import { Editors } from '../index'; -import { InputPasswordEditor } from '../inputPasswordEditor'; -import { AutocompleteOption, Column, ColumnEditor, EditorArguments, GridOption, KeyCode } from '../../models'; - -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; -jest.useFakeTimers(); - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('InputPasswordEditor', () => { - let divContainer: HTMLDivElement; - let editor: InputPasswordEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.text }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new InputPasswordEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.text }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new InputPasswordEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - expect(editorCount).toBe(1); - }); - - it('should initialize the editor and focus on the element after a small delay', () => { - editor = new InputPasswordEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - - jest.runAllTimers(); // fast-forward timer - - expect(editorCount).toBe(1); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new InputPasswordEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-title').length; - - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-title') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-title') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - alwaysSaveOnEnterKey: false, - }; - - editor = new InputPasswordEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same string when calling "getValue"', () => { - editor = new InputPasswordEditor(editorArguments); - editor.setValue('task 1'); - - expect(editor.getValue()).toBe('task 1'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('task 1'); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char "a"', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new InputPasswordEditor(editorArguments); - editor.setValue('z'); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.loadValue({ id: 1, title: 'a', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when previously dispatched keyboard event as ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).alwaysSaveOnEnterKey = true; - - editor = new InputPasswordEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-title') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the title property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: 'task 2', isActive: true }); - }); - - it('should apply the value to the title property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.title'; - mockItemData = { id: 1, part: { title: 'task 1' }, isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, part: { title: 'task 2' }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, title: '', isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, title: null, isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'task.title'; - mockItemData = { id: 1, task: { title: 'task 1' }, isActive: true }; - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should not call anything when the input value is empty but is required', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(''); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "focusout" is triggered', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new InputPasswordEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 21'); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).Event('focusout')); - jest.runAllTimers(); // fast-forward timer - - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is lower than a minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is at least 5 character(s)' }); - }); - - it('should return False when field is lower than a minLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is more than 5 character(s)' }); - }); - - it('should return True when field is equal to the minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is greater than a maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - - it('should return False when field is greater than a maxLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 10 characters' }); - }); - - it('should return True when field is equal to the maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is equal to the maxLength defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to the maxLength defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 16 characters' }); - }); - - it('should return False when field is not between minLength & maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text length is between 0 and 10 characters' }); - }); - - it('should return True when field is is equal to maxLength defined when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is is equal to minLength defined when "operatorType" is set to "inclusive" and both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 15; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to maxLength but "operatorType" is set to "exclusive" when both min/max lengths are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new InputPasswordEditor(editorArguments); - const validation1 = editor.validate('text is 16 chars'); - const validation2 = editor.validate('text'); - - expect(validation1).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - expect(validation2).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new InputPasswordEditor(editorArguments); - const validation = editor.validate('Task is longer than 10 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/integerEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/integerEditor.spec.ts deleted file mode 100644 index 9df76e191..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/integerEditor.spec.ts +++ /dev/null @@ -1,473 +0,0 @@ -import { Editors } from '../index'; -import { IntegerEditor } from '../integerEditor'; -import { Column, ColumnEditor, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; - -const KEY_CHAR_0 = 48; -const containerId = 'demo-container'; - -jest.useFakeTimers(); - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('IntegerEditor', () => { - let divContainer: HTMLDivElement; - let editor: IntegerEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.integer }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new IntegerEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, price: 213, isActive: true }; - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.integer }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new IntegerEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-price').length; - expect(editorCount).toBe(1); - }); - - it('should initialize the editor and focus on the element after a small delay', () => { - editor = new IntegerEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('input.editor-text.editor-price').length; - - jest.runAllTimers(); // fast-forward timer - - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - alwaysSaveOnEnterKey: false, - }; - - editor = new IntegerEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same but as a string when calling "getValue"', () => { - editor = new IntegerEditor(editorArguments); - editor.setValue(123); - - expect(editor.getValue()).toBe('123'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('213'); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Left Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.LEFT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - it('should dispatch a keyboard event and expect "stopImmediatePropagation()" to have been called when using Right Arrow key', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.RIGHT, bubbles: true, cancelable: true }); - const spyEvent = jest.spyOn(event, 'stopImmediatePropagation'); - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(spyEvent).toHaveBeenCalled(); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char 0', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new IntegerEditor(editorArguments); - editor.setValue(9); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.loadValue({ id: 1, price: 0, isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_0, bubbles: true, cancelable: true }); - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.loadValue({ id: 1, price: '0', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when previously dispatched keyboard event as ENTER and "alwaysSaveOnEnterKey" is enabled', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).alwaysSaveOnEnterKey = true; - - editor = new IntegerEditor(editorArguments); - const editorElm = divContainer.querySelector('input.editor-price') as HTMLInputElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the price property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, price: 456, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, price: 78, isActive: true }); - }); - - it('should apply the value to the price property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 456 }, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, part: { price: 78 }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (+value < 10) { - return { valid: false, msg: 'Value must be over 10.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, price: 32, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.applyValue(mockItemData, 4); - - expect(mockItemData).toEqual({ id: 1, price: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a number', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(32); - }); - - it('should return serialized value as a number even when the item property value is a number in a string', () => { - mockItemData = { id: 1, price: '32', isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(32); - }); - - it('should return only the left side of the number (not rounded) when a float is provided', () => { - mockItemData = { id: 1, price: '32.7', isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(32); - }); - - it('should return original item value when this value cannot be parsed', () => { - mockItemData = { id: 1, price: '.2', isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('.2'); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, price: '', isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 5 }, isActive: true }; - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(5); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should not call anything when the input value is not a valid integer', () => { - mockItemData = { id: 1, price: '.1', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('.2'); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "focusout" is triggered', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new IntegerEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).Event('focusout')); - - jest.runAllTimers(); // fast-forward timer - - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when field is not a valid integer', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate('.2'); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number' }); - }); - - it('should return False when field is lower than a minValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(3); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number that is greater than or equal to 10' }); - }); - - it('should return False when field is lower than a minValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(3); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number that is greater than 10' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 10; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(33); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number that is lower than or equal to 10' }); - }); - - it('should return False when field is greater than a maxValue defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(33); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number that is lower than 10' }); - }); - - it('should return False when field is not between minValue & maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10; - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 99; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(345); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid integer number between 10 and 99' }); - }); - - it('should return True when field is required and field is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new IntegerEditor(editorArguments); - const validation = editor.validate(2); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/longTextEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/longTextEditor.spec.ts deleted file mode 100644 index 4a16adaf1..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/longTextEditor.spec.ts +++ /dev/null @@ -1,750 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { Editors } from '../index'; -import { LongTextEditor } from '../longTextEditor'; -import { AutocompleteOption, Column, ColumnEditor, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; -import * as utilities from '../../services/utilities'; - -const mockGetHtmlElementOffset = jest.fn(); -// @ts-ignore:2540 -utilities.getHtmlElementOffset = mockGetHtmlElementOffset; - -const KEY_CHAR_A = 97; -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, - i18n: null, -} as unknown as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - navigateNext: jest.fn(), - navigatePrev: jest.fn(), - render: jest.fn(), -}; - -describe('LongTextEditor', () => { - let divContainer: HTMLDivElement; - let editor: LongTextEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - let translate: TranslateService; - - beforeEach(async () => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - divContainer.style.height = '500px'; - divContainer.style.width = '600px'; - document.body.innerHTML = ''; - document.body.style.height = '700px'; - document.body.style.width = '1024px'; - document.body.appendChild(divContainer); - - await TestBed.configureTestingModule({ - providers: [], - imports: [TranslateModule.forRoot()] - }); - translate = TestBed.inject(TranslateService); - - translate.setTranslation('en', { - CANCEL: 'Cancel', - SAVE: 'Save', - }); - translate.setTranslation('fr', { - CANCEL: 'Annuler', - SAVE: 'Sauvegarder', - }); - translate.setDefaultLang('fr'); - translate.use('fr'); - - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.longText }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 600, width: 800, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new LongTextEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - mockColumn = { id: 'title', field: 'title', editable: true, editor: { model: Editors.longText }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - gridOptionMock.i18n = translate; - gridOptionMock.enableTranslate = true; - editor = new LongTextEditor(editorArguments); - const editorCount = document.body.querySelectorAll('.slick-large-editor-text.editor-title textarea').length; - const editorTextCounter = document.body.querySelectorAll('.slick-large-editor-text.editor-title .editor-footer .counter'); - const currentTextLengthElm = document.body.querySelector('.editor-footer .text-length') as HTMLDivElement; - const maxTextLengthElm = document.body.querySelector('.editor-footer .max-length') as HTMLDivElement; - const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement; - const buttonCancelElm = editorFooterElm.querySelector('.btn-default') as HTMLButtonElement; - const buttonSaveElm = editorFooterElm.querySelector('.btn-primary') as HTMLButtonElement; - - expect(editorCount).toBe(1); - expect(editorTextCounter.length).toBe(1); - expect(currentTextLengthElm.textContent).toBe('0'); - expect(maxTextLengthElm).toBeNull(); - expect(buttonCancelElm.textContent).toBe('Annuler'); - expect(buttonSaveElm.textContent).toBe('Sauvegarder'); - }); - - it('should initialize the editor with default constant text when translate service is not provided', () => { - gridOptionMock.i18n = null as any; - editor = new LongTextEditor(editorArguments); - const editorCount = document.body.querySelectorAll('.slick-large-editor-text.editor-title textarea').length; - const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement; - const buttonCancelElm = editorFooterElm.querySelector('.btn-default') as HTMLButtonElement; - const buttonSaveElm = editorFooterElm.querySelector('.btn-primary') as HTMLButtonElement; - - expect(editorCount).toBe(1); - expect(buttonCancelElm.textContent).toBe('Cancel'); - expect(buttonSaveElm.textContent).toBe('Save'); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new LongTextEditor(editorArguments); - const editorCount = document.body.querySelectorAll('.slick-large-editor-text.editor-title textarea').length; - - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.slick-large-editor-text.editor-title textarea') as HTMLTextAreaElement; - - expect(editorElm.placeholder).toBe(testValue); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.slick-large-editor-text.editor-title textarea') as HTMLTextAreaElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - placeholder: 'test placeholder', - title: 'test title', - }; - - editor = new LongTextEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" and expect the DOM element value to be the same string when calling "getValue"', () => { - editor = new LongTextEditor(editorArguments); - editor.setValue('task 1'); - - expect(editor.getValue()).toBe('task 1'); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - const currentTextLengthElm = document.body.querySelector('.editor-footer .text-length') as HTMLDivElement; - const maxTextLengthElm = document.body.querySelector('.editor-footer .max-length') as HTMLDivElement; - - expect(currentTextLengthElm.textContent).toBe('6'); - expect(maxTextLengthElm).toBeNull(); - expect(editor.getValue()).toBe('task 1'); - expect(editorElm.defaultValue).toBe('task 1'); - }); - - it('should hide the DOM element div wrapper when the "hide" method is called', () => { - editor = new LongTextEditor(editorArguments); - const wrapperElm = document.body.querySelector('.slick-large-editor-text.editor-title') as HTMLDivElement; - editor.show(); - expect(wrapperElm.style.display).toBe('block'); - - editor.hide(); - expect(wrapperElm.style.display).toBe('none'); - }); - - it('should show the DOM element div wrapper when the "show" method is called', () => { - editor = new LongTextEditor(editorArguments); - const wrapperElm = document.body.querySelector('.slick-large-editor-text.editor-title') as HTMLDivElement; - - editor.hide(); - expect(wrapperElm.style.display).toBe('none'); - - editor.show(); - expect(wrapperElm.style.display).toBe('block'); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched keyboard event is a new char "a" and it should also update the text counter accordingly', () => { - const eventKeyDown = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - const eventInput = new (window.window as any).Event('input', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 255; - - editor = new LongTextEditor(editorArguments); - editor.setValue('z'); - const editorElm = document.body.querySelector('.editor-title textarea') as HTMLTextAreaElement; - const currentTextLengthElm = document.body.querySelector('.editor-footer .text-length') as HTMLDivElement; - const maxTextLengthElm = document.body.querySelector('.editor-footer .max-length') as HTMLDivElement; - editor.focus(); - editorElm.dispatchEvent(eventKeyDown); - editorElm.dispatchEvent(eventInput); - - expect(currentTextLengthElm.textContent).toBe('1'); - expect(maxTextLengthElm.textContent).toBe('255'); - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched keyboard event is same string number as current value', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.editor-title textarea') as HTMLTextAreaElement; - - editor.loadValue({ id: 1, title: 'a', isActive: true }); - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True when previously dispatched keyboard event ENTER', () => { - const event = new (window.window as any).KeyboardEvent('keydown', { keyCode: KeyCode.ENTER, bubbles: true, cancelable: true }); - - editor = new LongTextEditor(editorArguments); - editor.setValue('a'); - const editorElm = document.body.querySelector('.editor-title textarea') as HTMLTextAreaElement; - - editor.focus(); - editorElm.dispatchEvent(event); - - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the title property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: 'task 2', isActive: true }); - }); - - it('should apply the value to the title property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.title'; - mockItemData = { id: 1, part: { title: 'task 1' }, isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, part: { title: 'task 2' }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.applyValue(mockItemData, 'task 2'); - - expect(mockItemData).toEqual({ id: 1, title: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, title: 'task 1', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - - it('should return serialized value as an empty string when item value is also an empty string', () => { - mockItemData = { id: 1, title: '', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, title: null, isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(''); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'task.title'; - mockItemData = { id: 1, task: { title: 'task 1' }, isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('task 1'); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 1'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is enabled but value is invalid', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - const commitCurrentSpy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - const commitChangeSpy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 1'); - editor.save(); - - expect(commitCurrentSpy).not.toHaveBeenCalled(); - expect(commitChangeSpy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue('task 1'); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should not call anything when the input value is empty but is required', () => { - mockItemData = { id: 1, title: '', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(''); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - describe('handleKeyDown private method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call the "save" method when the Ctrl+ENTER combination event is triggered', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { - keyCode: KeyCode.ENTER, - ctrlKey: true, - bubbles: true - })); - - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - }); - - it('should call the "cancel" method when the Escape keydown event is triggered', () => { - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const spyCancel = jest.spyOn(editorArguments, 'cancelChanges'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { - keyCode: KeyCode.ESCAPE, - bubbles: true - })); - - expect(spyCancel).toHaveBeenCalled(); - }); - - it('should call the grid "navigatePrev" method when the Shift+TAB combination event is triggered', () => { - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - const spyNavigate = jest.spyOn(gridStub, 'navigatePrev'); - - editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { - keyCode: KeyCode.TAB, - shiftKey: true, - bubbles: true - })); - - expect(spyNavigate).toHaveBeenCalled(); - }); - - it('should call the grid "navigateNext" method when the TAB (without shift) event is triggered', () => { - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - const spyNavigate = jest.spyOn(gridStub, 'navigateNext'); - - editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { - keyCode: KeyCode.TAB, - shiftKey: false, - bubbles: true - })); - - expect(spyNavigate).toHaveBeenCalled(); - }); - }); - - describe('on button clicked events', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should call "save" method when the save button is clicked', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const spySave = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement; - const buttonSaveElm = editorFooterElm.querySelector('.btn-primary') as HTMLButtonElement; - - buttonSaveElm.click(); - - expect(spySave).toHaveBeenCalled(); - }); - - it('should call "cancel" method when the cancel button is clicked', () => { - mockItemData = { id: 1, title: 'task', isActive: true }; - - editor = new LongTextEditor(editorArguments); - editor.loadValue(mockItemData); - const spyCancel = jest.spyOn(editorArguments, 'cancelChanges'); - const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement; - const buttonCancelElm = editorFooterElm.querySelector('.btn-default') as HTMLButtonElement; - - buttonCancelElm.click(); - - expect(spyCancel).toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is lower than a minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is at least 5 character(s)' }); - }); - - it('should return False when field is lower than a minLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 5; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is more than 5 character(s)' }); - }); - - it('should return True when field is equal to the minLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is greater than a maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - - it('should return False when field is greater than a maxLength defined using exclusive operator', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 10 characters' }); - }); - - it('should return True when field is equal to the maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is equal to the maxLength defined and "operatorType" is set to "inclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to the maxLength defined but "operatorType" is set to "exclusive"', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than 16 characters' }); - }); - - it('should return False when field is not between minLength & maxLength defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text length is between 0 and 10 characters' }); - }); - - it('should return True when field is is equal to maxLength defined when both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 0; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text is 16 chars'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return True when field is is equal to minLength defined when "operatorType" is set to "inclusive" and both min/max values are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 15; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'inclusive'; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: '' }); - }); - - it('should return False when field is equal to maxLength but "operatorType" is set to "exclusive" when both min/max lengths are defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minLength = 4; - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 16; - (mockColumn.internalColumnEditor as ColumnEditor).operatorConditionalType = 'exclusive'; - editor = new LongTextEditor(editorArguments); - const validation1 = editor.validate('text is 16 chars'); - const validation2 = editor.validate('text'); - - expect(validation1).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - expect(validation2).toEqual({ valid: false, msg: 'Please make sure your text length is between 4 and 16 characters' }); - }); - - it('should return False when field is greater than a maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - editor = new LongTextEditor(editorArguments); - const validation = editor.validate('Task is longer than 10 chars'); - - expect(validation).toEqual({ valid: false, msg: 'Please make sure your text is less than or equal to 10 characters' }); - }); - }); - - describe('Truncate Text when using maxLength', () => { - it('should truncate text to 10 chars when the provided text (with input/keydown event) is more than maxLength(10)', () => { - const eventInput = new (window.window as any).KeyboardEvent('input', { keyCode: KEY_CHAR_A, bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - - editor = new LongTextEditor(editorArguments); - - editor.setValue('some extra long text that is over the maxLength'); - const editorElm = document.body.querySelector('.editor-title textarea') as HTMLTextAreaElement; - - editor.focus(); - editorElm.dispatchEvent(eventInput); - - const currentTextLengthElm = document.body.querySelector('.editor-footer .text-length') as HTMLDivElement; - const maxTextLengthElm = document.body.querySelector('.editor-footer .max-length') as HTMLDivElement; - - expect(editorElm.value).toBe('some extra'); - expect(currentTextLengthElm.textContent).toBe('10'); - expect(maxTextLengthElm.textContent).toBe('10'); - expect(editor.isValueChanged()).toBe(true); - }); - - it('should truncate text to 10 chars when the provided text (with paste event) is more than maxLength(10)', () => { - const eventPaste = new (window.window as any).CustomEvent('paste', { bubbles: true, cancelable: true }); - (mockColumn.internalColumnEditor as ColumnEditor).maxLength = 10; - - editor = new LongTextEditor(editorArguments); - - editor.setValue('some extra long text that is over the maxLength'); - const editorElm = document.body.querySelector('.editor-title textarea') as HTMLTextAreaElement; - - editor.focus(); - editorElm.dispatchEvent(eventPaste); - - const currentTextLengthElm = document.body.querySelector('.editor-footer .text-length') as HTMLDivElement; - const maxTextLengthElm = document.body.querySelector('.editor-footer .max-length') as HTMLDivElement; - - expect(editorElm.value).toBe('some extra'); - expect(currentTextLengthElm.textContent).toBe('10'); - expect(maxTextLengthElm.textContent).toBe('10'); - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('Position Editor', () => { - beforeEach(() => { - Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 600 }); - Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1024 }); - - // cell height/width - editorArguments.position = { top: 0, left: 900, bottom: 10, right: 10, height: 100, width: 310, visible: true }; - Object.defineProperty(editorArguments.container, 'offsetHeight', { writable: true, configurable: true, value: 33 }); - Object.defineProperty(editorArguments.container, 'offsetWidth', { writable: true, configurable: true, value: 100 }); - }); - - it('should assume editor to positioned on the right & bottom of the cell when there is enough room', () => { - mockGetHtmlElementOffset.mockReturnValue({ top: 100, left: 200 }); // mock cell position - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.slick-large-editor-text') as HTMLDivElement; - - expect(editorElm.style.top).toBe('100px'); - expect(editorElm.style.left).toBe('200px'); - }); - - it('should assume editor to positioned on the right of the cell when there is NOT enough room on the left', () => { - mockGetHtmlElementOffset.mockReturnValue({ top: 100, left: 900 }); // mock cell position that will be over max of 1024px - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.slick-large-editor-text') as HTMLDivElement; - - expect(editorElm.style.top).toBe('100px'); - expect(editorElm.style.left).toBe('690px'); // cellLeftPos - (editorWidth - cellWidth + marginAdjust) => (900 - (310 - 100 + 0)) - }); - - it('should assume editor to positioned on the top of the cell when there is NOT enough room on the bottom', () => { - mockGetHtmlElementOffset.mockReturnValue({ top: 550, left: 200 }); // mock cell position that will be over max of 600px - - editor = new LongTextEditor(editorArguments); - const editorElm = document.body.querySelector('.slick-large-editor-text') as HTMLDivElement; - - expect(editorElm.style.top).toBe('483px'); - expect(editorElm.style.left).toBe('200px'); // cellTopPos - (editorHeight - cellHeight) => (550 - (100 - 33)) - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/multipleSelectEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/multipleSelectEditor.spec.ts deleted file mode 100644 index 7cf21c8b2..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/multipleSelectEditor.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -// import 3rd party lib multiple-select for the tests -import '../../../../../assets/lib/multiple-select/multiple-select'; - -import { TestBed } from '@angular/core/testing'; -import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { Editors } from '../index'; -import { MultipleSelectEditor } from '../multipleSelectEditor'; -import { Column, EditorArguments, GridOption } from '../../models'; -import { ColumnEditor } from '../../../../../../dist/public_api'; - -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, - i18n: null, -} as unknown as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - navigateNext: jest.fn(), - navigatePrev: jest.fn(), - render: jest.fn(), -}; - -describe('SelectEditor', () => { - let divContainer: HTMLDivElement; - let editor: MultipleSelectEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - let translate: TranslateService; - - beforeEach(async () => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - await TestBed.configureTestingModule({ - providers: [], - imports: [TranslateModule.forRoot()] - }); - translate = TestBed.inject(TranslateService); - - translate.setTranslation('en', { - CANCEL: 'Cancel', - SAVE: 'Save', - }); - translate.setTranslation('fr', { - CANCEL: 'Annuler', - SAVE: 'Sauvegarder', - }); - translate.setDefaultLang('fr'); - translate.use('fr'); - - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', (done) => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - gridOptionMock.i18n = translate; - editor = new MultipleSelectEditor(editorArguments); - const editorCount = document.body.querySelectorAll('select.ms-filter.editor-gender').length; - const spy = jest.spyOn(editor, 'show'); - - setTimeout(() => { - expect(spy).toHaveBeenCalled(); - expect(editorCount).toBe(1); - done(); - }); - }); - - it('should call "setValue" with a single string and expect the string to be returned as an array when calling "getValue"', () => { - editor = new MultipleSelectEditor(editorArguments); - editor.setValue(['male']); - - expect(editor.getValue()).toEqual(['male']); - }); - - it('should hide the DOM element div wrapper when the "hide" method is called', () => { - editor = new MultipleSelectEditor(editorArguments); - const editorElm = document.body.querySelector('[name=editor-gender].ms-drop') as HTMLInputElement; - - editor.show(); - expect(editorElm.style.display).toBe(''); - - editor.hide(); - expect(editorElm.style.display).toBe('none'); - }); - - it('should show the DOM element div wrapper when the "show" method is called', () => { - editor = new MultipleSelectEditor(editorArguments); - const editorElm = document.body.querySelector('[name=editor-gender].ms-drop') as HTMLInputElement; - - editor.hide(); - expect(editorElm.style.display).toBe('none'); - - editor.show(); - expect(editorElm.style.display).toBe(''); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/selectEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/selectEditor.spec.ts deleted file mode 100644 index 74542a1b3..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/selectEditor.spec.ts +++ /dev/null @@ -1,747 +0,0 @@ -// import 3rd party lib multiple-select for the tests -import '../../../../../assets/lib/multiple-select/multiple-select'; - -import { TestBed } from '@angular/core/testing'; -import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { Editors } from '../index'; -import { SelectEditor } from '../selectEditor'; -import { AutocompleteOption, Column, EditorArgs, EditorArguments, GridOption, FieldType, OperatorType, ColumnEditor } from '../../models'; - -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, - i18n: null, -} as unknown as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - navigateNext: jest.fn(), - navigatePrev: jest.fn(), - render: jest.fn(), -}; - -describe('SelectEditor', () => { - let divContainer: HTMLDivElement; - let editor: SelectEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - let translate: TranslateService; - - beforeEach(async () => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - await TestBed.configureTestingModule({ - providers: [], - imports: [TranslateModule.forRoot()] - }); - translate = TestBed.inject(TranslateService); - - translate.setTranslation('en', { - CANCEL: 'Cancel', - SAVE: 'Save', - }); - translate.setTranslation('fr', { - CANCEL: 'Annuler', - SAVE: 'Sauvegarder', - }); - translate.setDefaultLang('fr'); - translate.use('fr'); - - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new SelectEditor(null as any, true); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - - it('should throw an error when there is no collection provided in the editor property', (done) => { - try { - (mockColumn.internalColumnEditor as ColumnEditor).collection = undefined; - editor = new SelectEditor(editorArguments, true); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] You need to pass a "collection" (or "collectionAsync") inside Column Definition Editor for the MultipleSelect/SingleSelect Editor to work correctly.`); - done(); - } - }); - - it('should throw an error when collection is not a valid array', (done) => { - try { - // @ts-ignore - (mockColumn.internalColumnEditor as ColumnEditor).collection = { hello: 'world' }; - editor = new SelectEditor(editorArguments, true); - } catch (e) { - expect(e.toString()).toContain(`The "collection" passed to the Select Editor is not a valid array.`); - done(); - } - }); - - it('should throw an error when collection is not a valid value/label pair array', (done) => { - try { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ hello: 'world' }]; - editor = new SelectEditor(editorArguments, true); - } catch (e) { - expect(e.toString()).toContain(`[Angular-Slickgrid] Select Filter/Editor collection with value/label (or value/labelKey when using Locale) is required to populate the Select list`); - done(); - } - }); - - it('should throw an error when "enableTranslateLabel" is set without a valid TranslateService', (done) => { - try { - translate = undefined as any; - (mockColumn.internalColumnEditor as ColumnEditor).enableTranslateLabel = true; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - editor = new SelectEditor(editorArguments, true); - } catch (e) { - expect(e.toString()).toContain(`[Angular-Slickgrid] requires "ngx-translate" to be installed and configured when the grid option "enableTranslate" is enabled.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }, { value: 'other', label: 'other' }]; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - gridOptionMock.i18n = translate; - editor = new SelectEditor(editorArguments, true); - editor.focus(); - const editorCount = document.body.querySelectorAll('select.ms-filter.editor-gender').length; - - expect(editorCount).toBe(1); - }); - - it('should initialize the editor even when user define his own editor options', () => { - (mockColumn.internalColumnEditor as ColumnEditor).editorOptions = { minLength: 3 } as AutocompleteOption; - editor = new SelectEditor(editorArguments, true); - const editorCount = document.body.querySelectorAll('select.ms-filter.editor-gender').length; - - expect(editorCount).toBe(1); - }); - - it('should have a placeholder when defined in its column definition', () => { - const testValue = 'test placeholder'; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = testValue; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - - editor = new SelectEditor(editorArguments, true); - const editorElm = divContainer.querySelector('.ms-filter.editor-gender .placeholder') as HTMLSpanElement; - - expect(editorElm.innerHTML).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).placeholder = 'test placeholder'; - - editor = new SelectEditor(editorArguments, true); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should call "setValue" with a single string and expect the string to be returned in a single string array when calling "getValue" when using single select', () => { - editor = new SelectEditor(editorArguments, true); - editor.setValue(['male']); - - expect(editor.getValue()).toEqual(['male']); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toEqual(['male']); - expect(editorElm[0].value).toEqual('male'); - }); - - it('should create the multi-select editor with a blank entry at the beginning of the collection when "addBlankEntry" is set in the "collectionOptions" property', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).collectionOptions = { addBlankEntry: true }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - editorOkElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[0].value).toBe(''); - expect(editorListElm[1].textContent).toBe(''); - }); - - it('should create the multi-select editor with a custom entry at the beginning of the collection when "addCustomFirstEntry" is provided in the "collectionOptions" property', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).collectionOptions = { addCustomFirstEntry: { value: null, label: '' } }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - editorOkElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[0].value).toBe(''); - expect(editorListElm[1].textContent).toBe(''); - }); - - it('should create the multi-select editor with a custom entry at the end of the collection when "addCustomFirstEntry" is provided in the "collectionOptions" property', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - (mockColumn.internalColumnEditor as ColumnEditor).collectionOptions = { addCustomLastEntry: { value: null, label: '' } }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - editorOkElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[2].value).toBe(''); - expect(editorListElm[1].textContent).toBe(''); - }); - - describe('isValueChanged method', () => { - it('should return True after doing a check of an option and clicking on the OK button', () => { - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - - // we can use property "checked" or dispatch an event - editorListElm[0].dispatchEvent(new CustomEvent('click')); - editorOkElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False after doing a check & uncheck of the same option and clicking on the OK button', () => { - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - - // we can use property "checked" or dispatch an event - // check and uncheck the same option - editorListElm[0].checked = true; - editorListElm[0].checked = false; - editorOkElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editor.isValueChanged()).toBe(true); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the gender property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, 'female'); - - expect(mockItemData).toEqual({ id: 1, gender: 'female', isActive: true }); - }); - - it('should apply the value to the gender (last property) when field has a dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'person.bio.gender'; - mockItemData = { id: 1, person: { bio: { gender: 'male' } }, isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, 'female'); - - expect(mockItemData).toEqual({ id: 1, person: { bio: { gender: 'female' } }, isActive: true }); - }); - - it('should apply the value to the bio property (second last) when field has a dot notation (complex object) value provided is an object and it that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - (mockColumn.internalColumnEditor as ColumnEditor).complexObjectPath = 'person.bio'; - mockColumn.field = 'person.bio.gender'; - mockItemData = { id: 1, person: { bio: { gender: 'male' } }, isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, { gender: 'female' }); - - expect(mockItemData).toEqual({ id: 1, person: { bio: { gender: 'female' } }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (value.length < 10) { - return { valid: false, msg: 'Must be at least 10 chars long.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, 'female'); - - expect(mockItemData).toEqual({ id: 1, gender: '', isActive: true }); - }); - - it('should apply the value to the gender property as an array with multiple when the input value is a CSV string', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, 'male,other'); - - expect(mockItemData).toEqual({ id: 1, gender: ['male', 'other'], isActive: true }); - }); - - it('should parse the value as a float when field type is defined as float then apply the value', () => { - mockColumn = { id: 'age', field: 'age', type: FieldType.boolean, editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - mockItemData = { id: 1, gender: 'male', isActive: true, age: 26 }; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 20, label: '20' }, { value: 25, label: '25' }]; - - editorArguments.column = mockColumn; - editor = new SelectEditor(editorArguments, true); - editor.applyValue(mockItemData, 25); - - expect(mockItemData).toEqual({ id: 1, gender: 'male', isActive: true, age: 25 }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual(['male']); - }); - - it('should return serialized value as an empty array when item value is also an empty string', () => { - mockItemData = { id: 1, gender: '', isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual([]); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, gender: null, isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - const currentValue = editor.currentValue; - - expect(output).toEqual([]); - expect(currentValue).toEqual(''); - }); - - it('should return value as a string when using a dot (.) notation for complex object with a collection of string values', () => { - mockColumn.field = 'employee.gender'; - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 1, employee: { id: 24, gender: 'male' }, isActive: true }; - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual(['male']); - }); - - it('should return object value when using a dot (.) notation for complex object with a collection of option/label pair', () => { - mockColumn.field = 'employee.gender'; - mockItemData = { id: 1, employee: { id: 24, gender: ['male', 'other'] }, isActive: true }; - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - const currentValue = editor.currentValue; - - expect(output).toEqual([{ label: 'male', value: 'male' }, { label: 'other', value: 'other' }]); - expect(currentValue).toEqual({}); - }); - - it('should return flat value when using a dot (.) notation for complex object with a collection of option/label pair and using "serializeComplexValueFormat" as "flat"', () => { - mockColumn.field = 'employee.gender'; - mockItemData = { id: 1, employee: { id: 24, gender: ['male', 'other'] }, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).serializeComplexValueFormat = 'flat'; - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - const currentValue = editor.currentValue; - - expect(output).toEqual(['male', 'other']); - expect(currentValue).toEqual(''); - }); - - it('should return object value when using a dot (.) notation and we override the object path using "complexObjectPath" to find correct values', () => { - mockColumn.field = 'employee.bio'; - mockItemData = { id: 1, employee: { id: 24, bio: { gender: ['male', 'other'] } }, isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).complexObjectPath = 'employee.bio.gender'; - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual([{ label: 'male', value: 'male' }, { label: 'other', value: 'other' }]); - }); - }); - - describe('save method', () => { - afterEach(() => { - editor.destroy(); - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "save" and "getEditorLock" method when "hasAutoCommitEdit" is enabled and we are destroying the editor without it being saved yet', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - gridOptionMock.autoCommitEdit = true; - const lockSpy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SelectEditor(editorArguments, true); - const saveSpy = jest.spyOn(editor, 'save'); - - editor.loadValue(mockItemData); - editor.destroy(); - - expect(saveSpy).toHaveBeenCalledWith(true); - expect(lockSpy).toHaveBeenCalled(); - }); - - it('should not call "commitCurrentEdit" when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - - it('should not call anything when the input value is empty but is required', () => { - mockItemData = { id: 1, gender: '', isActive: true }; - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - editor.save(); - - expect(spy).not.toHaveBeenCalled(); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new SelectEditor(editorArguments, true); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return True when field is required and input is a valid input value', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new SelectEditor(editorArguments, true); - const validation = editor.validate('text'); - - expect(validation).toEqual({ valid: true, msg: null }); - }); - }); - - describe('initialize with collection', () => { - it('should create the multi-select editor with a default search term when passed as a filter argument even with collection an array of strings', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - const editorOkElm = divContainer.querySelector(`[name=editor-gender].ms-drop .ms-ok-button`) as HTMLButtonElement; - editorBtnElm.click(); - editorOkElm.click(); - - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].value).toBe('male'); - expect(editorListElm[1].value).toBe('female'); - }); - }); - - describe('collectionSortBy setting', () => { - it('should create the multi-select editor and sort the string collection when "collectionSortBy" is set', () => { - mockColumn.internalColumnEditor = { - collection: ['other', 'male', 'female'], - collectionSortBy: { - sortDesc: true, - fieldType: FieldType.string - } - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[0].value).toBe('other'); - expect(editorListElm[1].value).toBe('male'); - expect(editorListElm[2].value).toBe('female'); - }); - - it('should create the multi-select editor and sort the value/label pair collection when "collectionSortBy" is set', () => { - mockColumn.internalColumnEditor = { - collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }], - collectionSortBy: { - property: 'value', - sortDesc: false, - fieldType: FieldType.string - }, - customStructure: { - value: 'value', - label: 'description', - }, - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[0].value).toBe('female'); - expect(editorListElm[1].value).toBe('male'); - expect(editorListElm[2].value).toBe('other'); - }); - }); - - describe('collectionFilterBy setting', () => { - it('should create the multi-select editor and filter the string collection when "collectionFilterBy" is set', () => { - mockColumn.internalColumnEditor = { - collection: ['other', 'male', 'female'], - collectionFilterBy: { - operator: OperatorType.equal, - value: 'other' - } - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(1); - expect(editorListElm[0].value).toBe('other'); - }); - - it('should create the multi-select editor and filter the value/label pair collection when "collectionFilterBy" is set', () => { - mockColumn.internalColumnEditor = { - collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }], - collectionFilterBy: [ - { property: 'value', operator: OperatorType.notEqual, value: 'other' }, - { property: 'value', operator: OperatorType.notEqual, value: 'male' } - ], - customStructure: { - value: 'value', - label: 'description', - }, - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(1); - expect(editorListElm[0].value).toBe('female'); - }); - - it('should create the multi-select editor and filter the value/label pair collection when "collectionFilterBy" is set and "filterResultAfterEachPass" is set to "merge"', () => { - mockColumn.internalColumnEditor = { - collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }], - collectionFilterBy: [ - { property: 'value', operator: OperatorType.equal, value: 'other' }, - { property: 'value', operator: OperatorType.equal, value: 'male' } - ], - collectionOptions: { - filterResultAfterEachPass: 'merge' - }, - customStructure: { - value: 'value', - label: 'description', - }, - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].value).toBe('other'); - expect(editorListElm[1].value).toBe('male'); - }); - }); - - describe('collectionOverride callback option', () => { - it('should create the multi-select editor and expect a different collection outputed when using the override', () => { - mockColumn.internalColumnEditor = { - collection: ['other', 'male', 'female'], - collectionOverride: (inputCollection) => inputCollection.filter(item => item !== 'other') - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].value).toBe('male'); - expect(editorListElm[1].value).toBe('female'); - }); - }); - - describe('collectionInsideObjectProperty setting', () => { - it('should create the multi-select editor with a value/label pair collection that is inside an object when "collectionInsideObjectProperty" is defined with a dot notation', () => { - mockColumn.internalColumnEditor = { - // @ts-ignore - collection: { deep: { myCollection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }] } }, - collectionOptions: { - collectionInsideObjectProperty: 'deep.myCollection' - }, - customStructure: { - value: 'value', - label: 'description', - }, - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=checkbox]`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(3); - expect(editorListElm[0].value).toBe('other'); - expect(editorListElm[1].value).toBe('male'); - expect(editorListElm[2].value).toBe('female'); - }); - }); - - describe('enableRenderHtml property', () => { - it('should create the multi-select editor with a default search term and have the HTML rendered when "enableRenderHtml" is set', () => { - mockColumn.internalColumnEditor = { - enableRenderHtml: true, - collection: [{ value: true, label: 'True', labelPrefix: ` ` }, { value: false, label: 'False' }], - customStructure: { - value: 'isEffort', - label: 'label', - labelPrefix: 'labelPrefix', - }, - }; - - editor = new SelectEditor(editorArguments, true); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li span`); - editorBtnElm.click(); - - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].innerHTML).toBe(' True'); - }); - - it('should create the multi-select editor with a default search term and have the HTML rendered and sanitized when "enableRenderHtml" is set and has ` }, { isEffort: false, label: 'False' }], - collectionOptions: { - separatorBetweenTextLabels: ': ', - includePrefixSuffixToSelectedValues: true, - }, - customStructure: { - value: 'isEffort', - label: 'label', - labelPrefix: 'labelPrefix', - }, - }; - mockItemData = { id: 1, gender: 'male', isEffort: false }; - - editor = new SelectEditor(editorArguments, true); - editor.loadValue(mockItemData); - editor.setValue([false]); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li span`); - editorBtnElm.click(); - - expect(editor.getValue()).toEqual(['']); - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].innerHTML).toBe(' : True'); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/singleSelectEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/singleSelectEditor.spec.ts deleted file mode 100644 index f41b4f08a..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/singleSelectEditor.spec.ts +++ /dev/null @@ -1,291 +0,0 @@ -// import 3rd party lib multiple-select for the tests -import '../../../../../assets/lib/multiple-select/multiple-select'; - -import { TestBed } from '@angular/core/testing'; -import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { Editors } from '../index'; -import { SingleSelectEditor } from '../singleSelectEditor'; -import { Column, EditorArguments, GridOption } from '../../models'; -import { ColumnEditor } from 'dist/public_api'; - -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, - i18n: null, -} as unknown as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - navigateNext: jest.fn(), - navigatePrev: jest.fn(), - render: jest.fn(), -}; - -describe('SelectEditor', () => { - let divContainer: HTMLDivElement; - let editor: SingleSelectEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - let translate: TranslateService; - - beforeEach(async () => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - await TestBed.configureTestingModule({ - providers: [], - imports: [TranslateModule.forRoot()] - }); - translate = TestBed.inject(TranslateService); - - translate.setTranslation('en', { - CANCEL: 'Cancel', - SAVE: 'Save', - }); - translate.setTranslation('fr', { - CANCEL: 'Annuler', - SAVE: 'Sauvegarder', - }); - translate.setDefaultLang('fr'); - translate.use('fr'); - - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - mockColumn = { id: 'gender', field: 'gender', editable: true, editor: { model: Editors.multipleSelect }, internalColumnEditor: {} } as Column; - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }]; - gridOptionMock.i18n = translate; - editor = new SingleSelectEditor(editorArguments); - const editorCount = document.body.querySelectorAll('select.ms-filter.editor-gender').length; - - expect(editorCount).toBe(1); - }); - - it('should hide the DOM element div wrapper when the "hide" method is called', () => { - editor = new SingleSelectEditor(editorArguments); - const editorElm = document.body.querySelector('[name=editor-gender].ms-drop') as HTMLDivElement; - - editor.show(); - expect(editorElm.style.display).toBe(''); - - editor.hide(); - expect(editorElm.style.display).toBe('none'); - }); - - it('should show the DOM element div wrapper when the "show" method is called', () => { - editor = new SingleSelectEditor(editorArguments); - const editorElm = document.body.querySelector('[name=editor-gender].ms-drop') as HTMLDivElement; - - editor.hide(); - expect(editorElm.style.display).toBe('none'); - - editor.show(); - expect(editorElm.style.display).toBe(''); - }); - - it('should call "setValue" with a single string and expect the string to be returned as an single string when calling "getValue"', () => { - editor = new SingleSelectEditor(editorArguments); - editor.setValue('male'); - - expect(editor.getValue()).toEqual('male'); - }); - - describe('isValueChanged method', () => { - it('should return False if the value is undefined', () => { - editor = new SingleSelectEditor(editorArguments); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=radio]`); - editorBtnElm.click(); - - // we can use property "checked" or dispatch an event - editorListElm[0].checked = false; - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return True after doing a check of an option', () => { - editor = new SingleSelectEditor(editorArguments); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=radio]`); - editorBtnElm.click(); - - // we can use property "checked" or dispatch an event - editorListElm[0].dispatchEvent(new CustomEvent('click')); - - expect(editorListElm.length).toBe(3); - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False after re-selecting the same option as the one loaded', () => { - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li input[type=radio]`); - editorBtnElm.click(); - - // we can use property "checked" or dispatch an event - editorListElm[0].dispatchEvent(new CustomEvent('click')); - - expect(editorListElm.length).toBe(2); - expect(editor.isValueChanged()).toBe(false); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe('male'); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a string', () => { - mockItemData = { id: 1, gender: 'male', isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual('male'); - }); - - it('should return serialized value as an empty array when item value is also an empty string', () => { - mockItemData = { id: 1, gender: '', isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual(''); - }); - - it('should return serialized value as an empty string when item value is null', () => { - mockItemData = { id: 1, gender: null, isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual(''); - }); - - it('should return value as a string when using a dot (.) notation for complex object', () => { - mockColumn.field = 'employee.gender'; - (mockColumn.internalColumnEditor as ColumnEditor).collection = ['male', 'female']; - mockItemData = { id: 1, employee: { gender: 'male' }, isActive: true }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toEqual('male'); - }); - }); - - describe('enableRenderHtml property', () => { - it('should create the multi-select filter with a default search term and have the HTML rendered when "enableRenderHtml" is set', () => { - mockColumn.internalColumnEditor = { - enableRenderHtml: true, - collection: [{ value: true, label: 'True', labelPrefix: ` ` }, { value: false, label: 'False' }], - customStructure: { - value: 'isEffort', - label: 'label', - labelPrefix: 'labelPrefix', - }, - }; - - editor = new SingleSelectEditor(editorArguments); - editor.setValue(false); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li span`); - editorBtnElm.click(); - - expect(editor.getValue()).toEqual(''); - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].innerHTML).toBe(' True'); - }); - - it('should create the multi-select filter with a default search term and have the HTML rendered and sanitized when "enableRenderHtml" is set and has ` }, { isEffort: false, label: 'False' }], - collectionOptions: { - separatorBetweenTextLabels: ': ', - includePrefixSuffixToSelectedValues: true, - }, - customStructure: { - value: 'isEffort', - label: 'label', - labelPrefix: 'labelPrefix', - }, - }; - mockItemData = { id: 1, gender: 'male', isEffort: false }; - - editor = new SingleSelectEditor(editorArguments); - editor.loadValue(mockItemData); - const editorBtnElm = divContainer.querySelector('.ms-parent.ms-filter.editor-gender button.ms-choice') as HTMLButtonElement; - const editorListElm = divContainer.querySelectorAll(`[name=editor-gender].ms-drop ul>li span`); - editorBtnElm.click(); - - expect(editor.getValue()).toEqual(` : true`); - expect(editorListElm.length).toBe(2); - expect(editorListElm[0].innerHTML).toBe(' : True'); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/__tests__/sliderEditor.spec.ts b/src/app/modules/angular-slickgrid/editors/__tests__/sliderEditor.spec.ts deleted file mode 100644 index b2ab6a3f4..000000000 --- a/src/app/modules/angular-slickgrid/editors/__tests__/sliderEditor.spec.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { Editors } from '../index'; -import { SliderEditor } from '../sliderEditor'; -import { AutocompleteOption, Column, ColumnEditor, EditorArgs, EditorArguments, GridOption, KeyCode } from '../../models'; - -const KEY_CHAR_0 = 48; -const containerId = 'demo-container'; - -// define a
    container to simulate the grid container -const template = `
    `; - -const dataViewStub = { - refresh: jest.fn(), -}; - -const gridOptionMock = { - autoCommitEdit: false, - editable: true, -} as GridOption; - -const getEditorLockMock = { - commitCurrentEdit: jest.fn(), -}; - -const gridStub = { - getOptions: () => gridOptionMock, - getColumns: jest.fn(), - getEditorLock: () => getEditorLockMock, - getHeaderRowColumn: jest.fn(), - render: jest.fn(), -}; - -describe('SliderEditor', () => { - let divContainer: HTMLDivElement; - let editor: SliderEditor; - let editorArguments: EditorArguments; - let mockColumn: Column; - let mockItemData: any; - - beforeEach(() => { - divContainer = document.createElement('div'); - divContainer.innerHTML = template; - document.body.appendChild(divContainer); - - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.float }, internalColumnEditor: {} } as Column; - - editorArguments = { - grid: gridStub, - column: mockColumn, - item: mockItemData, - event: null as any, - cancelChanges: jest.fn(), - commitChanges: jest.fn(), - container: divContainer, - columnMetaData: null, - dataView: dataViewStub, - gridPosition: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - position: { top: 0, left: 0, bottom: 10, right: 10, height: 100, width: 100, visible: true }, - }; - }); - - describe('with invalid Editor instance', () => { - it('should throw an error when trying to call init without any arguments', (done) => { - try { - editor = new SliderEditor(null as any); - } catch (e) { - expect(e.toString()).toContain(`[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.`); - done(); - } - }); - }); - - describe('with valid Editor instance', () => { - beforeEach(() => { - mockItemData = { id: 1, price: 213, isActive: true }; - mockColumn = { id: 'price', field: 'price', editable: true, editor: { model: Editors.float }, internalColumnEditor: {} } as Column; - - editorArguments.column = mockColumn; - editorArguments.item = mockItemData; - }); - - afterEach(() => { - editor.destroy(); - }); - - it('should initialize the editor', () => { - editor = new SliderEditor(editorArguments); - const editorCount = divContainer.querySelectorAll('.slider-editor input.editor-price').length; - expect(editorCount).toBe(1); - }); - - it('should have a title (tooltip) when defined in its column definition', () => { - const testValue = 'test title'; - (mockColumn.internalColumnEditor as ColumnEditor).title = testValue; - - editor = new SliderEditor(editorArguments); - const editorElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - - expect(editorElm.title).toBe(testValue); - }); - - it('should call "columnEditor" GETTER and expect to equal the editor settings we provided', () => { - mockColumn.internalColumnEditor = { - title: 'test title', - }; - - editor = new SliderEditor(editorArguments); - - expect(editor.columnEditor).toEqual(mockColumn.internalColumnEditor); - }); - - it('should create the input editor with defined value and a different step size when "valueStep" is provided', () => { - (mockColumn.internalColumnEditor as ColumnEditor).valueStep = 5; - mockItemData = { id: 1, price: 15, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const editorNumberElm = divContainer.querySelector('.input-group-text') as HTMLInputElement; - const editorInputElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - - expect(editorInputElm.step).toBe('5'); - expect(editorNumberElm.textContent).toBe('15'); - expect(editor.getValue()).toEqual('15'); - }); - - it('should create the input editor with min slider values being set by editor "minValue"', () => { - mockColumn.internalColumnEditor = { - minValue: 4, - maxValue: 69, - }; - - editor = new SliderEditor(editorArguments); - - const editorInputElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - const editorNumberElm = divContainer.querySelector('.input-group-text') as HTMLInputElement; - - expect(editorInputElm.min).toBe('4'); - expect(editorInputElm.max).toBe('69'); - expect(editorNumberElm.textContent).toBe('4'); - }); - - it('should create the input editor with min/max slider values being set by editor "sliderStartValue" through the editor params', () => { - mockColumn.internalColumnEditor = { params: { sliderStartValue: 4 } }; - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - - const editorInputElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - const editorNumberElm = divContainer.querySelector('.input-group-text') as HTMLInputElement; - - expect(editor.getValue()).toEqual('4'); - expect(editorInputElm.min).toBe('0'); - expect(editorInputElm.defaultValue).toBe('4'); - expect(editorNumberElm.textContent).toBe('4'); - }); - - it('should create the input editor with default search terms range but without showing side numbers when "hideSliderNumber" is set in params', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { hideSliderNumber: true }; - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new SliderEditor(editorArguments); - - const editorNumberElms = divContainer.querySelectorAll('.input-group-text'); - - expect(editorNumberElms.length).toBe(0); - expect(editor.getValue()).toEqual('0'); - }); - - it('should call "setValue" and expect the DOM element value to be the same but as a string when calling "getValue"', () => { - editor = new SliderEditor(editorArguments); - editor.setValue(85); - - expect(editor.getValue()).toBe('85'); - }); - - it('should call "cancel" and expect "cancelChanges" to be called in the Slickgrid editor object', () => { - const spy = jest.spyOn(editorArguments, 'cancelChanges'); - editor = new SliderEditor(editorArguments); - editor.cancel(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should define an item datacontext containing a string as cell value and expect this value to be loaded in the editor when calling "loadValue"', () => { - mockColumn.internalColumnEditor = { maxValue: 500 }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const editorInputElm = editor.editorInputDomElement; - const editorElm = editor.editorDomElement; - - expect(editor.getValue()).toBe('213'); - expect(editorElm).toBeTruthy(); - expect(editorInputElm.defaultValue).toBe('0'); - }); - - it('should update slider number every time a change event happens on the input slider', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { hideSliderNumber: false }; - mockItemData = { id: 1, price: 32, isActive: true }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(17); - - const editorElm = divContainer.querySelector('.slider-container.slider-editor') as HTMLDivElement; - const editorNumberElm = divContainer.querySelector('.input-group-text') as HTMLInputElement; - const mockEvent = new Event('change'); - Object.defineProperty(mockEvent, 'target', { writable: true, configurable: true, value: { value: '13' } }); - editorElm.dispatchEvent(mockEvent); - - expect(editor.isValueChanged()).toBe(true); - expect(editorNumberElm.textContent).toBe('13'); - }); - - describe('isValueChanged method', () => { - it('should return True when previously dispatched change event is a different slider input number', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { sliderStartValue: 5 }; - mockItemData = { id: 1, price: 32, isActive: true }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(45); - - const editorElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - editorElm.dispatchEvent(new Event('change')); - - expect(editor.isValueChanged()).toBe(true); - }); - - it('should return False when previously dispatched change event is the same as default (0) slider input number', () => { - mockItemData = { id: 1, price: 0, isActive: true }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - - const editorElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - editorElm.dispatchEvent(new Event('change')); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when previously dispatched change event is the same as default (0) slider input number but provided as a string', () => { - mockItemData = { id: 1, price: '0', isActive: true }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - - const editorElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - editorElm.dispatchEvent(new Event('change')); - - expect(editor.isValueChanged()).toBe(false); - }); - - it('should return False when previously dispatched change event is the same input number as "sliderStartValue" provided by the user', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { sliderStartValue: 5 }; - mockItemData = { id: 1, price: 5, isActive: true }; - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - - const editorElm = divContainer.querySelector('.slider-editor input.editor-price') as HTMLInputElement; - editorElm.dispatchEvent(new Event('change')); - - expect(editor.isValueChanged()).toBe(false); - }); - }); - - describe('applyValue method', () => { - it('should apply the value to the price property when it passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockItemData = { id: 1, price: 456, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, price: 78, isActive: true }); - }); - - it('should apply the value to the price property with a field having dot notation (complex object) that passes validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = null as any; - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 456 }, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.applyValue(mockItemData, 78); - - expect(mockItemData).toEqual({ id: 1, part: { price: 78 }, isActive: true }); - }); - - it('should return item data with an empty string in its value when it fails the custom validation', () => { - (mockColumn.internalColumnEditor as ColumnEditor).validator = (value: any) => { - if (+value < 10) { - return { valid: false, msg: 'Value must be over 10.' }; - } - return { valid: true, msg: '' }; - }; - mockItemData = { id: 1, price: 32, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.applyValue(mockItemData, 4); - - expect(mockItemData).toEqual({ id: 1, price: '', isActive: true }); - }); - }); - - describe('serializeValue method', () => { - it('should return serialized value as a number', () => { - mockItemData = { id: 1, price: 33, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(33); - }); - - it('should return serialized value as a number even when the item property value is a number in a string', () => { - mockItemData = { id: 1, price: '33', isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(33); - }); - - it('should return serialized value as the default minimum number (0) when item value provided is an empty string', () => { - mockItemData = { id: 1, price: '', isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(0); - }); - - it('should return serialized value as the default minimum number (0) when item value is null', () => { - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(0); - }); - - it('should return serialized value as the custom "sliderStartValue" number when item value is null', () => { - (mockColumn.internalColumnEditor as ColumnEditor).params = { sliderStartValue: 5 }; - mockItemData = { id: 1, price: null, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(5); - }); - - it('should return value as a number when using a dot (.) notation for complex object', () => { - mockColumn.field = 'part.price'; - mockItemData = { id: 1, part: { price: 5 }, isActive: true }; - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - const output = editor.serializeValue(); - - expect(output).toBe(5); - }); - }); - - describe('save method', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should call "getEditorLock" method when "hasAutoCommitEdit" is enabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitChanges" method when "hasAutoCommitEdit" is disabled', () => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = false; - const spy = jest.spyOn(editorArguments, 'commitChanges'); - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "commitCurrentEdit" even when the input value is the same as the default value', () => { - mockItemData = { id: 1, price: 0, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spy = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.save(); - - expect(spy).toHaveBeenCalled(); - }); - - it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "focusout" is triggered', (done) => { - mockItemData = { id: 1, price: 32, isActive: true }; - gridOptionMock.autoCommitEdit = true; - const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit'); - - editor = new SliderEditor(editorArguments); - editor.loadValue(mockItemData); - editor.setValue(35); - const spySave = jest.spyOn(editor, 'save'); - const editorElm = editor.editorDomElement; - - editorElm.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true })); - - setTimeout(() => { - expect(spyCommit).toHaveBeenCalled(); - expect(spySave).toHaveBeenCalled(); - done(); - }); - }); - }); - - describe('validate method', () => { - it('should return False when field is required and field is empty', () => { - (mockColumn.internalColumnEditor as ColumnEditor).required = true; - editor = new SliderEditor(editorArguments); - const validation = editor.validate(''); - - expect(validation).toEqual({ valid: false, msg: 'Field is required' }); - }); - - it('should return False when field is not between minValue & maxValue defined', () => { - (mockColumn.internalColumnEditor as ColumnEditor).minValue = 10; - (mockColumn.internalColumnEditor as ColumnEditor).maxValue = 99; - editor = new SliderEditor(editorArguments); - const validation = editor.validate(100); - - expect(validation).toEqual({ valid: false, msg: 'Please enter a valid number between 10 and 99' }); - }); - }); - }); -}); diff --git a/src/app/modules/angular-slickgrid/editors/autoCompleteEditor.ts b/src/app/modules/angular-slickgrid/editors/autoCompleteEditor.ts deleted file mode 100644 index f065fa489..000000000 --- a/src/app/modules/angular-slickgrid/editors/autoCompleteEditor.ts +++ /dev/null @@ -1,438 +0,0 @@ -import * as DOMPurify_ from 'dompurify'; -const DOMPurify = DOMPurify_; // patch to fix rollup to work - -import { - AutocompleteOption, - CollectionCustomStructure, - Column, - ColumnEditor, - Editor, - EditorArguments, - EditorValidator, - EditorValidatorOutput, - FieldType, - GridOption, - KeyCode, -} from './../models/index'; -import { findOrDefault, getDescendantProperty, setDeepValue, toKebabCase } from '../services/utilities'; -import { textValidator } from '../editorValidators/textValidator'; - -// using external non-typed js libraries -declare const $: any; - -// minimum length of chars to type before starting to start querying -const MIN_LENGTH = 3; - -/* - * An example of a 'detached' editor. - * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. - */ -export class AutoCompleteEditor implements Editor { - protected _autoCompleteOptions!: AutocompleteOption; - protected _currentValue: any; - protected _defaultTextValue = ''; - protected _elementCollection: any[] | null = null; - protected _lastInputEvent!: JQuery.Event; - - /** The JQuery DOM element */ - protected _$editorElm: any; - - /** SlickGrid Grid object */ - grid: any; - - /** The property name for labels in the collection */ - labelName!: string; - - /** The property name for a prefix that can be added to the labels in the collection */ - labelPrefixName!: string; - - /** The property name for a suffix that can be added to the labels in the collection */ - labelSuffixName!: string; - - /** The property name for values in the collection */ - valueName!: string; - - forceUserInput!: boolean; - - constructor(protected args: EditorArguments) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.init(); - } - - /** Getter for the Autocomplete Option */ - get autoCompleteOptions(): Partial { - return this._autoCompleteOptions || {}; - } - - /** Get the Collection */ - get editorCollection(): any[] { - return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collection || []; - } - - /** Get the Final Collection used in the AutoCompleted Source (this may vary from the "collection" especially when providing a customStructure) */ - get elementCollection(): any[] | null { - return this._elementCollection; - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Getter for the Custom Structure if exist */ - get customStructure(): CollectionCustomStructure { - let customStructure = this.columnEditor && this.columnEditor.customStructure; - const columnType = this.columnEditor && this.columnEditor.type || this.columnDef && this.columnDef.type; - if (!customStructure && (columnType === FieldType.object && this.columnDef && this.columnDef.dataKey && this.columnDef.labelKey)) { - customStructure = { - label: this.columnDef.labelKey, - value: this.columnDef.dataKey, - }; - } - return customStructure as CollectionCustomStructure; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor.validator || this.columnDef.validator; - } - - get editorOptions() { - return this.columnEditor && this.columnEditor.editorOptions || {}; - } - - /** jQuery UI AutoComplete instance */ - get instance(): any { - return this._$editorElm.autocomplete('instance'); - } - - /** Getter for the Grid Options pulled through the Grid Object */ - get gridOptions(): GridOption { - return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; - } - - /** Get the Editor DOM Element */ - get editorDomElement(): any { - return this._$editorElm; - } - - init() { - this.labelName = this.customStructure && this.customStructure.label || 'label'; - this.valueName = this.customStructure && this.customStructure.value || 'value'; - this.labelPrefixName = this.customStructure && this.customStructure.labelPrefix || 'labelPrefix'; - this.labelSuffixName = this.customStructure && this.customStructure.labelSuffix || 'labelSuffix'; - - // always render the DOM element, even if user passed a "collectionAsync", - const newCollection = this.columnEditor.collection || []; - this.renderDomElement(newCollection); - } - - destroy() { - if (this._$editorElm) { - this._$editorElm.autocomplete('destroy'); - this._$editorElm.off('keydown.nav').remove(); - } - this._$editorElm = null; - this._elementCollection = null; - } - - focus() { - if (this._$editorElm) { - this._$editorElm.focus().select(); - } - } - - getValue() { - return this._$editorElm.val(); - } - - setValue(value: string) { - this._$editorElm.val(value); - } - - applyValue(item: any, state: any) { - let newValue = state; - const fieldName = this.columnDef && this.columnDef.field; - - // if we have a collection defined, we will try to find the string within the collection and return it - if (Array.isArray(this.editorCollection) && this.editorCollection.length > 0) { - newValue = findOrDefault(this.editorCollection, (collectionItem: any) => { - if (collectionItem && typeof state === 'object' && collectionItem.hasOwnProperty(this.labelName)) { - return (collectionItem.hasOwnProperty(this.labelName) && collectionItem[this.labelName].toString()) === (state.hasOwnProperty(this.labelName) && state[this.labelName].toString()); - } else if (collectionItem && typeof state === 'string' && collectionItem.hasOwnProperty(this.labelName)) { - return (collectionItem.hasOwnProperty(this.labelName) && collectionItem[this.labelName].toString()) === state; - } - return collectionItem && collectionItem.toString() === state; - }); - } - - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(newValue); - newValue = (validation && validation.valid) ? newValue : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - setDeepValue(item, fieldName, newValue); - } else { - item[fieldName] = newValue; - } - } - - isValueChanged(): boolean { - const elmValue = this._$editorElm.val(); - const lastKeyEvent = this._lastInputEvent && this._lastInputEvent.keyCode; - if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastKeyEvent === KeyCode.ENTER) { - return true; - } - return (!(elmValue === '' && (this._defaultTextValue === null || this._defaultTextValue === undefined))) && (elmValue !== this._defaultTextValue); - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (item && fieldName !== undefined) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - const data = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - - this._currentValue = data; - this._defaultTextValue = typeof data === 'string' ? data : (data && data.hasOwnProperty(this.labelName) && data[this.labelName] || ''); - this._$editorElm.val(this._defaultTextValue); - this._$editorElm.select(); - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue(): any { - // if you want to add the autocomplete functionality but want the user to be able to input a new option - if (this.editorOptions.forceUserInput) { - const minLength = this.editorOptions && this.editorOptions.hasOwnProperty('minLength') ? this.editorOptions.minLength : MIN_LENGTH; - this._currentValue = this._$editorElm.val().length > minLength ? this._$editorElm.val() : this._currentValue; - } - // if user provided a custom structure, we will serialize the value returned from the object with custom structure - if (this.customStructure && this._currentValue && this._currentValue.hasOwnProperty(this.valueName) && (this.columnDef && this.columnDef.type !== FieldType.object && this.columnEditor && this.columnEditor.type !== FieldType.object)) { - return this._currentValue[this.valueName]; - } else if (this._currentValue && this._currentValue.value !== undefined) { - // when object has a "value" property and its column is set as an Object type, we'll return an object with optional custom structure - if (this.columnDef && this.columnDef.type === FieldType.object || ((this.columnEditor && this.columnEditor.type) === FieldType.object)) { - return { - [this.labelName]: this._currentValue.label, - [this.valueName]: this._currentValue.value - }; - } - return this._currentValue.value; - } - // if it falls here it might be that the user provided its own custom item with something else than the regular label/value pair - // at this point it's only available when user provide a custom template for the autocomplete renderItem callback - return this._currentValue; - } - - validate(inputValue?: any): EditorValidatorOutput { - const val = (inputValue !== undefined) ? inputValue : this._$editorElm && this._$editorElm.val && this._$editorElm.val(); - return textValidator(val, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - minLength: this.columnEditor.minLength, - maxLength: this.columnEditor.maxLength, - operatorConditionalType: this.columnEditor.operatorConditionalType, - required: this.columnEditor.required, - validator: this.validator, - }); - } - - // - // protected functions - // ------------------ - - // this function should be protected but for unit tests purposes we'll make it public until a better solution is found - // a better solution would be to get the autocomplete DOM element to work with selection but I couldn't find how to do that in Jest - handleSelect(event: Event, ui: { item: any; }) { - if (ui && ui.item) { - const item = ui && ui.item; - this._currentValue = item; - // when the user defines a "renderItem" (or "_renderItem") template, then we assume the user defines his own custom structure of label/value pair - // otherwise we know that jQueryUI always require a label/value pair, we can pull them directly - const hasCustomRenderItemCallback = this.columnEditor && this.columnEditor.callbacks && this.columnEditor.callbacks.hasOwnProperty('_renderItem') || (this.columnEditor && this.columnEditor.editorOptions && this.columnEditor.editorOptions.renderItem) || false; - - const itemLabel = typeof item === 'string' ? item : (hasCustomRenderItemCallback ? item[this.labelName] : item.label); - this.setValue(itemLabel); - this.save(); - - // if user wants to hook to the "select", he can do via this "onSelect" - // it purposely has a similar signature as the "select" callback + some extra arguments (row, cell, column, dataContext) - if (this.editorOptions.onSelect) { - const activeCell = this.grid && this.grid.getActiveCell() || {}; - this.editorOptions.onSelect(event, ui, activeCell.row, activeCell.cell, this.args.column, this.args.item); - } - } - return false; - } - - protected renderCustomItem(ul: HTMLElement, item: any) { - const templateString = this._autoCompleteOptions && this._autoCompleteOptions.renderItem && this._autoCompleteOptions.renderItem.templateCallback(item) || ''; - - // sanitize any unauthorized html tags like script and others - // for the remaining allowed tags we'll permit all attributes - const sanitizedTemplateText = (DOMPurify.sanitize(templateString, {}) || '').toString(); - - return $('
  • ') - .data('item.autocomplete', item) - .append(sanitizedTemplateText) - .appendTo(ul); - } - - protected renderCollectionItem(ul: HTMLElement, item: any) { - const isRenderHtmlEnabled = this.columnEditor && this.columnEditor.enableRenderHtml || false; - const prefixText = item.labelPrefix || ''; - const labelText = item.label || ''; - const suffixText = item.labelSuffix || ''; - const finalText = prefixText + labelText + suffixText; - - // sanitize any unauthorized html tags like script and others - // for the remaining allowed tags we'll permit all attributes - const sanitizedText = (DOMPurify.sanitize(finalText, {}) || '').toString(); - - const $liDiv = $('
    ')[isRenderHtmlEnabled ? 'html' : 'text'](sanitizedText); - return $('
  • ') - .data('item.autocomplete', item) - .append($liDiv) - .appendTo(ul); - } - - protected renderDomElement(collection: any[]) { - if (!Array.isArray(collection)) { - throw new Error('The "collection" passed to the Autocomplete Editor is not a valid array.'); - } - const columnId = this.columnDef && this.columnDef.id || ''; - const placeholder = this.columnEditor && this.columnEditor.placeholder || ''; - const title = this.columnEditor && this.columnEditor.title || ''; - - this._$editorElm = $(``) - .appendTo(this.args.container) - .on('keydown.nav', (event: JQuery.Event) => { - this._lastInputEvent = event; - if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { - event.stopImmediatePropagation(); - } - }); - - // add a in order to add spinner styling - $(``).appendTo(this.args.container); - - // user might pass his own autocomplete options - const autoCompleteOptions: AutocompleteOption = this.columnEditor.editorOptions; - - // assign the collection to a temp variable before filtering/sorting the collection - let finalCollection = collection; - - // user could also override the collection - if (this.columnEditor && this.columnEditor.collectionOverride) { - finalCollection = this.columnEditor.collectionOverride(finalCollection, { column: this.columnDef, dataContext: this.args.item, grid: this.grid }); - } - - // user might provide his own custom structure - // jQuery UI autocomplete requires a label/value pair, so we must remap them when user provide different ones - if (Array.isArray(finalCollection)) { - finalCollection = finalCollection.map((item) => { - return { label: item[this.labelName], value: item[this.valueName], labelPrefix: item[this.labelPrefixName] || '', labelSuffix: item[this.labelSuffixName] || '' }; - }); - } - - // keep the final source collection used in the AutoComplete as reference - this._elementCollection = finalCollection; - - // when user passes it's own autocomplete options - // we still need to provide our own "select" callback implementation - if (autoCompleteOptions && autoCompleteOptions.source) { - autoCompleteOptions.select = (event: Event, ui: { item: any; }) => this.handleSelect(event, ui); - this._autoCompleteOptions = { ...autoCompleteOptions }; - - // when "renderItem" is defined, we need to add our custom style CSS class - if (this._autoCompleteOptions.renderItem) { - this._autoCompleteOptions.classes = { - 'ui-autocomplete': `autocomplete-custom-${toKebabCase(this._autoCompleteOptions.renderItem.layout)}` - }; - } - // create the jQueryUI AutoComplete - this._$editorElm.autocomplete(this._autoCompleteOptions); - - // when "renderItem" is defined, we need to call the user's custom renderItem template callback - if (this._autoCompleteOptions.renderItem) { - this._$editorElm.autocomplete('instance')._renderItem = this.renderCustomItem.bind(this); - } - } else { - const definedOptions: AutocompleteOption = { - source: finalCollection, - minLength: 0, - select: (event: Event, ui: { item: any; }) => this.handleSelect(event, ui), - }; - this._autoCompleteOptions = { ...definedOptions, ...(this.columnEditor.editorOptions as AutocompleteOption) }; - this._$editorElm.autocomplete(this._autoCompleteOptions); - - // we'll use our own renderer so that it works with label prefix/suffix and also with html rendering when enabled - this._$editorElm.autocomplete('instance')._renderItem = this.renderCollectionItem.bind(this); - } - - // in case the user wants to save even an empty value, - // we need to subscribe to the onKeyDown event for that use case and clear the current value - if (this.columnEditor.alwaysSaveOnEnterKey) { - this._$editorElm.keydown((event: JQuery.Event) => { - if (event.keyCode === KeyCode.ENTER) { - this._currentValue = null; - } - }); - } - - // we could optionally trigger a search when clicking on the AutoComplete - if (this.editorOptions.openSearchListOnFocus) { - this._$editorElm.click(() => this._$editorElm.autocomplete('search', this._$editorElm.val())); - } - - // user might override any of the jQueryUI callback methods - if (this.columnEditor.callbacks) { - for (const callback of Object.keys(this.columnEditor.callbacks)) { - if (typeof this.columnEditor.callbacks[callback] === 'function') { - this.instance[callback] = this.columnEditor.callbacks[callback]; - } - } - } - - this._$editorElm.on('focus', () => { - this._$editorElm.select(); - - // we could optionally trigger a search to open the AutoComplete search list - if (this.editorOptions.openSearchListOnFocus) { - this._$editorElm.autocomplete('search', this._$editorElm.val()); - } - }); - - setTimeout(() => this.focus(), 50); - } -} diff --git a/src/app/modules/angular-slickgrid/editors/checkboxEditor.ts b/src/app/modules/angular-slickgrid/editors/checkboxEditor.ts deleted file mode 100644 index afee38cfa..000000000 --- a/src/app/modules/angular-slickgrid/editors/checkboxEditor.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Constants } from './../constants'; -import { Column, ColumnEditor, Editor, EditorArguments, EditorValidator, EditorValidatorOutput, GridOption, SlickGrid } from './../models/index'; -import { getDescendantProperty, setDeepValue } from '../services/utilities'; -import { BindingEventService } from '../services/bindingEvent.service'; - -/* - * An example of a 'detached' editor. - * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. - */ -export class CheckboxEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _checkboxContainerElm!: HTMLDivElement; - protected _input!: HTMLInputElement; - protected _originalValue?: boolean | string; - - /** SlickGrid Grid object */ - grid: SlickGrid; - - /** Grid options */ - gridOptions: GridOption; - - constructor(protected readonly args: EditorArguments) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = (this.grid.getOptions() || {}) as GridOption; - this._bindEventService = new BindingEventService(); - this.init(); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Getter for the Editor DOM Element */ - get editorDomElement(): any { - return this._input; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init(): void { - const columnId = this.columnDef?.id ?? ''; - const title = this.columnEditor?.title ?? ''; - - this._checkboxContainerElm = document.createElement('div'); - this._checkboxContainerElm.className = `checkbox-editor-container editor-${columnId}`; - - this._input = document.createElement('input'); - this._input.className = `editor-checkbox editor-${columnId}`; - this._input.title = title; - this._input.type = 'checkbox'; - this._input.value = 'true'; - - const cellContainer = this.args?.container; - if (cellContainer && typeof cellContainer.appendChild === 'function') { - cellContainer.appendChild(this._input); - } - - // make the checkbox editor act like a regular checkbox that commit the value on click - if (this.hasAutoCommitEdit) { - this._bindEventService.bind(this._input, 'click', () => this.save()); - } - - this.focus(); - } - - destroy() { - this._bindEventService.unbindAll(); - this._input?.remove?.(); - } - - focus(): void { - if (this._input) { - this._input.focus(); - } - } - - /** pre-click, when enabled, will simply toggle the checkbox without requiring to double-click */ - preClick() { - if (this._input) { - this._input.checked = !this._input.checked; - } - } - - getValue() { - return this._input?.checked ?? false; - } - - setValue(val: boolean | string) { - const isChecked = val ? true : false; - if (this._input) { - this._input.checked = isChecked; - } - } - - applyValue(item: any, state: any) { - const fieldName = this.columnDef && this.columnDef.field; - if (fieldName !== undefined) { - const isComplexObject = fieldName?.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(state); - const newValue = (validation && validation.valid) ? state : ''; - - // set the new value to the item datacontext - if (isComplexObject) {// when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor?.complexObjectPath ?? fieldName ?? ''; - setDeepValue(item, objectPath, newValue); - } else { - item[fieldName] = newValue; - } - } - } - - isValueChanged(): boolean { - return (this.serializeValue() !== this._originalValue); - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (item && fieldName !== undefined && this._input) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - - this._originalValue = value; - this._input.checked = !!this._originalValue; - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue(): boolean { - return this._input?.checked ?? false; - } - - validate(inputValue?: any): EditorValidatorOutput { - const isRequired = this.columnEditor.required; - const isChecked = (inputValue !== undefined) ? inputValue : this._input?.checked; - const errorMsg = this.columnEditor.errorMessage; - - if (this.validator) { - return this.validator(isChecked, this.args); - } - - // by default the editor is almost always valid (except when it's required but not provided) - if (isRequired && !isChecked) { - return { - valid: false, - msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD - }; - } - - return { - valid: true, - msg: null - }; - } -} diff --git a/src/app/modules/angular-slickgrid/editors/dateEditor.ts b/src/app/modules/angular-slickgrid/editors/dateEditor.ts deleted file mode 100644 index 3dd40ad28..000000000 --- a/src/app/modules/angular-slickgrid/editors/dateEditor.ts +++ /dev/null @@ -1,340 +0,0 @@ -import * as flatpickr_ from 'flatpickr'; -import * as moment_ from 'moment-mini'; -import { BaseOptions as FlatpickrBaseOptions } from 'flatpickr/dist/types/options'; -import { Instance as FlatpickrInstance, FlatpickrFn } from 'flatpickr/dist/types/instance'; -const flatpickr: FlatpickrFn = (flatpickr_ && flatpickr_['default'] || flatpickr_) as any; // patch for rollup -const moment = (moment_ as any)['default'] || moment_; // patch to fix rollup "moment has no default export" issue, document here https://github.com/rollup/rollup/issues/670 -import { TranslateService } from '@ngx-translate/core'; - -import { Constants } from './../constants'; -import { - Column, - ColumnEditor, - Editor, - EditorArguments, - EditorValidator, - EditorValidatorOutput, - FieldType, - FlatpickrOption, - GridOption, - SlickGrid, -} from './../models/index'; -import { - destroyObjectDomElementProps, - emptyElement, - getDescendantProperty, - mapFlatpickrDateFormatWithFieldType, - mapMomentDateFormatWithFieldType, - setDeepValue, -} from './../services/utilities'; -import { BindingEventService } from '../services/bindingEvent.service'; - -/* - * An example of a date picker editor using Flatpickr - * https://chmln.github.io/flatpickr - */ -export class DateEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _closeButtonElm!: HTMLButtonElement; - protected _editorInputGroupElm!: HTMLDivElement; - protected _inputElm!: HTMLInputElement; - protected _inputWithDataElm!: HTMLInputElement | null; - protected _isValueTouched = false; - protected _lastTriggeredByClearDate = false; - protected _originalDate?: string; - protected _pickerMergedOptions!: FlatpickrOption; - - flatInstance!: FlatpickrInstance; - defaultDate?: string; - - /** SlickGrid Grid object */ - grid: SlickGrid; - - /** Grid options */ - gridOptions: GridOption; - - /** The translate library */ - protected _translate?: TranslateService; - - constructor(protected readonly args: EditorArguments) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = (this.grid.getOptions() || {}) as GridOption; - const options = this.gridOptions || this.args.column.params || {}; - if (options?.i18n instanceof TranslateService) { - this._translate = options.i18n; - } - this._bindEventService = new BindingEventService(); - this.init(); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Getter for the Editor DOM Element */ - get editorDomElement(): HTMLInputElement { - return this._inputElm; - } - - /** Get Flatpickr options passed to the editor by the user */ - get editorOptions(): FlatpickrOption { - return this.columnEditor.editorOptions || {}; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - get pickerOptions(): FlatpickrOption { - return this._pickerMergedOptions; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init(): void { - if (this.args && this.columnDef) { - const columnId = this.columnDef?.id ?? ''; - const placeholder = this.columnEditor?.placeholder ?? ''; - const title = this.columnEditor && this.columnEditor.title || ''; - this.defaultDate = (this.args.item) ? this.args.item[this.columnDef.field] : null; - const inputFormat = mapFlatpickrDateFormatWithFieldType(this.columnEditor.type || this.columnDef.type || FieldType.dateUtc); - const outputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateUtc); - let currentLocale = this._translate && this._translate.currentLang || this.gridOptions.locale || 'en'; - if (currentLocale && currentLocale.length > 2) { - currentLocale = currentLocale.substring(0, 2); - } - - const pickerOptions: FlatpickrOption = { - defaultDate: this.defaultDate as string, - altInput: true, - altFormat: outputFormat, - dateFormat: inputFormat, - closeOnSelect: true, - wrap: true, - locale: currentLocale, - onChange: () => this.handleOnDateChange(), - errorHandler: (error: Error) => { - if (error.toString().includes('invalid locale')) { - console.warn(`[Angular-Slickgrid] Flatpickr missing locale imports (${currentLocale}), will revert to English as the default locale. - See Flatpickr Localization for more info, for example if we want to use French, then we can import it with: import 'flatpickr/dist/l10n/fr';`); - } - // for any other error do nothing - // Flatpickr is a little too sensitive and will throw an error when provided date is lower than minDate so just disregard the error completely - } - }; - - // merge options with optional user's custom options - this._pickerMergedOptions = { ...pickerOptions, ...(this.editorOptions as FlatpickrOption) }; - const inputCssClasses = `.editor-text.editor-${columnId}.form-control`; - if (this._pickerMergedOptions.altInput) { - this._pickerMergedOptions.altInputClass = 'flatpickr-alt-input form-control'; - } - - this._editorInputGroupElm = document.createElement('div'); - this._editorInputGroupElm.className = 'flatpickr input-group'; - - const closeButtonGroupElm = document.createElement('span'); - closeButtonGroupElm.className = 'input-group-btn input-group-append'; - closeButtonGroupElm.dataset.clear = ''; - - this._closeButtonElm = document.createElement('button'); - this._closeButtonElm.type = 'button'; - this._closeButtonElm.className = 'btn btn-default icon-clear'; - - this._inputElm = document.createElement('input'); - this._inputElm.dataset.input = ''; - this._inputElm.dataset.defaultdate = this.defaultDate; - this._inputElm.className = inputCssClasses.replace(/\./g, ' '); - this._inputElm.placeholder = placeholder; - this._inputElm.title = title; - - this._editorInputGroupElm.appendChild(this._inputElm); - - // show clear date button (unless user specifically doesn't want it) - if (!this.columnEditor?.params?.hideClearButton) { - closeButtonGroupElm.appendChild(this._closeButtonElm); - this._editorInputGroupElm.appendChild(closeButtonGroupElm); - this._bindEventService.bind(this._closeButtonElm, 'click', () => this._lastTriggeredByClearDate = true); - } - - this.args.container.appendChild(this._editorInputGroupElm); - this.flatInstance = flatpickr(this._editorInputGroupElm, this._pickerMergedOptions as unknown as Partial); - - // when we're using an alternate input to display data, we'll consider this input as the one to do the focus later on - // else just use the top one - this._inputWithDataElm = (this._pickerMergedOptions?.altInput) ? document.querySelector(`${inputCssClasses}.flatpickr-alt-input`) : this._inputElm; - - setTimeout(() => { - this.show(); - this.focus(); - }, 50); - } - } - - destroy() { - this.hide(); - this._bindEventService.unbindAll(); - - if (this.flatInstance?.destroy) { - this.flatInstance.destroy(); - if (this.flatInstance?.element) { - setTimeout(() => destroyObjectDomElementProps(this.flatInstance)); - } - } - emptyElement(this._editorInputGroupElm); - emptyElement(this._inputWithDataElm); - emptyElement(this._inputElm); - this._editorInputGroupElm?.remove?.(); - this._inputWithDataElm?.remove?.(); - this._inputElm?.remove?.(); - } - - focus() { - if (this._inputElm?.focus) { - this._inputElm.focus(); - } - if (this._inputWithDataElm?.focus) { - this._inputWithDataElm.focus(); - this._inputWithDataElm.select(); - } - } - - hide() { - if (this.flatInstance && typeof this.flatInstance.close === 'function') { - this.flatInstance.close(); - } - } - - show() { - if (this.flatInstance && typeof this.flatInstance.open === 'function' && this.flatInstance._input) { - this.flatInstance.open(); - } - } - - getValue(): string { - return this._inputElm.value; - } - - setValue(val: string) { - this.flatInstance.setDate(val); - } - - applyValue(item: any, state: any) { - const fieldName = this.columnDef && this.columnDef.field; - if (fieldName !== undefined) { - const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnEditor.type || this.columnDef.type)) || FieldType.dateUtc); - const saveTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.saveOutputType || this.columnDef.outputType || this.columnEditor.type || this.columnDef.type)) || FieldType.dateUtc); - const isComplexObject = fieldName?.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(state); - const newValue = (state && validation && validation.valid) ? moment(state, outputTypeFormat).format(saveTypeFormat) : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor?.complexObjectPath ?? fieldName ?? ''; - setDeepValue(item, objectPath, newValue); - } else { - item[fieldName] = newValue; - } - } - } - - isValueChanged(): boolean { - const elmValue = this._inputElm.value; - const inputFormat = mapMomentDateFormatWithFieldType(this.columnEditor.type || this.columnDef?.type || FieldType.dateIso); - const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnEditor.type || this.columnDef.type)) || FieldType.dateUtc); - const elmDateStr = elmValue ? moment(elmValue, inputFormat, false).format(outputTypeFormat) : ''; - const orgDateStr = this._originalDate ? moment(this._originalDate, inputFormat, false).format(outputTypeFormat) : ''; - if (elmDateStr === 'Invalid date' || orgDateStr === 'Invalid date') { - return false; - } - const isChanged = this._lastTriggeredByClearDate || (!(elmDateStr === '' && orgDateStr === '')) && (elmDateStr !== orgDateStr); - - return isChanged; - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (item && fieldName !== undefined) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - - this._originalDate = value; - this.flatInstance.setDate(value); - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue() { - const domValue: string = this._inputElm.value; - - if (!domValue) { - return ''; - } - - const inputFormat = mapMomentDateFormatWithFieldType(this.columnEditor.type || this.columnDef?.type || FieldType.dateIso); - const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnEditor.type || this.columnDef.type)) || FieldType.dateIso); - const value = moment(domValue, inputFormat, false).format(outputTypeFormat); - - return value; - } - - validate(inputValue?: any): EditorValidatorOutput { - const isRequired = this.columnEditor.required; - const elmValue = (inputValue !== undefined) ? inputValue : this._inputElm?.value; - const errorMsg = this.columnEditor.errorMessage; - - if (this.validator) { - return this.validator(elmValue, this.args); - } - - // by default the editor is almost always valid (except when it's required but not provided) - if (isRequired && elmValue === '') { - return { valid: false, msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD }; - } - - return { valid: true, msg: null }; - } - - // - // protected functions - // ------------------ - - protected handleOnDateChange() { - this._isValueTouched = true; - - if (this.args) { - this.save(); - } - setTimeout(() => this._lastTriggeredByClearDate = false); // reset flag after a cycle - } -} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/editors/dualInputEditor.ts b/src/app/modules/angular-slickgrid/editors/dualInputEditor.ts deleted file mode 100644 index 712717e76..000000000 --- a/src/app/modules/angular-slickgrid/editors/dualInputEditor.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { - DOMEvent, - Column, - ColumnEditor, - ColumnEditorDualInput, - Editor, - EditorArguments, - EditorValidator, - EditorValidatorOutput, - GridOption, - KeyCode, - SlickEventHandler, -} from '../models/index'; -import { BindingEventService } from '../services/bindingEvent.service'; -import { getDescendantProperty, setDeepValue } from '../services/utilities'; -import { floatValidator, integerValidator, textValidator } from '../editorValidators'; - -// using external non-typed js libraries -declare const Slick: any; - -/* - * An example of a 'detached' editor. - * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. - */ -export class DualInputEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _eventHandler: SlickEventHandler; - protected _isValueSaveCalled = false; - protected _lastEventType: string | undefined; - protected _lastInputKeyEvent?: JQuery.Event; - protected _leftInput!: HTMLInputElement; - protected _rightInput!: HTMLInputElement; - protected _leftFieldName!: string; - protected _rightFieldName!: string; - originalLeftValue?: string | number; - originalRightValue?: string | number; - - /** SlickGrid Grid object */ - grid: any; - - /** Grid options */ - gridOptions: GridOption; - - constructor(protected args: EditorArguments) { - if (!args) { - throw new Error('[Angular-Slickgrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = (this.grid.getOptions() || {}) as GridOption; - this._eventHandler = new Slick.EventHandler(); - this._bindEventService = new BindingEventService(); - this.init(); - this._eventHandler.subscribe(this.grid.onValidationError, () => this._isValueSaveCalled = true); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Get the Editor DOM Element */ - get editorDomElement(): { leftInput: HTMLInputElement, rightInput: HTMLInputElement } { - return { leftInput: this._leftInput, rightInput: this._rightInput }; - } - - get editorParams(): ColumnEditorDualInput { - return this.columnEditor.params || {}; - } - - get eventHandler(): SlickEventHandler { - return this._eventHandler; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - get isValueSaveCalled(): boolean { - return this._isValueSaveCalled; - } - - /** Get the Shared Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init() { - if (!this.editorParams || !this.editorParams.leftInput || !this.editorParams.leftInput.field || !this.editorParams.rightInput || !this.editorParams.rightInput.field) { - throw new Error(`[Angular-Slickgrid] Please make sure that your Combo Input Editor has params defined with "leftInput" and "rightInput" (example: { editor: { model: Editors.comboInput, params: { leftInput: { field: 'firstName' }, { rightSide: { field: 'lastName' } }}}`); - } - this._leftFieldName = this.editorParams && this.editorParams.leftInput && this.editorParams.leftInput.field; - this._rightFieldName = this.editorParams && this.editorParams.rightInput && this.editorParams.rightInput.field; - this._leftInput = this.createInput('leftInput'); - this._rightInput = this.createInput('rightInput'); - - const containerElm = this.args.container; - if (containerElm && typeof containerElm.appendChild === 'function') { - containerElm.appendChild(this._leftInput); - containerElm.appendChild(this._rightInput); - } - - this._leftInput.onkeydown = this.handleKeyDown.bind(this) as unknown as EventListener; - this._rightInput.onkeydown = this.handleKeyDown.bind(this) as unknown as EventListener; - - // the lib does not get the focus out event for some reason, so register it here - if (this.hasAutoCommitEdit) { - this._bindEventService.bind(this._leftInput, 'focusout', ((event: DOMEvent) => this.handleFocusOut(event, 'leftInput')) as EventListener); - this._bindEventService.bind(this._rightInput, 'focusout', ((event: DOMEvent) => this.handleFocusOut(event, 'rightInput')) as EventListener); - } - - setTimeout(() => this._leftInput.select(), 50); - } - - handleFocusOut(event: DOMEvent, position: 'leftInput' | 'rightInput') { - // when clicking outside the editable cell OR when focusing out of it - const targetClassNames = event.relatedTarget && event.relatedTarget.className || ''; - if (targetClassNames.indexOf('dual-editor') === -1 && this._lastEventType !== 'focusout-right') { - if (position === 'rightInput' || (position === 'leftInput' && this._lastEventType !== 'focusout-left')) { - this.save(); - } - } - const side = (position === 'leftInput') ? 'left' : 'right'; - this._lastEventType = `${event && event.type}-${side}`; - } - - handleKeyDown(event: JQuery.Event) { - this._lastInputKeyEvent = event; - if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT || event.keyCode === KeyCode.TAB) { - event.stopImmediatePropagation(); - } - } - - destroy() { - // unsubscribe all SlickGrid events - this._eventHandler.unsubscribeAll(); - this._bindEventService.unbindAll(); - this._leftInput?.remove?.(); - this._rightInput?.remove?.(); - } - - createInput(position: 'leftInput' | 'rightInput'): HTMLInputElement { - const editorSideParams = this.editorParams[position]; - const columnId = this.columnDef && this.columnDef.id; - const idPropName = this.gridOptions.datasetIdPropertyName || 'id'; - const itemId = this.args && this.args.item && this.args.item[idPropName] || 0; - - let fieldType: string = editorSideParams.type || 'text'; - if (fieldType === 'float' || fieldType === 'integer') { - fieldType = 'number'; - } - - const input = document.createElement('input') as HTMLInputElement; - input.id = `item-${itemId}-${position}`; - input.className = `dual-editor-text editor-${columnId} ${position.replace(/input/gi, '')}`; - if (fieldType === 'readonly') { - // when the custom type is defined as readonly, we'll make a readonly text input - input.readOnly = true; - fieldType = 'text'; - } - input.type = fieldType || 'text'; - input.setAttribute('role', 'presentation'); - input.autocomplete = 'off'; - input.placeholder = editorSideParams.placeholder || ''; - input.title = editorSideParams.title || ''; - if (fieldType === 'number') { - input.step = this.getInputDecimalSteps(position); - } - return input; - } - - focus() { - // do nothing since we have 2 inputs and we might focus on left/right depending on which is invalid and/or new - } - - getValues(): { [fieldName: string]: string | number } { - const obj = {}; - const leftInputValue = this._leftInput.value; - const rightInputValue = this._rightInput.value; - const isLeftInputTypeNumber = (this.editorParams.leftInput && (this.editorParams.leftInput.type === 'float' || this.editorParams.leftInput.type === 'integer')); - const isRightInputTypeNumber = (this.editorParams.rightInput && (this.editorParams.rightInput.type === 'float' || this.editorParams.rightInput.type === 'integer')); - const resultLeftValue = (leftInputValue !== '' && isLeftInputTypeNumber) ? +this._leftInput.value : (leftInputValue || ''); - const resultRightValue = (rightInputValue !== '' && isRightInputTypeNumber) ? +this._rightInput.value : (rightInputValue || ''); - setDeepValue(obj, this._leftFieldName, resultLeftValue); - setDeepValue(obj, this._rightFieldName, resultRightValue); - - return obj; - } - - setValues(values: Array) { - if (Array.isArray(values) && values.length === 2) { - this._leftInput.value = `${values[0]}`; - this._rightInput.value = `${values[1]}`; - } - } - - applyValue(item: any, state: any) { - this.applyValueByPosition(item, state, 'leftInput'); - this.applyValueByPosition(item, state, 'rightInput'); - } - - applyValueByPosition(item: any, state: any, position: 'leftInput' | 'rightInput') { - const fieldName = position === 'leftInput' ? this._leftFieldName : this._rightFieldName; - if (fieldName !== undefined) { - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - let fieldNameToUse = fieldName; - if (isComplexObject) { - const complexFieldNames = fieldName.split(/\.(.*)/); - fieldNameToUse = (complexFieldNames.length > 1 ? complexFieldNames[1] : complexFieldNames) as string; - } - - // validate the value before applying it (if not valid we'll set an empty string) - const stateValue = isComplexObject ? getDescendantProperty(state, fieldNameToUse) : state[fieldName]; - const validation = this.validate({ position, inputValue: stateValue }); - - // set the new value to the item datacontext - if (isComplexObject) { - const newValueFromComplex = getDescendantProperty(state, fieldNameToUse); - const newValue = (validation && validation.valid) ? newValueFromComplex : ''; - setDeepValue(item, fieldName, newValue); - } else if (fieldName) { - item[fieldName] = (validation && validation.valid) ? state[fieldName] : ''; - } - } - } - - isValueChanged(): boolean { - const leftElmValue = this._leftInput.value; - const rightElmValue = this._rightInput.value; - const leftEditorParams = this.editorParams && this.editorParams.leftInput; - const rightEditorParams = this.editorParams && this.editorParams.rightInput; - const lastKeyEvent = this._lastInputKeyEvent && this._lastInputKeyEvent.keyCode; - if ((leftEditorParams && leftEditorParams.alwaysSaveOnEnterKey || rightEditorParams && rightEditorParams.alwaysSaveOnEnterKey) && lastKeyEvent === KeyCode.ENTER) { - return true; - } - const leftResult = (!(leftElmValue === '' && (this.originalLeftValue === null || this.originalLeftValue === undefined))) && (leftElmValue !== this.originalLeftValue); - const rightResult = (!(rightElmValue === '' && (this.originalRightValue === null || this.originalRightValue === undefined))) && (rightElmValue !== this.originalRightValue); - return leftResult || rightResult; - } - - loadValue(item: any) { - this.loadValueByPosition(item, 'leftInput'); - this.loadValueByPosition(item, 'rightInput'); - this._leftInput.select(); - } - - loadValueByPosition(item: any, position: 'leftInput' | 'rightInput') { - // is the field a complex object, "address.streetNumber" - const fieldName = (position === 'leftInput') ? this._leftFieldName : this._rightFieldName; - const originalValuePosition = (position === 'leftInput') ? 'originalLeftValue' : 'originalRightValue'; - const inputVarPosition = (position === 'leftInput') ? '_leftInput' : '_rightInput'; - - if (item && fieldName !== undefined) { - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - const itemValue = (isComplexObject) ? getDescendantProperty(item, fieldName) : (item.hasOwnProperty(fieldName) ? item[fieldName] : ''); - this[originalValuePosition] = itemValue; - if (this.editorParams[position].type === 'float') { - const decimalPlaces = this.getDecimalPlaces(position); - if (decimalPlaces !== null && (this[originalValuePosition] || this[originalValuePosition] === 0) && (+(this as any)[originalValuePosition]).toFixed) { - this[originalValuePosition] = (+(this as any)[originalValuePosition]).toFixed(decimalPlaces); - } - } - if (this[inputVarPosition]) { - const originalValue = this[originalValuePosition]; - this[inputVarPosition].value = `${originalValue}`; - } - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (!this._isValueSaveCalled) { - if (this.hasAutoCommitEdit && isValid) { - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - this._isValueSaveCalled = true; - } - } - - serializeValue() { - const obj = {}; - const leftValue = this.serializeValueByPosition('leftInput'); - const rightValue = this.serializeValueByPosition('rightInput'); - - setDeepValue(obj, this._leftFieldName, leftValue); - setDeepValue(obj, this._rightFieldName, rightValue); - - return obj; - } - - serializeValueByPosition(position: 'leftInput' | 'rightInput') { - const elmValue = position === 'leftInput' ? this._leftInput.value : this._rightInput.value; - if (elmValue === '' || isNaN(+elmValue)) { - return elmValue; - } - - let rtn = parseFloat(elmValue); - const decPlaces = this.getDecimalPlaces(position); - if (decPlaces !== null && (rtn || rtn === 0) && rtn.toFixed) { - rtn = parseFloat(rtn.toFixed(decPlaces)); - } - - return rtn; - } - - getDecimalPlaces(position: 'leftInput' | 'rightInput'): number { - const defaultDecimalPlaces = 0; // TODO move into a constant - - // returns the number of fixed decimal places or null - const positionSide = position === 'leftInput' ? 'leftInput' : 'rightInput'; - const sideParams = this.editorParams[positionSide]; - const rtn: number | undefined = sideParams && sideParams.decimal; - - if (rtn === undefined) { - return defaultDecimalPlaces; - } - return rtn; - } - - getInputDecimalSteps(position: 'leftInput' | 'rightInput'): string { - const decimals = this.getDecimalPlaces(position); - let zeroString = ''; - for (let i = 1; i < decimals; i++) { - zeroString += '0'; - } - - if (decimals > 0) { - return `0.${zeroString}1`; - } - return '1'; - } - - validate(inputValidation?: { position: 'leftInput' | 'rightInput', inputValue: any }): EditorValidatorOutput { - if (inputValidation) { - const posValidation = this.validateByPosition(inputValidation.position, inputValidation.inputValue); - if (!posValidation.valid) { - inputValidation.position === 'leftInput' ? this._leftInput.select() : this._rightInput.select(); - return posValidation; - } - } else { - const leftValidation = this.validateByPosition('leftInput'); - const rightValidation = this.validateByPosition('rightInput'); - - if (!leftValidation.valid) { - this._leftInput.select(); - return leftValidation; - } - if (!rightValidation.valid) { - this._rightInput.select(); - return rightValidation; - } - } - return { valid: true, msg: '' }; - } - - validateByPosition(position: 'leftInput' | 'rightInput', inputValue?: any): EditorValidatorOutput { - const positionEditorParams = this.editorParams[position]; - let currentVal: any = ''; - if (inputValue) { - currentVal = inputValue; - } else { - const input = position === 'leftInput' ? this._leftInput : this._rightInput; - currentVal = input && input.value; - } - - // there are 2 ways of passing a Validator, 1-independent validator on each side, 2-shared validator - const commonValidator = this.validator; - currentVal = typeof commonValidator === 'function' ? this.getValues() : currentVal; - const baseValidatorOptions = { - editorArgs: this.args, - errorMessage: positionEditorParams.errorMessage, - required: positionEditorParams.required, - validator: typeof commonValidator === 'function' ? commonValidator : positionEditorParams.validator, - }; - - switch (positionEditorParams.type) { - case 'float': - return floatValidator(currentVal, { - ...baseValidatorOptions, - decimal: this.getDecimalPlaces(position), - minValue: positionEditorParams.minValue, - maxValue: positionEditorParams.maxValue, - operatorConditionalType: positionEditorParams.operatorConditionalType, - }); - case 'integer': - return integerValidator(currentVal, { - ...baseValidatorOptions, - minValue: positionEditorParams.minValue, - maxValue: positionEditorParams.maxValue, - operatorConditionalType: positionEditorParams.operatorConditionalType, - }); - case 'text': - case 'password': - default: - return textValidator(currentVal, baseValidatorOptions); - } - } -} diff --git a/src/app/modules/angular-slickgrid/editors/editors.index.ts b/src/app/modules/angular-slickgrid/editors/editors.index.ts deleted file mode 100644 index 732e075ab..000000000 --- a/src/app/modules/angular-slickgrid/editors/editors.index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AutoCompleteEditor } from './autoCompleteEditor'; -import { CheckboxEditor } from './checkboxEditor'; -import { DateEditor } from './dateEditor'; -import { DualInputEditor } from './dualInputEditor'; -import { FloatEditor } from './floatEditor'; -import { InputEditor } from './inputEditor'; -import { InputPasswordEditor } from './inputPasswordEditor'; -import { IntegerEditor } from './integerEditor'; -import { LongTextEditor } from './longTextEditor'; -import { MultipleSelectEditor } from './multipleSelectEditor'; -import { SingleSelectEditor } from './singleSelectEditor'; -import { SliderEditor } from './sliderEditor'; - -export const Editors = { - /** AutoComplete Editor (using jQuery UI autocomplete feature) */ - autoComplete: AutoCompleteEditor, - - /** Checkbox Editor (uses native checkbox DOM element) */ - checkbox: CheckboxEditor, - - /** Date Picker Editor (which uses 3rd party lib "flatpickr") */ - date: DateEditor, - - /** Dual Input Editor, default input type is text but it could be (integer/float/number/password/text) */ - dualInput: DualInputEditor, - - /** Float Number Editor using an input of type "number" */ - float: FloatEditor, - - /** Integer Number Editor using an input of type "number" */ - integer: IntegerEditor, - - /** Long Text Editor (uses a textarea) */ - longText: LongTextEditor, - - /** Multiple Select editor (which uses 3rd party lib "multiple-select.js") */ - multipleSelect: MultipleSelectEditor, - - /** Editor with an input of type Password (note that only the text shown in the UI will be masked, the editor value is still plain text) */ - password: InputPasswordEditor, - - /** Single Select editor (which uses 3rd party lib "multiple-select.js") */ - singleSelect: SingleSelectEditor, - - /** Slider Editor using an input of type "range" */ - slider: SliderEditor, - - /** Text Editor using an input of type "text" */ - text: InputEditor -}; diff --git a/src/app/modules/angular-slickgrid/editors/floatEditor.ts b/src/app/modules/angular-slickgrid/editors/floatEditor.ts deleted file mode 100644 index 684ed502c..000000000 --- a/src/app/modules/angular-slickgrid/editors/floatEditor.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { EditorArguments, EditorValidatorOutput, KeyCode } from './../models/index'; -import { getDescendantProperty } from '../services/utilities'; -import { floatValidator } from '../editorValidators/floatValidator'; -import { InputEditor } from './inputEditor'; - -const DEFAULT_DECIMAL_PLACES = 0; - -export class FloatEditor extends InputEditor { - constructor(protected readonly args: EditorArguments) { - super(args, 'number'); - } - - /** Initialize the Editor */ - init() { - if (this.columnDef && this.columnEditor && this.args) { - const columnId = this.columnDef?.id ?? ''; - const placeholder = this.columnEditor?.placeholder ?? ''; - const title = this.columnEditor?.title ?? ''; - const inputStep = (this.columnEditor.valueStep !== undefined) ? this.columnEditor.valueStep : this.getInputDecimalSteps(); - - this._input = document.createElement('input') as HTMLInputElement; - this._input.className = `editor-text editor-${columnId}`; - this._input.type = 'number'; - this._input.setAttribute('role', 'presentation'); - this._input.autocomplete = 'off'; - this._input.placeholder = placeholder; - this._input.title = title; - this._input.step = `${inputStep}`; - const cellContainer = this.args.container; - if (cellContainer && typeof cellContainer.appendChild === 'function') { - cellContainer.appendChild(this._input); - } - - this._bindEventService.bind(this._input, 'focus', () => this._input?.select()); - this._bindEventService.bind(this._input, 'keydown', ((event: KeyboardEvent) => { - this._lastInputKeyEvent = event; - if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { - event.stopImmediatePropagation(); - } - }) as EventListener); - - // the lib does not get the focus out event for some reason - // so register it here - if (this.hasAutoCommitEdit) { - this._bindEventService.bind(this._input, 'focusout', () => { - this.save(); - }); - } - } - } - - getDecimalPlaces(): number { - // returns the number of fixed decimal places or null - let rtn = this.columnEditor?.decimal ?? this.columnEditor?.params?.decimalPlaces ?? undefined; - - if (rtn === undefined) { - rtn = DEFAULT_DECIMAL_PLACES; - } - return (!rtn && rtn !== 0 ? null : rtn); - } - - getInputDecimalSteps(): string { - const decimals = this.getDecimalPlaces(); - let zeroString = ''; - for (let i = 1; i < decimals; i++) { - zeroString += '0'; - } - - if (decimals > 0) { - return `0.${zeroString}1`; - } - return '1'; - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (fieldName !== undefined) { - - if (item && fieldName !== undefined && this._input) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - - this._originalValue = value; - const decPlaces = this.getDecimalPlaces(); - if (decPlaces !== null && (this._originalValue || this._originalValue === 0) && (+this._originalValue).toFixed) { - this._originalValue = (+this._originalValue).toFixed(decPlaces); - } - this._input.value = `${this._originalValue}`; - this._input.select(); - } - } - } - - serializeValue() { - const elmValue = this._input?.value; - if (elmValue === undefined || elmValue === '' || isNaN(+elmValue)) { - return elmValue as string; - } - - let rtn = parseFloat(elmValue); - const decPlaces = this.getDecimalPlaces(); - if (decPlaces !== null && (rtn || rtn === 0) && rtn.toFixed) { - rtn = parseFloat(rtn.toFixed(decPlaces)); - } - - return rtn; - } - - validate(inputValue?: any): EditorValidatorOutput { - const elmValue = (inputValue !== undefined) ? inputValue : this._input?.value; - return floatValidator(elmValue, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - decimal: this.getDecimalPlaces(), - minValue: this.columnEditor.minValue, - maxValue: this.columnEditor.maxValue, - operatorConditionalType: this.columnEditor.operatorConditionalType, - required: this.columnEditor.required, - validator: this.validator, - }); - } -} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/editors/index.ts b/src/app/modules/angular-slickgrid/editors/index.ts deleted file mode 100644 index b7b2d24b7..000000000 --- a/src/app/modules/angular-slickgrid/editors/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * from './autoCompleteEditor'; -export * from './checkboxEditor'; -export * from './dateEditor'; -export * from './dualInputEditor'; -export * from './editors.index'; -export * from './floatEditor'; -export * from './inputEditor'; -export * from './integerEditor'; -export * from './longTextEditor'; -export * from './multipleSelectEditor'; -export * from './selectEditor'; -export * from './singleSelectEditor'; -export * from './sliderEditor'; diff --git a/src/app/modules/angular-slickgrid/editors/inputEditor.ts b/src/app/modules/angular-slickgrid/editors/inputEditor.ts deleted file mode 100644 index ccf68ca46..000000000 --- a/src/app/modules/angular-slickgrid/editors/inputEditor.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Column, ColumnEditor, Editor, EditorArguments, EditorValidator, EditorValidatorOutput, GridOption, KeyCode, SlickGrid } from '../models/index'; -import { getDescendantProperty, setDeepValue } from '../services/utilities'; -import { textValidator } from '../editorValidators/textValidator'; -import { BindingEventService } from '../services/bindingEvent.service'; - -/* - * An example of a 'detached' editor. - * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. - */ -export class InputEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _input!: HTMLInputElement | undefined; - protected _inputType = 'text'; - protected _lastInputKeyEvent?: KeyboardEvent; - protected _originalValue?: number | string; - - /** SlickGrid Grid object */ - grid: SlickGrid; - - /** Grid options */ - gridOptions: GridOption; - - constructor(protected readonly args: EditorArguments, inputType: string) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = args.grid && args.grid.getOptions() as GridOption; - this._bindEventService = new BindingEventService(); - this.inputType = inputType || 'text'; - this.init(); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Getter for the Editor DOM Element */ - get editorDomElement(): any { - return this._input; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** Getter of input type (text, number, password) */ - get inputType() { - return this._inputType; - } - - /** Setter of input type (text, number, password) */ - set inputType(type: string) { - this._inputType = type; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init() { - const columnId = this.columnDef?.id ?? ''; - const placeholder = this.columnEditor?.placeholder ?? ''; - const title = this.columnEditor?.title ?? ''; - - this._input = document.createElement('input') as HTMLInputElement; - this._input.className = `editor-text editor-${columnId}`; - this._input.type = this._inputType || 'text'; - this._input.setAttribute('role', 'presentation'); - this._input.autocomplete = 'off'; - this._input.placeholder = placeholder; - this._input.title = title; - const cellContainer = this.args.container; - if (cellContainer && typeof cellContainer.appendChild === 'function') { - cellContainer.appendChild(this._input); - } - - this._bindEventService.bind(this._input, 'focus', () => this._input?.select()); - this._bindEventService.bind(this._input, 'keydown', ((event: KeyboardEvent) => { - this._lastInputKeyEvent = event; - if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { - event.stopImmediatePropagation(); - } - }) as EventListener); - - // the lib does not get the focus out event for some reason - // so register it here - if (this.hasAutoCommitEdit) { - this._bindEventService.bind(this._input, 'focusout', () => { - this.save(); - }); - } - } - - destroy() { - this._bindEventService.unbindAll(); - this._input?.remove?.(); - } - - focus(): void { - if (this._input) { - this._input.focus(); - } - } - - getValue(): string { - return this._input?.value || ''; - } - - setValue(value: number | string) { - if (this._input) { - this._input.value = `${value}`; - } - } - - applyValue(item: any, state: any) { - const fieldName = this.columnDef && this.columnDef.field; - if (fieldName !== undefined) { - const isComplexObject = fieldName?.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(state); - const newValue = (validation?.valid) ? state : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor?.complexObjectPath ?? fieldName ?? ''; - setDeepValue(item, objectPath, newValue); - } else if (fieldName) { - item[fieldName] = newValue; - } - } - } - - isValueChanged(): boolean { - const elmValue = this._input?.value; - const lastKeyEvent = this._lastInputKeyEvent && this._lastInputKeyEvent.keyCode; - if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastKeyEvent === KeyCode.ENTER) { - return true; - } - return (!(elmValue === '' && (this._originalValue === null || this._originalValue === undefined))) && (elmValue !== this._originalValue); - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (item && fieldName !== undefined && this._input) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : (item.hasOwnProperty(fieldName) && item[fieldName] || ''); - - this._originalValue = value; - this._input.value = this._originalValue as string; - this._input.select(); - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue(): number | string { - return this._input?.value ?? ''; - } - - validate(inputValue?: any): EditorValidatorOutput { - const elmValue = (inputValue !== undefined) ? inputValue : this._input && this._input.value; - return textValidator(elmValue, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - minLength: this.columnEditor.minLength, - maxLength: this.columnEditor.maxLength, - operatorConditionalType: this.columnEditor.operatorConditionalType, - required: this.columnEditor.required, - validator: this.validator, - }); - } -} diff --git a/src/app/modules/angular-slickgrid/editors/inputPasswordEditor.ts b/src/app/modules/angular-slickgrid/editors/inputPasswordEditor.ts deleted file mode 100644 index a08d6ca62..000000000 --- a/src/app/modules/angular-slickgrid/editors/inputPasswordEditor.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EditorArguments } from '../models/editorArguments.interface'; -import { InputEditor } from './inputEditor'; - -export class InputPasswordEditor extends InputEditor { - /** Initialize the Editor */ - constructor(protected readonly args: EditorArguments) { - super(args, 'password'); - } -} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/editors/integerEditor.ts b/src/app/modules/angular-slickgrid/editors/integerEditor.ts deleted file mode 100644 index fcd94a7b2..000000000 --- a/src/app/modules/angular-slickgrid/editors/integerEditor.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { EditorArguments, EditorValidatorOutput, KeyCode } from './../models/index'; -import { getDescendantProperty } from '../services/utilities'; -import { integerValidator } from '../editorValidators/integerValidator'; -import { InputEditor } from './inputEditor'; - -export class IntegerEditor extends InputEditor { - constructor(protected readonly args: EditorArguments) { - super(args, 'number'); - } - - /** Initialize the Editor */ - init() { - if (this.columnDef && this.columnEditor && this.args) { - const columnId = this.columnDef?.id ?? ''; - const placeholder = this.columnEditor?.placeholder ?? ''; - const title = this.columnEditor?.title ?? ''; - const inputStep = (this.columnEditor.valueStep !== undefined) ? this.columnEditor.valueStep : '1'; - - this._input = document.createElement('input') as HTMLInputElement; - this._input.className = `editor-text editor-${columnId}`; - this._input.type = 'number'; - this._input.setAttribute('role', 'presentation'); - this._input.autocomplete = 'off'; - this._input.placeholder = placeholder; - this._input.title = title; - this._input.step = `${inputStep}`; - const cellContainer = this.args.container; - if (cellContainer && typeof cellContainer.appendChild === 'function') { - cellContainer.appendChild(this._input); - } - - this._bindEventService.bind(this._input, 'focus', () => this._input?.select()); - this._bindEventService.bind(this._input, 'keydown', ((event: KeyboardEvent) => { - this._lastInputKeyEvent = event; - if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT) { - event.stopImmediatePropagation(); - } - }) as EventListener); - - // the lib does not get the focus out event for some reason - // so register it here - if (this.hasAutoCommitEdit) { - this._bindEventService.bind(this._input, 'focusout', () => { - this.save(); - }); - } - } - } - - loadValue(item: any) { - const fieldName = this.columnDef && this.columnDef.field; - - if (fieldName !== undefined) { - - if (item && fieldName !== undefined && this._input) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - this._originalValue = (isNaN(value) || value === null || value === undefined) ? value : `${value}`; - this._input.value = `${this._originalValue}`; - this._input.select(); - } - } - } - - serializeValue() { - const elmValue = this._input?.value; - if (elmValue === undefined || elmValue === '' || isNaN(+elmValue)) { - return elmValue as string; - } - const output = isNaN(+elmValue) ? elmValue : parseInt(elmValue, 10); - return isNaN(+output) ? elmValue : output; - } - - validate(inputValue?: any): EditorValidatorOutput { - const elmValue = (inputValue !== undefined) ? inputValue : this.getValue(); - return integerValidator(elmValue, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - minValue: this.columnEditor.minValue, - maxValue: this.columnEditor.maxValue, - operatorConditionalType: this.columnEditor.operatorConditionalType, - required: this.columnEditor.required, - validator: this.validator, - }); - } -} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/editors/longTextEditor.ts b/src/app/modules/angular-slickgrid/editors/longTextEditor.ts deleted file mode 100644 index 350023c1f..000000000 --- a/src/app/modules/angular-slickgrid/editors/longTextEditor.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { TranslateService } from '@ngx-translate/core'; -import { Constants } from './../constants'; -import { - Column, - ColumnEditor, - Editor, - EditorArguments, - EditorValidator, - EditorValidatorOutput, - GridOption, - HtmlElementPosition, - KeyCode, - Locale, - LongTextEditorOption, - SlickGrid, -} from './../models/index'; -import { BindingEventService } from '../services/bindingEvent.service'; -import { getDescendantProperty, getHtmlElementOffset, getTranslationPrefix, setDeepValue } from '../services/utilities'; -import { textValidator } from '../editorValidators/textValidator'; - -// using external non-typed js libraries -declare const $: any; - -/* - * An example of a 'detached' editor. - * The UI is added onto document BODY and .position(), .show() and .hide() are implemented. - * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. - */ -export class LongTextEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _defaultTextValue: any; - protected _locales: Locale; - protected _timer?: any; - protected _currentLengthElm!: HTMLSpanElement; - protected _textareaElm!: HTMLTextAreaElement; - protected _wrapperElm!: HTMLDivElement; - - /** SlickGrid Grid object */ - grid: SlickGrid; - - /** Grid options */ - gridOptions: GridOption; - - /** The translate library */ - protected _translate?: TranslateService; - - constructor(protected readonly args: EditorArguments) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = args.grid.getOptions() as GridOption; - const options = this.gridOptions || this.args.column.params || {}; - if (options && options.i18n instanceof TranslateService) { - this._translate = options.i18n; - } - - // get locales provided by user in forRoot or else use default English locales via the Constants - this._locales = this.gridOptions && this.gridOptions.locales || Constants.locales; - - this._bindEventService = new BindingEventService(); - this.init(); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef?.internalColumnEditor ?? {}; - } - - /** Getter for the Editor DOM Element */ - get editorDomElement(): HTMLTextAreaElement { - return this._textareaElm; - } - - get editorOptions(): LongTextEditorOption { - return this.columnEditor?.editorOptions ?? {}; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init(): void { - let cancelText = ''; - let saveText = ''; - if (this._translate?.instant && this.gridOptions.enableTranslate) { - const translationPrefix = getTranslationPrefix(this.gridOptions); - const cancelKey = this.editorOptions.buttonTexts?.cancelKey ?? `${translationPrefix}CANCEL`; - const saveKey = this.editorOptions.buttonTexts?.saveKey ?? `${translationPrefix}SAVE`; - cancelText = this._translate.instant(`${translationPrefix}${cancelKey}`); - saveText = this._translate.instant(`${translationPrefix}${saveKey}`); - } else { - cancelText = this.editorOptions.buttonTexts?.cancel ?? this._locales?.TEXT_CANCEL ?? 'Cancel'; - saveText = this.editorOptions.buttonTexts?.save ?? this._locales?.TEXT_SAVE ?? 'Save'; - } - - const columnId = this.columnDef?.id ?? ''; - const placeholder = this.columnEditor?.placeholder ?? ''; - const title = this.columnEditor?.title ?? ''; - const maxLength = this.columnEditor?.maxLength; - const textAreaCols = this.editorOptions?.cols ?? 40; - const textAreaRows = this.editorOptions?.rows ?? 4; - - const containerElm = document.body; - this._wrapperElm = document.createElement('div'); - this._wrapperElm.className = `slick-large-editor-text editor-${columnId}`; - this._wrapperElm.style.position = 'absolute'; - containerElm.appendChild(this._wrapperElm); - - this._textareaElm = document.createElement('textarea'); - this._textareaElm.cols = textAreaCols; - this._textareaElm.rows = textAreaRows; - this._textareaElm.placeholder = placeholder; - this._textareaElm.title = title; - this._wrapperElm.appendChild(this._textareaElm); - - const editorFooterElm = document.createElement('div'); - editorFooterElm.className = 'editor-footer'; - - const countContainerElm = document.createElement('span'); - countContainerElm.className = 'counter'; - - this._currentLengthElm = document.createElement('span'); - this._currentLengthElm.className = 'text-length'; - this._currentLengthElm.textContent = '0'; - countContainerElm.appendChild(this._currentLengthElm); - - if (maxLength !== undefined) { - const maxLengthSeparatorElm = document.createElement('span'); - maxLengthSeparatorElm.className = 'separator'; - maxLengthSeparatorElm.textContent = '/'; - const maxLengthElm = document.createElement('span'); - maxLengthElm.className = 'max-length'; - maxLengthElm.textContent = `${maxLength}`; - countContainerElm.appendChild(maxLengthSeparatorElm); - countContainerElm.appendChild(maxLengthElm); - } - editorFooterElm.appendChild(countContainerElm); - - const cancelBtnElm = document.createElement('button'); - cancelBtnElm.className = 'btn btn-cancel btn-default btn-xs'; - cancelBtnElm.textContent = cancelText; - const saveBtnElm = document.createElement('button'); - saveBtnElm.className = 'btn btn-save btn-primary btn-xs'; - saveBtnElm.textContent = saveText; - editorFooterElm.appendChild(cancelBtnElm); - editorFooterElm.appendChild(saveBtnElm); - this._bindEventService.bind(cancelBtnElm, 'click', this.cancel.bind(this) as EventListener); - this._bindEventService.bind(saveBtnElm, 'click', this.save.bind(this) as EventListener); - this.position(this.args?.position); - this._textareaElm.focus(); - this._textareaElm.select(); - this._wrapperElm.appendChild(editorFooterElm); - - this._bindEventService.bind(this._textareaElm, 'keydown', this.handleKeyDown.bind(this) as EventListener); - this._bindEventService.bind(this._textareaElm, 'input', this.handleOnInputChange.bind(this) as unknown as EventListener); - this._bindEventService.bind(this._textareaElm, 'paste', this.handleOnInputChange.bind(this) as unknown as EventListener); - } - - cancel() { - const value = this._defaultTextValue || ''; - this._textareaElm.value = value; - this._currentLengthElm.textContent = `${value.length}`; - if (this.args?.cancelChanges) { - this.args.cancelChanges(); - } - } - - hide() { - this._wrapperElm.style.display = 'none'; - } - - show() { - this._wrapperElm.style.display = 'block'; - } - - destroy() { - this._bindEventService.unbindAll(); - this._wrapperElm?.remove?.(); - } - - focus() { - if (this._textareaElm) { - this._textareaElm.focus(); - this._textareaElm.select(); - } - } - - getValue(): string { - return this._textareaElm.value; - } - - setValue(val: string) { - this._textareaElm.value = val; - this._currentLengthElm.textContent = `${val.length}`; - } - - applyValue(item: any, state: any) { - const fieldName = this.columnDef?.field; - if (fieldName !== undefined) { - const isComplexObject = fieldName?.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(state); - const newValue = (validation && validation.valid) ? state : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor?.complexObjectPath ?? fieldName ?? ''; - setDeepValue(item, objectPath, newValue); - } else { - item[fieldName] = newValue; - } - } - } - - isValueChanged(): boolean { - const elmValue = this._textareaElm.value; - return (!(elmValue === '' && (this._defaultTextValue === null || this._defaultTextValue === undefined))) && (elmValue !== this._defaultTextValue); - } - - loadValue(item: any) { - const fieldName = this.columnDef?.field; - - if (item && fieldName !== undefined) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - const value = (isComplexObject) ? getDescendantProperty(item, fieldName) : item[fieldName]; - - this._defaultTextValue = value || ''; - this._textareaElm.value = this._defaultTextValue; - this._currentLengthElm.textContent = this._defaultTextValue.length; - this._textareaElm.defaultValue = this._defaultTextValue; - this._textareaElm.select(); - } - } - - /** - * Reposition the LongText Editor to be right over the cell, so that it looks like we opened the editor on top of the cell when in reality we just reposition (absolute) over the cell. - * By default we use an "auto" mode which will allow to position the LongText Editor to the best logical position in the window, also when we say position, we are talking about the relative position against the grid cell. - * We can assume that in 80% of the time the default position is bottom right, the default is "auto" but we can also override this and use a specific position. - * Most of the time positioning of the editor will be to the "right" of the cell is ok but if our column is completely on the right side then we'll want to change the position to "left" align. - * Same goes for the top/bottom position, Most of the time positioning the editor to the "bottom" but we are clicking on a cell at the bottom of the grid then we might need to reposition to "top" instead. - */ - position(parentPosition: HtmlElementPosition) { - const containerOffset = getHtmlElementOffset(this.args.container); - const containerHeight = this.args.container.offsetHeight; - const containerWidth = this.args.container.offsetWidth; - const calculatedEditorHeight = this._wrapperElm.getBoundingClientRect().height || this.args.position.height; - const calculatedEditorWidth = this._wrapperElm.getBoundingClientRect().width || this.args.position.width; - const calculatedBodyHeight = document.body.offsetHeight || window.innerHeight; // body height/width might be 0 if so use the window height/width - const calculatedBodyWidth = document.body.offsetWidth || window.innerWidth; - - // first defined position will be bottom/right (which will position the editor completely over the cell) - let newPositionTop = containerOffset?.top ?? parentPosition.top ?? 0; - let newPositionLeft = containerOffset?.left ?? parentPosition.left ?? 0; - - // user could explicitely use a "left" position (when user knows his column is completely on the right) - // or when using "auto" and we detect not enough available space then we'll position to the "left" of the cell - const position = this.editorOptions?.position ?? 'auto'; - if (position === 'left' || (position === 'auto' && (newPositionLeft + calculatedEditorWidth) > calculatedBodyWidth)) { - const marginRightAdjustment = this.editorOptions?.marginRight ?? 0; - newPositionLeft -= (calculatedEditorWidth - containerWidth + marginRightAdjustment); - } - - // do the same calculation/reposition with top/bottom (default is bottom of the cell or in other word starting from the cell going down) - if (position === 'top' || (position === 'auto' && (newPositionTop + calculatedEditorHeight) > calculatedBodyHeight)) { - newPositionTop -= (calculatedEditorHeight - containerHeight); - } - - // reposition the editor over the cell (90% of the time this will end up using a position on the "right" of the cell) - this._wrapperElm.style.top = `${newPositionTop}px`; - this._wrapperElm.style.left = `${newPositionLeft}px`; - } - - save() { - const validation = this.validate(); - const isValid = validation?.valid ?? false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue() { - return this._textareaElm.value; - } - - validate(inputValue?: any): EditorValidatorOutput { - const elmValue = (inputValue !== undefined) ? inputValue : this._textareaElm?.value; - return textValidator(elmValue, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - minLength: this.columnEditor.minLength, - maxLength: this.columnEditor.maxLength, - operatorConditionalType: this.columnEditor.operatorConditionalType, - required: this.columnEditor.required, - validator: this.validator, - }); - } - - // -- - // protected functions - // ------------------ - - protected handleKeyDown(event: KeyboardEvent) { - const keyCode = event.keyCode ?? event.code; - - if (keyCode === KeyCode.ENTER && event.ctrlKey) { - this.save(); - } else if (keyCode === KeyCode.ESCAPE) { - event.preventDefault(); - this.cancel(); - } else if (keyCode === KeyCode.TAB && event.shiftKey) { - event.preventDefault(); - if (this.args && this.grid) { - this.grid.navigatePrev(); - } - } else if (keyCode === KeyCode.TAB) { - event.preventDefault(); - if (this.args && this.grid) { - this.grid.navigateNext(); - } - } - } - - /** On every input change event, we'll update the current text length counter */ - protected handleOnInputChange(event: Event & { clipboardData: DataTransfer, target: HTMLTextAreaElement }) { - const maxLength = this.columnEditor?.maxLength; - - // when user defines a maxLength, we'll make sure that it doesn't go over this limit if so then truncate the text (disregard the extra text) - let isTruncated = false; - if (maxLength) { - isTruncated = this.truncateText(this._textareaElm, maxLength); - } - - // if the text get truncated then update text length as maxLength, else update text length with actual - if (isTruncated) { - this._currentLengthElm.textContent = `${maxLength}`; - } else { - const newText = event.type === 'paste' ? event.clipboardData.getData('text') : event.target.value; - this._currentLengthElm.textContent = `${newText.length}`; - } - } - - /** - * Truncate text if the value is longer than the acceptable max length - * @param inputElm - textarea html element - * @param maxLength - max acceptable length - * @returns truncated - returns True if it truncated or False otherwise - */ - protected truncateText(inputElm: HTMLTextAreaElement, maxLength: number): boolean { - const text = inputElm.value + ''; - if (text.length > maxLength) { - inputElm.value = text.substring(0, maxLength); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/editors/multipleSelectEditor.ts b/src/app/modules/angular-slickgrid/editors/multipleSelectEditor.ts deleted file mode 100644 index 81219cb77..000000000 --- a/src/app/modules/angular-slickgrid/editors/multipleSelectEditor.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SelectEditor } from './selectEditor'; - -export class MultipleSelectEditor extends SelectEditor { - /** - * Initialize the Editor - */ - constructor(protected args: any) { - super(args, true); - } -} diff --git a/src/app/modules/angular-slickgrid/editors/selectEditor.ts b/src/app/modules/angular-slickgrid/editors/selectEditor.ts deleted file mode 100644 index b7a9323e5..000000000 --- a/src/app/modules/angular-slickgrid/editors/selectEditor.ts +++ /dev/null @@ -1,629 +0,0 @@ -import { TranslateService } from '@ngx-translate/core'; -import { dequal } from 'dequal/lite'; -import { Subscription } from 'rxjs'; - -import { Constants } from '../constants'; -import { - CollectionCustomStructure, - CollectionOption, - Column, - ColumnEditor, - Editor, - EditorArguments, - EditorValidator, - EditorValidatorOutput, - FieldType, - GridOption, - Locale, - MultipleSelectOption, - SelectOption, -} from './../models/index'; -import { buildSelectEditorOrFilterDomElement, CollectionService } from '../services/index'; -import { findOrDefault, getDescendantProperty, getTranslationPrefix, setDeepValue } from '../services/utilities'; - -// using external non-typed js libraries -declare const $: any; - -/** - * Slickgrid editor class for multiple/single select lists - */ -export class SelectEditor implements Editor { - /** The JQuery DOM element */ - $editorElm: any; - - /** Editor Multiple-Select options */ - editorElmOptions!: MultipleSelectOption; - - /** DOM Element Name, useful for auto-detecting positioning (dropup / dropdown) */ - elementName: string; - - /** The multiple-select options for a multiple select list */ - defaultOptions: MultipleSelectOption; - - /** The original item values that are set at the beginning */ - originalValue?: any[]; - - /** The property name for values in the collection */ - valueName!: string; - - /** The property name for labels in the collection */ - labelName!: string; - - /** The property name for a prefix that can be added to the labels in the collection */ - labelPrefixName!: string; - - /** The property name for a suffix that can be added to the labels in the collection */ - labelSuffixName!: string; - - /** A label that can be added to each option and can be used as an alternative to display selected options */ - optionLabel!: string; - - /** Grid options */ - gridOptions: GridOption; - - /** Do we translate the label? */ - enableTranslateLabel = false; - - /** Locales */ - protected _locales: Locale; - - /** Observable Subscriptions */ - protected _subscriptions: Subscription[] = []; - - // flag to signal that the editor is destroying itself, helps prevent - // commit changes from being called twice and erroring - protected _isDisposing = false; - - /** Collection Service */ - protected _collectionService!: CollectionService; - - /** The translate library */ - protected _translate?: TranslateService; - - /** SlickGrid Grid object */ - grid: any; - - constructor(protected args: EditorArguments, protected isMultipleSelect: boolean) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = (this.grid.getOptions() || {}) as GridOption; - const options = this.gridOptions || this.args.column.params || {}; - if (options && options.i18n instanceof TranslateService) { - this._translate = options.i18n; - } - - // get locales provided by user in main file or else use default English locales via the Constants - this._locales = this.gridOptions.locales || Constants.locales; - - // provide the name attribute to the DOM element which will be needed to auto-adjust drop position (dropup / dropdown) - const fieldId = this.columnDef && this.columnDef.id; - this.elementName = `editor-${fieldId}`; - - const libOptions: MultipleSelectOption = { - autoAdjustDropHeight: true, - autoAdjustDropPosition: true, - autoAdjustDropWidthByTextSize: true, - container: 'body', - filter: false, - maxHeight: 275, - name: this.elementName, - single: true, - textTemplate: ($elm) => { - // render HTML code or not, by default it is sanitized and won't be rendered - const isRenderHtmlEnabled = this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.enableRenderHtml || false; - return isRenderHtmlEnabled ? $elm.text() : $elm.html(); - }, - onClose: () => this.save(), - }; - - if (isMultipleSelect) { - libOptions.single = false; - libOptions.addTitle = true; - libOptions.okButton = true; - libOptions.selectAllDelimiter = ['', '']; - - if (this._translate && this._translate.instant && this._translate.currentLang) { - const translationPrefix = getTranslationPrefix(this.gridOptions); - libOptions.countSelected = this._translate.instant(`${translationPrefix}X_OF_Y_SELECTED`); - libOptions.allSelected = this._translate.instant(`${translationPrefix}ALL_SELECTED`); - libOptions.selectAllText = this._translate.instant(`${translationPrefix}SELECT_ALL`); - libOptions.okButtonText = this._translate.instant(`${translationPrefix}OK`); - } else { - libOptions.countSelected = this._locales && this._locales.TEXT_X_OF_Y_SELECTED; - libOptions.allSelected = this._locales && this._locales.TEXT_ALL_SELECTED; - libOptions.selectAllText = this._locales && this._locales.TEXT_SELECT_ALL; - libOptions.okButtonText = this._locales && this._locales.TEXT_OK; - } - } - - // assign the multiple select lib options - this.defaultOptions = libOptions; - - this.init(); - } - - /** Get the Collection */ - get collection(): SelectOption[] { - return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collection || []; - } - - - /** Getter for the Collection Options */ - get collectionOptions(): CollectionOption | undefined { - return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionOptions; - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor | undefined { - return this.columnDef && this.columnDef.internalColumnEditor; - } - - /** Get the Editor DOM Element */ - get editorDomElement(): any { - return this.$editorElm; - } - - /** Getter for the Custom Structure if exist */ - protected get customStructure(): CollectionCustomStructure | undefined { - return this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.customStructure; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** - * The current selected values (multiple select) from the collection - */ - get currentValues() { - const elmValue = this.$editorElm.val(); - - // collection of strings, just return the filtered string that are equals - if (this.collection.every(x => typeof x === 'string')) { - return this.collection.filter(c => elmValue.indexOf(c.toString()) !== -1); - } - - // collection of label/value pair - const separatorBetweenLabels = this.collectionOptions && this.collectionOptions.separatorBetweenTextLabels || ''; - const isIncludingPrefixSuffix = this.collectionOptions && this.collectionOptions.includePrefixSuffixToSelectedValues || false; - - return this.collection - .filter(c => elmValue.indexOf(c.hasOwnProperty(this.valueName) && c[this.valueName] !== null && c[this.valueName].toString()) !== -1) - .map(c => { - const labelText = c[this.valueName]; - let prefixText = c[this.labelPrefixName] || ''; - let suffixText = c[this.labelSuffixName] || ''; - - // when it's a complex object, then pull the object name only, e.g.: "user.firstName" => "user" - const fieldName = this.columnDef && this.columnDef.field; - - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - const serializeComplexValueFormat = this.columnEditor && this.columnEditor.serializeComplexValueFormat || 'object'; - - if (isComplexObject && typeof c === 'object' && serializeComplexValueFormat === 'object') { - return c; - } - - // also translate prefix/suffix if enableTranslateLabel is true and text is a string - prefixText = (this.enableTranslateLabel && this._translate && prefixText && typeof prefixText === 'string') ? this._translate.instant(prefixText || ' ') : prefixText; - suffixText = (this.enableTranslateLabel && this._translate && suffixText && typeof suffixText === 'string') ? this._translate.instant(suffixText || ' ') : suffixText; - - if (isIncludingPrefixSuffix) { - const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text); // add to a temp array for joining purpose and filter out empty text - return tmpOptionArray.join(separatorBetweenLabels); - } - return labelText; - }); - } - - /** - * The current selected values (single select) from the collection - */ - get currentValue() { - const elmValue = this.$editorElm.val(); - - // collection of strings, just return the filtered string that are equals - if (this.collection.every(x => typeof x === 'string')) { - return findOrDefault(this.collection, (c: any) => c.toString() === elmValue); - } - - // collection of label/value pair - const separatorBetweenLabels = this.collectionOptions && this.collectionOptions.separatorBetweenTextLabels || ''; - const isIncludingPrefixSuffix = this.collectionOptions && this.collectionOptions.includePrefixSuffixToSelectedValues || false; - const itemFound = findOrDefault(this.collection, (c: any) => c.hasOwnProperty(this.valueName) && c[this.valueName] !== null && c[this.valueName].toString() === elmValue); - - // is the field a complex object, "address.streetNumber" - const fieldName = this.columnDef && this.columnDef.field; - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - const serializeComplexValueFormat = this.columnEditor && this.columnEditor.serializeComplexValueFormat || 'object'; - - if (isComplexObject && typeof itemFound === 'object' && serializeComplexValueFormat === 'object') { - return itemFound; - } else if (itemFound && itemFound.hasOwnProperty(this.valueName)) { - const labelText = itemFound[this.valueName]; - - if (isIncludingPrefixSuffix) { - let prefixText = itemFound[this.labelPrefixName] || ''; - let suffixText = itemFound[this.labelSuffixName] || ''; - - // also translate prefix/suffix if enableTranslateLabel is true and text is a string - prefixText = (this.enableTranslateLabel && this._translate && prefixText && typeof prefixText === 'string') ? this._translate.instant(prefixText || ' ') : prefixText; - suffixText = (this.enableTranslateLabel && this._translate && suffixText && typeof suffixText === 'string') ? this._translate.instant(suffixText || ' ') : suffixText; - - // add to a temp array for joining purpose and filter out empty text - const tmpOptionArray = [prefixText, labelText, suffixText].filter((text) => text); - return tmpOptionArray.join(separatorBetweenLabels); - } - - return labelText; - } - - return ''; - } - - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor!.validator || this.columnDef.validator; - } - - init() { - if (!this.columnDef || !this.columnDef.internalColumnEditor || (!this.columnDef.internalColumnEditor.collection && !this.columnDef.internalColumnEditor.collectionAsync)) { - throw new Error(`[Angular-SlickGrid] You need to pass a "collection" (or "collectionAsync") inside Column Definition Editor for the MultipleSelect/SingleSelect Editor to work correctly. - Also each option should include a value/label pair (or value/labelKey when using Locale). - For example: { editor: { collection: [{ value: true, label: 'True' },{ value: false, label: 'False'}] } }`); - } - - this._collectionService = new CollectionService(this._translate); - this.enableTranslateLabel = (this.columnDef.internalColumnEditor.enableTranslateLabel) ? this.columnDef.internalColumnEditor.enableTranslateLabel : false; - this.labelName = this.customStructure && this.customStructure.label || 'label'; - this.labelPrefixName = this.customStructure && this.customStructure.labelPrefix || 'labelPrefix'; - this.labelSuffixName = this.customStructure && this.customStructure.labelSuffix || 'labelSuffix'; - this.optionLabel = this.customStructure && this.customStructure.optionLabel || 'value'; - this.valueName = this.customStructure && this.customStructure.value || 'value'; - - if (this.enableTranslateLabel && (!this._translate || typeof this._translate.instant !== 'function')) { - throw new Error('[Angular-Slickgrid] requires "ngx-translate" to be installed and configured when the grid option "enableTranslate" is enabled.'); - } - - // always render the Select (dropdown) DOM element, even if user passed a "collectionAsync", - // if that is the case, the Select will simply be without any options but we still have to render it (else SlickGrid would throw an error) - this.renderDomElement(this.collection); - } - - getValue(): any | any[] { - return (this.isMultipleSelect) ? this.currentValues : this.currentValue; - } - - setValue(value: any | any[]) { - if (this.isMultipleSelect && Array.isArray(value)) { - this.loadMultipleValues(value); - } else { - this.loadSingleValue(value); - } - } - - hide() { - if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') { - this.$editorElm.multipleSelect('close'); - } - } - - show() { - if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') { - this.$editorElm.multipleSelect('open'); - } - } - - applyValue(item: any, state: any): void { - const fieldName = this.columnDef && this.columnDef.field; - const fieldType = this.columnDef && this.columnDef.type; - let newValue = state; - - // when the provided user defined the column field type as a possible number then try parsing the state value as that - if (fieldType === FieldType.number || fieldType === FieldType.integer || fieldType === FieldType.boolean) { - newValue = parseFloat(state); - } - - // when set as a multiple selection, we can assume that the 3rd party lib multiple-select will return a CSV string - // we need to re-split that into an array to be the same as the original column - if (this.isMultipleSelect && typeof state === 'string' && state.indexOf(',') >= 0) { - newValue = state.split(','); - } - - // is the field a complex object, "user.address.streetNumber" - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - - // validate the value before applying it (if not valid we'll set an empty string) - const validation = this.validate(newValue); - newValue = (validation && validation.valid) ? newValue : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor && this.columnEditor.complexObjectPath || fieldName; - setDeepValue(item, objectPath, newValue); - } else { - item[fieldName] = newValue; - } - } - - destroy() { - // when autoCommitEdit is enabled, we might end up leaving an editor without it being saved, if so do call a save before destroying - // this mainly happens doing a blur or focusing on another cell in the grid (it won't come here if we click outside the grid, in the body) - if (this.$editorElm && this.hasAutoCommitEdit && this.isValueChanged() && !this._isDisposing) { - this._isDisposing = true; // change destroying flag to avoid infinite loop - this.save(true); - } - - this._isDisposing = true; - if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') { - this.$editorElm.multipleSelect('destroy'); - this.$editorElm.remove(); - const elementClassName = this.elementName.toString().replace('.', '\\.'); // make sure to escape any dot "." from CSS class to avoid console error - $(`[name=${elementClassName}].ms-drop`).remove(); - this.$editorElm.remove(); - this.$editorElm = null; - } - } - - loadValue(item: any): void { - const fieldName = this.columnDef && this.columnDef.field; - - if (item && fieldName !== undefined) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName && fieldName.indexOf('.') > 0; - - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor && this.columnEditor.complexObjectPath || fieldName; - const currentValue = (isComplexObject) ? getDescendantProperty(item, objectPath) : item[fieldName]; - const value = (isComplexObject && currentValue && currentValue.hasOwnProperty(this.valueName)) ? currentValue[this.valueName] : currentValue; - - if (this.isMultipleSelect && Array.isArray(value)) { - this.loadMultipleValues(value); - } else { - this.loadSingleValue(value); - } - this.refresh(); - } - } - - loadMultipleValues(currentValues: any[]) { - // convert to string because that is how the DOM will return these values - if (Array.isArray(currentValues)) { - // keep the default values in memory for references - this.originalValue = currentValues.map((i: any) => i); - - // compare all the array values but as string type since multiple-select always return string - const currentStringValues = currentValues.map((i: any) => i.toString()); - this.$editorElm.find('option').each((i: number, $e: any) => { - $e.selected = (currentStringValues.indexOf($e.value) !== -1); - }); - } - } - - loadSingleValue(currentValue: any) { - // keep the default value in memory for references - this.originalValue = typeof currentValue === 'number' ? `${currentValue}` : currentValue; - this.$editorElm.val(currentValue); - - // make sure the prop exists first - this.$editorElm.find('option').each((i: number, $e: any) => { - // check equality after converting originalValue to string since the DOM value will always be of type string - $e.selected = (`${currentValue}` === $e.value); - }); - } - - save(forceCommitCurrentEdit = false) { - const validation = this.validate(); - const isValid = validation?.valid ?? false; - - if ((!this._isDisposing || forceCommitCurrentEdit) && this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue(): any | any[] { - return (this.isMultipleSelect) ? this.currentValues : this.currentValue; - } - - focus() { - if (this.$editorElm && this.$editorElm.multipleSelect) { - this.$editorElm.multipleSelect('focus'); - } - } - - isValueChanged(): boolean { - const valueSelection = this.$editorElm?.multipleSelect('getSelects'); - if (this.isMultipleSelect) { - const isEqual = dequal(valueSelection, this.originalValue); - return !isEqual; - } - const value = Array.isArray(valueSelection) && valueSelection.length > 0 ? valueSelection[0] : undefined; - return value !== undefined && value !== this.originalValue; - } - - validate(inputValue?: any): EditorValidatorOutput { - const isRequired = this.columnEditor!.required; - const elmValue = (inputValue !== undefined) ? inputValue : this.$editorElm && this.$editorElm.val && this.$editorElm.val(); - const errorMsg = this.columnEditor!.errorMessage; - - if (this.validator) { - const value = (inputValue !== undefined) ? inputValue : (this.isMultipleSelect ? this.currentValues : this.currentValue); - return this.validator(value, this.args); - } - - // by default the editor is almost always valid (except when it's required but not provided) - if (isRequired && (elmValue === '' || (Array.isArray(elmValue) && elmValue.length === 0))) { - return { - valid: false, - msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD - }; - } - - return { - valid: true, - msg: null - }; - } - - // - // protected functions - // ------------------ - - /** - * user might want to filter certain items of the collection - * @param inputCollection - * @return outputCollection filtered and/or sorted collection - */ - protected filterCollection(inputCollection: any[]) { - let outputCollection = inputCollection; - - // user might want to filter certain items of the collection - if (this.columnEditor && this.columnEditor.collectionFilterBy) { - const filterBy = this.columnEditor.collectionFilterBy; - const filterCollectionBy = this.columnEditor.collectionOptions && this.columnEditor.collectionOptions.filterResultAfterEachPass || null; - outputCollection = this._collectionService.filterCollection(outputCollection, filterBy, filterCollectionBy); - } - - return outputCollection; - } - - /** - * user might want to sort the collection in a certain way - * @param inputCollection - * @return outputCollection sorted collection - */ - protected sortCollection(inputCollection: any[]) { - let outputCollection = inputCollection; - - // user might want to sort the collection - if (this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.collectionSortBy) { - const sortBy = this.columnDef.internalColumnEditor.collectionSortBy; - outputCollection = this._collectionService.sortCollection(this.columnDef, outputCollection, sortBy, this.enableTranslateLabel); - } - - return outputCollection; - } - - protected renderDomElement(inputCollection: any[]) { - if (!Array.isArray(inputCollection) && this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) { - const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty; - inputCollection = getDescendantProperty(inputCollection, collectionInsideObjectProperty as string); - } - if (!Array.isArray(inputCollection)) { - throw new Error('The "collection" passed to the Select Editor is not a valid array.'); - } - - // make a copy of the collection so that we don't impact SelectFilter, this could happen when calling "addBlankEntry" or "addCustomFirstEntry" - let collection: any[] = []; - if (inputCollection.length > 0) { - collection = [...inputCollection]; - } - - // user can optionally add a blank entry at the beginning of the collection - // make sure however that it wasn't added more than once - if (this.collectionOptions && this.collectionOptions.addBlankEntry && Array.isArray(collection) && collection.length > 0 && collection[0][this.valueName] !== '') { - collection.unshift(this.createBlankEntry()); - } - - // user can optionally add his own custom entry at the beginning of the collection - if (this.collectionOptions && this.collectionOptions.addCustomFirstEntry && Array.isArray(collection) && collection.length > 0 && collection[0][this.valueName] !== this.collectionOptions.addCustomFirstEntry[this.valueName]) { - collection.unshift(this.collectionOptions && this.collectionOptions.addCustomFirstEntry); - } - - // user can optionally add his own custom entry at the end of the collection - if (this.collectionOptions && this.collectionOptions.addCustomLastEntry && Array.isArray(collection) && collection.length > 0) { - const lastCollectionIndex = collection.length - 1; - if (collection[lastCollectionIndex][this.valueName] !== this.collectionOptions.addCustomLastEntry[this.valueName]) { - collection.push(this.collectionOptions && this.collectionOptions.addCustomLastEntry); - } - } - - let newCollection = collection || []; - - // user might want to filter and/or sort certain items of the collection - newCollection = this.filterCollection(newCollection); - newCollection = this.sortCollection(newCollection); - - // user could also override the collection - if (this.columnEditor && this.columnEditor.collectionOverride) { - newCollection = this.columnEditor.collectionOverride(newCollection, { column: this.columnDef, dataContext: this.args.item, grid: this.grid }); - } - - // step 1, create HTML string template - const selectBuildResult = buildSelectEditorOrFilterDomElement( - 'editor', - newCollection, - this.columnDef, - this.grid, - this.isMultipleSelect, - this._translate - ); - - // step 2, create the DOM Element of the editor - // we will later also subscribe to the onClose event to save the Editor whenever that event is triggered - this.createDomElement(selectBuildResult.selectElement); - } - - /** Create a blank entry that can be added to the collection. It will also reuse the same customStructure if need be */ - protected createBlankEntry() { - const blankEntry = { - [this.labelName]: '', - [this.valueName]: '' - }; - if (this.labelPrefixName) { - blankEntry[this.labelPrefixName] = ''; - } - if (this.labelSuffixName) { - blankEntry[this.labelSuffixName] = ''; - } - return blankEntry; - } - - /** - * From the Select DOM Element created earlier, create a Multiple/Single Select Editor using the jQuery multiple-select.js lib - * @param {Object} selectElement - */ - protected createDomElement(selectElement: HTMLSelectElement) { - this.$editorElm = $(selectElement); - - if (this.$editorElm && typeof this.$editorElm.appendTo === 'function') { - this.$editorElm.appendTo(this.args.container); - } - - // add placeholder when found - const placeholder = this.columnEditor && this.columnEditor.placeholder || ''; - this.defaultOptions.placeholder = placeholder || ''; - - if (typeof this.$editorElm.multipleSelect === 'function') { - const elementOptions = (this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.elementOptions : {}; - const editorOptions = (this.columnDef && this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.editorOptions : {}; - this.editorElmOptions = { ...this.defaultOptions, ...elementOptions, ...editorOptions }; - this.$editorElm = this.$editorElm.multipleSelect(this.editorElmOptions); - setTimeout(() => this.show()); - } - } - - // refresh the jquery object because the selected checkboxes were already set - // prior to this method being called - protected refresh() { - if (typeof this.$editorElm.multipleSelect === 'function') { - this.$editorElm.multipleSelect('refresh'); - } - } -} diff --git a/src/app/modules/angular-slickgrid/editors/singleSelectEditor.ts b/src/app/modules/angular-slickgrid/editors/singleSelectEditor.ts deleted file mode 100644 index d9abcae7f..000000000 --- a/src/app/modules/angular-slickgrid/editors/singleSelectEditor.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SelectEditor } from './selectEditor'; - -export class SingleSelectEditor extends SelectEditor { - /** - * Initialize the Editor - */ - constructor(protected args: any) { - super(args, false); - } -} diff --git a/src/app/modules/angular-slickgrid/editors/sliderEditor.ts b/src/app/modules/angular-slickgrid/editors/sliderEditor.ts deleted file mode 100644 index 7ca8a2095..000000000 --- a/src/app/modules/angular-slickgrid/editors/sliderEditor.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { Column, Editor, EditorArguments, EditorValidator, EditorValidatorOutput, ColumnEditor, SlickGrid, GridOption } from './../models/index'; -import { getDescendantProperty, setDeepValue } from '../services/utilities'; -import { sliderValidator } from '../editorValidators/sliderValidator'; -import { BindingEventService } from '../services/bindingEvent.service'; - -// using external non-typed js libraries -declare const $: any; - -const DEFAULT_MIN_VALUE = 0; -const DEFAULT_MAX_VALUE = 100; -const DEFAULT_STEP = 1; - -export class SliderEditor implements Editor { - protected _bindEventService: BindingEventService; - protected _defaultValue = 0; - protected _elementRangeInputId = ''; - protected _elementRangeOutputId = ''; - protected _editorElm!: HTMLDivElement; - protected _inputElm!: HTMLInputElement; - originalValue?: number | string; - sliderNumberElm: HTMLSpanElement | null = null; - - /** SlickGrid Grid object */ - grid: SlickGrid; - - /** Grid options */ - gridOptions: GridOption; - - constructor(protected readonly args: EditorArguments) { - if (!args) { - throw new Error('[Angular-SlickGrid] Something is wrong with this grid, an Editor must always have valid arguments.'); - } - this.grid = args.grid; - this.gridOptions = (this.grid.getOptions() || {}) as GridOption; - this._bindEventService = new BindingEventService(); - this.init(); - } - - /** Get Column Definition object */ - get columnDef(): Column { - return this.args.column; - } - - /** Get Column Editor object */ - get columnEditor(): ColumnEditor { - return this.columnDef && this.columnDef.internalColumnEditor || {}; - } - - /** Getter for the Editor DOM Element */ - get editorDomElement(): HTMLDivElement { - return this._editorElm; - } - - /** Getter for the Editor Input DOM Element */ - get editorInputDomElement(): HTMLInputElement { - return this._inputElm; - } - - get hasAutoCommitEdit(): boolean { - return this.gridOptions.autoCommitEdit ?? false; - } - - /** Getter for the Editor Generic Params */ - protected get editorParams(): any { - return this.columnEditor.params || {}; - } - - /** Get the Validator function, can be passed in Editor property or Column Definition */ - get validator(): EditorValidator | undefined { - return this.columnEditor?.validator ?? this.columnDef?.validator; - } - - init(): void { - const container = this.args && this.args.container; - - if (container && this.columnDef) { - // define the input & slider number IDs - const itemId = this.args?.item?.id ?? ''; - this._elementRangeInputId = `rangeInput_${this.columnDef.id}_${itemId}`; - this._elementRangeOutputId = `rangeOutput_${this.columnDef.id}_${itemId}`; - - // create HTML string template - this._editorElm = this.buildDomElement(); - this._inputElm = this._editorElm.querySelector('input') as HTMLInputElement; - this.sliderNumberElm = this._editorElm.querySelector(`span.input-group-text.${this._elementRangeOutputId}`); - - this.focus(); - - // watch on change event - container.appendChild(this._editorElm); - this._bindEventService.bind(this._editorElm, ['change', 'mouseup', 'touchend'], () => this.save()); - - // if user chose to display the slider number on the right side, then update it every time it changes - // we need to use both "input" and "change" event to be all cross-browser - if (!this.editorParams.hideSliderNumber) { - this._bindEventService.bind(this._editorElm, ['input', 'change'], this.handleChangeSliderNumber.bind(this)); - } - } - } - - cancel() { - if (this._inputElm) { - this._inputElm.value = `${this.originalValue}`; - } - this.args.cancelChanges(); - } - - destroy() { - this._bindEventService.unbindAll(); - this._inputElm?.remove?.(); - } - - focus() { - if (this._inputElm) { - this._inputElm.focus(); - } - } - - getValue(): string { - return this._inputElm?.value ?? ''; - } - - setValue(value: number | string) { - if (this._inputElm) { - this._inputElm.value = `${value}`; - } - if (this.sliderNumberElm) { - this.sliderNumberElm.textContent = `${value}`; - } - } - - applyValue(item: any, state: any) { - const fieldName = this.columnDef?.field ?? ''; - if (fieldName !== undefined) { - const isComplexObject = fieldName?.indexOf('.') > 0; // is the field a complex object, "address.streetNumber" - - const validation = this.validate(state); - const newValue = (validation && validation.valid) ? state : ''; - - // set the new value to the item datacontext - if (isComplexObject) { - // when it's a complex object, user could override the object path (where the editable object is located) - // else we use the path provided in the Field Column Definition - const objectPath = this.columnEditor?.complexObjectPath ?? fieldName ?? ''; - setDeepValue(item, objectPath, newValue); - } else if (item) { - item[fieldName] = newValue; - } - } - } - - isValueChanged(): boolean { - const elmValue = this._inputElm?.value ?? ''; - return (!(elmValue === '' && this.originalValue === undefined)) && (+elmValue !== this.originalValue); - } - - loadValue(item: any) { - const fieldName = this.columnDef?.field ?? ''; - - - if (item && fieldName !== undefined) { - // is the field a complex object, "address.streetNumber" - const isComplexObject = fieldName?.indexOf('.') > 0; - let value = (isComplexObject) ? getDescendantProperty(item, fieldName) : (item.hasOwnProperty(fieldName) ? item[fieldName] : this._defaultValue); - - if (value === '' || value === null || value === undefined) { - value = this._defaultValue; // load default value when item doesn't have any value - } - this.originalValue = +value; - if (this._inputElm) { - this._inputElm.value = `${value}`; - } - if (this.sliderNumberElm) { - this.sliderNumberElm.textContent = `${value}`; - } - } - } - - save() { - const validation = this.validate(); - const isValid = (validation && validation.valid) || false; - - if (this.hasAutoCommitEdit && isValid) { - // do not use args.commitChanges() as this sets the focus to the next row. - // also the select list will stay shown when clicking off the grid - this.grid.getEditorLock().commitCurrentEdit(); - } else { - this.args.commitChanges(); - } - } - - serializeValue() { - const elmValue: string = this._inputElm?.value ?? ''; - return elmValue !== '' ? parseInt(elmValue, 10) : this.originalValue; - } - - validate(inputValue?: any): EditorValidatorOutput { - const elmValue = (inputValue !== undefined) ? inputValue : this._inputElm?.value; - return sliderValidator(elmValue, { - editorArgs: this.args, - errorMessage: this.columnEditor.errorMessage, - minValue: this.columnEditor.minValue, - maxValue: this.columnEditor.maxValue, - required: this.columnEditor.required, - validator: this.validator, - }); - } - - // - // protected functions - // ------------------ - - /** - * Create the HTML template as a string - */ - protected buildDomElement(): HTMLDivElement { - const columnId = this.columnDef?.id ?? ''; - const title = this.columnEditor && this.columnEditor.title || ''; - const minValue = this.columnEditor.hasOwnProperty('minValue') ? this.columnEditor.minValue : DEFAULT_MIN_VALUE; - const maxValue = this.columnEditor.hasOwnProperty('maxValue') ? this.columnEditor.maxValue : DEFAULT_MAX_VALUE; - const defaultValue = this.editorParams.hasOwnProperty('sliderStartValue') ? this.editorParams.sliderStartValue : minValue; - const step = this.columnEditor.hasOwnProperty('valueStep') ? this.columnEditor.valueStep : DEFAULT_STEP; - this._defaultValue = defaultValue; - - const inputElm = document.createElement('input'); - inputElm.name = this._elementRangeInputId; - inputElm.title = title; - inputElm.type = 'range'; - inputElm.defaultValue = defaultValue; - inputElm.value = defaultValue; - inputElm.min = `${minValue}`; - inputElm.max = `${maxValue}`; - inputElm.step = `${step}`; - inputElm.className = `form-control slider-editor-input editor-${columnId} range ${this._elementRangeInputId}`; - - const divContainerElm = document.createElement('div'); - divContainerElm.className = 'slider-container slider-editor'; - divContainerElm.appendChild(inputElm); - - if (!this.editorParams.hideSliderNumber) { - divContainerElm.classList.add('input-group'); - - //
    ${defaultValue}
    - const spanGroupElm = document.createElement('span'); - spanGroupElm.className = `input-group-text ${this._elementRangeOutputId}`; - spanGroupElm.textContent = `${defaultValue}`; - - const divGroupAddonElm = document.createElement('div'); - divGroupAddonElm.className = 'input-group-addon input-group-append slider-value'; - divGroupAddonElm.appendChild(spanGroupElm); - divContainerElm.appendChild(divGroupAddonElm); - } - - return divContainerElm; - } - - protected handleChangeSliderNumber(event: Event) { - const value = (event.target)?.value ?? ''; - if (value !== '' && this.sliderNumberElm) { - this.sliderNumberElm.textContent = value; - } - } -} diff --git a/src/app/modules/angular-slickgrid/index.ts b/src/app/modules/angular-slickgrid/index.ts index 286d9f408..2891822a2 100644 --- a/src/app/modules/angular-slickgrid/index.ts +++ b/src/app/modules/angular-slickgrid/index.ts @@ -1,5 +1,6 @@ export { Aggregators, + Editors, Formatters, GroupTotalFormatters, getValueFromParamsOrFormatterOptions, @@ -12,8 +13,8 @@ export { export * from './models/index'; export * from './services/index'; // export * from './aggregators/index'; -export * from './editors/index'; -export * from './editors/editors.index'; +// export * from './editors/index'; +// export * from './editors/editors.index'; export * from './extensions/index'; export * from './filter-conditions/index'; export * from './filters/index';