diff --git a/eclipse-scout-core/src/form/fields/smartfield/ProposalField.js b/eclipse-scout-core/src/form/fields/smartfield/ProposalField.js index d0dc634c5ab..0bcf975c73f 100644 --- a/eclipse-scout-core/src/form/fields/smartfield/ProposalField.js +++ b/eclipse-scout-core/src/form/fields/smartfield/ProposalField.js @@ -1,9 +1,9 @@ /* - * Copyright (c) 2014-2018 BSI Business Systems Integration AG. + * Copyright (c) 2010-2024 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * https://www.eclipse.org/legal/epl-v10.html * * Contributors: * BSI Business Systems Integration AG - initial API and implementation @@ -163,6 +163,7 @@ export default class ProposalField extends SmartField { if (this.lookupRow !== otherField.lookupRow) { this.setLookupRow(otherField.lookupRow); } + this.setErrorStatus(otherField.errorStatus); if (this.value !== otherField.value) { this.setValue(otherField.value); } diff --git a/eclipse-scout-core/src/testing/form/fields/smartfield/proposalFieldSpecHelper.js b/eclipse-scout-core/src/testing/form/fields/smartfield/proposalFieldSpecHelper.js new file mode 100644 index 00000000000..336757dc1d7 --- /dev/null +++ b/eclipse-scout-core/src/testing/form/fields/smartfield/proposalFieldSpecHelper.js @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2024 BSI Business Systems Integration AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ + +import {ProposalField} from '../../../../index'; + +export async function testProposalFieldInputs(field, inputs, touchMode, callbacks) { + field.touchMode = touchMode; + field.render(); + + callbacks = $.extend({ + beforeInput: (input) => { + }, + afterInput: (input) => { + }, + afterSelectLookupRow: (text, lookupRow) => { + }, + afterAcceptCustomText: (text) => { + } + }, callbacks); + + for (const inputOrText of inputs) { + const input = ensureInput(inputOrText); + const {text, lookup} = input; + + callbacks.beforeInput(input); + + if (lookup) { + const lookupRow = await selectLookupRow(field, text); + callbacks.afterSelectLookupRow(text, lookupRow); + } else { + await acceptCustomText(field, text); + callbacks.afterAcceptCustomText(text); + } + + callbacks.afterInput(input); + } +} + +export function ensureInput(inputOrText) { + return typeof inputOrText === 'string' ? {text: inputOrText} : inputOrText; +} + +export async function acceptCustomText(field, text) { + if (field.touchMode) { + // touchMode opens a popup with a field and a done-menu + const popup = await openPopup(field); + popup._field.$field.val(text); + popup._field.acceptInput(); + popup.doneAction.doAction(); + } else { + field.$field.val(text); + field.acceptInput(); + } +} + +export async function selectLookupRow(field, text) { + // find row for text + const popup = await openPopup(field); + const proposalChooser = field.touchMode ? popup._widget : popup.proposalChooser; + const table = proposalChooser.model; + const row = table.rows.find(r => r.cells[0].text === text); + + // trigger row mousedown and mouseup + row.$row.triggerMouseDown(); + row.$row.triggerMouseUp(); + + return row.lookupRow; +} + +export async function openPopup(field) { + field.$field.focus(); + await field.openPopup(true); + const popup = field.popup; + popup.animateRemoval = false; + return popup; +} + +export function createSpecProposalField() { + return new SpecProposalField(); +} + +class SpecProposalField extends ProposalField { + acceptInput(sync) { + this._acceptInputEnabled = true; // accept all inputs, no need for a timeout + return super.acceptInput(sync); + } +} + +export default { + testProposalFieldInputs, + ensureInput, + acceptCustomText, + selectLookupRow, + openPopup, + createSpecProposalField +} diff --git a/eclipse-scout-core/src/testing/index.js b/eclipse-scout-core/src/testing/index.js index 17eeba942ae..f54fadee7b9 100644 --- a/eclipse-scout-core/src/testing/index.js +++ b/eclipse-scout-core/src/testing/index.js @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2020 BSI Business Systems Integration AG. + * Copyright (c) 2010-2024 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html + * https://www.eclipse.org/legal/epl-v10.html * * Contributors: * BSI Business Systems Integration AG - initial API and implementation @@ -23,6 +23,7 @@ export {default as TableSpecHelper} from './table/TableSpecHelper'; export {default as FormSpecHelper} from './form/FormSpecHelper'; export {default as CloneSpecHelper} from './form/fields/CloneSpecHelper'; export {default as TestBeanField} from './form/fields/beanfield/TestBeanField'; +export {default as proposalFieldSpecHelper} from './form/fields/smartfield/proposalFieldSpecHelper'; export {default as TabBoxSpecHelper} from './form/fields/tabbox/TabBoxSpecHelper'; export {default as OutlineSpecHelper} from './desktop/outline/OutlineSpecHelper'; export {default as DummyLookupCall} from './lookup/DummyLookupCall'; diff --git a/eclipse-scout-core/test/form/fields/smartfield/ProposalFieldAdapterSpec.js b/eclipse-scout-core/test/form/fields/smartfield/ProposalFieldAdapterSpec.js new file mode 100644 index 00000000000..30b87510bad --- /dev/null +++ b/eclipse-scout-core/test/form/fields/smartfield/ProposalFieldAdapterSpec.js @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2010-2024 BSI Business Systems Integration AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ +import {ObjectFactory, scout, Status} from '../../../../src/index'; +import {proposalFieldSpecHelper} from '../../../../src/testing'; + +describe('ProposalFieldAdapter', () => { + + let session, field, modelAdapter; + + beforeEach(() => { + setFixtures(sandbox()); + session = sandboxSession(); + ObjectFactory.get().register('SpecProposalField', () => proposalFieldSpecHelper.createSpecProposalField()); + + field = scout.create('SpecProposalField', {parent: session.desktop}); + linkWidgetAndAdapter(field, 'ProposalFieldAdapter'); + modelAdapter = field.modelAdapter; + field.setLookupCall(scout.create('StaticLookupCall', { + session: field.session, + data: [ + ['foo', 'foo'], + ['bar', 'bar'] + ] + })); + }); + + afterEach(() => { + ObjectFactory.get().unregister('SpecProposalField'); + }); + + describe('acceptInput-event contains correct data', () => { + let spy; + + beforeEach(() => { + spy = spyOn(modelAdapter, '_send'); + }); + + // some > foo > bar + + it('write \'some\' > write \'foo\' > write \'bar\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['some', 'foo', 'bar'], + false, + false)); + + it('write \'some\' > write \'foo\' > write \'bar\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['some', 'foo', 'bar'], + true, + false)); + + it('write \'some\' > write \'foo\' > write \'bar\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['some', 'foo', 'bar'], + false, + true)); + + it('write \'some\' > write \'foo\' > write \'bar\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['some', 'foo', 'bar'], + true, + true)); + + // foo > some > bar + + it('write \'foo\' > write \'some\' > write \'bar\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'some', 'bar'], + false, + false)); + + it('write \'foo\' > write \'some\' > write \'bar\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'some', 'bar'], + true, + false)); + + it('write \'foo\' > write \'some\' > write \'bar\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'some', 'bar'], + false, + true)); + + it('write \'foo\' > write \'some\' > write \'bar\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'some', 'bar'], + true, + true)); + + // foo > bar > some + + it('write \'foo\' > write \'bar\' > write \'some\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'bar', 'some'], + false, + false)); + + it('write \'foo\' > write \'bar\' > write \'some\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'bar', 'some'], + true, + false)); + + it('write \'foo\' > write \'bar\' > write \'some\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'bar', 'some'], + false, + true)); + + it('write \'foo\' > write \'bar\' > write \'some\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'bar', 'some'], + true, + true)); + + // foo > bar > some > thing + + it('lookup \'foo\' > lookup \'bar\' > write \'some\' > write \'thing\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'some', 'thing'], + false, + false)); + + it('lookup \'foo\' > lookup \'bar\' > write \'some\' > write \'thing\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'some', 'thing'], + true, + false)); + + it('lookup \'foo\' > lookup \'bar\' > write \'some\' > write \'thing\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'some', 'thing'], + false, + true)); + + it('lookup \'foo\' > lookup \'bar\' > write \'some\' > write \'thing\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'some', 'thing'], + true, + true)); + + // some > thing > foo > bar + + it('write \'some\' > write \'thing\' > lookup \'foo\' > lookup \'bar\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['some', 'thing', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + false, + false)); + + it('write \'some\' > write \'thing\' > lookup \'foo\' > lookup \'bar\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['some', 'thing', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + true, + false)); + + it('write \'some\' > write \'thing\' > lookup \'foo\' > lookup \'bar\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['some', 'thing', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + false, + true)); + + it('write \'some\' > write \'thing\' > lookup \'foo\' > lookup \'bar\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['some', 'thing', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + true, + true)); + + // foo > bar > foo > bar + + it('lookup \'foo\' > lookup \'bar\' > write \'foo\' > write \'bar\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'foo', 'bar'], + false, + false)); + + it('lookup \'foo\' > lookup \'bar\' > write \'foo\' > write \'bar\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'foo', 'bar'], + true, + false)); + + it('lookup \'foo\' > lookup \'bar\' > write \'foo\' > write \'bar\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'foo', 'bar'], + false, + true)); + + it('lookup \'foo\' > lookup \'bar\' > write \'foo\' > write \'bar\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + [{text: 'foo', lookup: true}, {text: 'bar', lookup: true}, 'foo', 'bar'], + true, + true)); + + it('write \'foo\' > write \'bar\' > lookup \'foo\' > lookup \'bar\' (touchMode: false, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'bar', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + false, + false)); + + it('write \'foo\' > write \'bar\' > lookup \'foo\' > lookup \'bar\' (touchMode: true, withErrorStatus: false)', async () => + await testProposalFieldInputs( + ['foo', 'bar', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + true, + false)); + + it('write \'foo\' > write \'bar\' > lookup \'foo\' > lookup \'bar\' (touchMode: false, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'bar', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + false, + true)); + + it('write \'foo\' > write \'bar\' > lookup \'foo\' > lookup \'bar\' (touchMode: true, withErrorStatus: true)', async () => + await testProposalFieldInputs( + ['foo', 'bar', {text: 'foo', lookup: true}, {text: 'bar', lookup: true}], + true, + true)); + + async function testProposalFieldInputs(inputs, touchMode, withErrorStatus) { + const callbacks = { + afterInput: (input) => { + const {text} = input; + expect(field.value).toBe(text); + expect(field.errorStatus).toBeNull(); + }, + afterSelectLookupRow: (text, lookupRow) => expectAcceptInputEvent(text, lookupRow), + afterAcceptCustomText: (text) => expectAcceptInputEvent(text) + }; + + if (withErrorStatus) { + callbacks.beforeInput = (input) => field.setErrorStatus(Status.warning('I am a WARNING!')); + } + + await proposalFieldSpecHelper.testProposalFieldInputs(field, inputs, touchMode, callbacks); + } + + function expectAcceptInputEvent(text, lookupRow) { + const displayText = text; + const value = text; + let eventData = {displayText, value, errorStatus: null}; + if (lookupRow) { + eventData = {...eventData, lookupRow}; + } + expect(spy).toHaveBeenCalledWith('acceptInput', jasmine.objectContaining(eventData), jasmine.anything()); + } + }); +});