Skip to content

Commit

Permalink
ProposalField: fix open and close touch popup multiple times
Browse files Browse the repository at this point in the history
If the touch popup of a SmartField or ProposalField is opened and closed
multiple times without changing the value, the properties value,
displayText, errorStatus and lookupRow of the corresponding field need
to stay unchanged. These properties are copied from the original field
to the field in the touch popup. Therefore, save the initial state of
these properties and compare them before closing the popup to detect
whether the popup was changed or not.
It is not sufficient to mark the field in the touch popup as saved and
check if it was touched before closing as there might be validators that
throw exceptions on some values. Consider two values 'ok' and 'error'
and a validator that throws an error if 'error' is selected. When
switching between these two the value of the ProposalField will always
be 'ok' but the displayText will change. Marking the field in the touch
popup as saved and checking if it was touched will only consider the
value which has not changed in this example and there would be no update
of the original field and the resulting errorStatus will be
inconsistent.

369873
  • Loading branch information
fschinkel committed Apr 5, 2024
1 parent 6f89d63 commit 2797744
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/*
* 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
*/
import {scout, TouchPopup} from '../../../index';
import {objects, scout, TouchPopup} from '../../../index';
import $ from 'jquery';

/**
* Info: this class must have the same interface as SmartFieldPopup. That's why there's some
Expand All @@ -18,12 +19,14 @@ export default class SmartFieldTouchPopup extends TouchPopup {

constructor() {
super();
this._initialFieldState = null;
}

_init(options) {
options.withFocusContext = false;
options.smartField = options.parent; // alias for parent (required by proposal chooser)
super._init(options);
this._initialFieldState = this._getFieldState();

this.setLookupResult(options.lookupResult);
this.setStatus(options.status);
Expand All @@ -42,6 +45,11 @@ export default class SmartFieldTouchPopup extends TouchPopup {
this._widget.on('activeFilterSelected', this._triggerEvent.bind(this));
}

_getFieldState() {
const {value, displayText, errorStatus, lookupRow} = this._field;
return $.extend(true, {}, {value, displayText, errorStatus, lookupRow});
}

_createProposalChooser() {
let objectType = this.parent.browseHierarchy ? 'TreeProposalChooser' : 'TableProposalChooser';
return scout.create(objectType, {
Expand Down Expand Up @@ -107,6 +115,9 @@ export default class SmartFieldTouchPopup extends TouchPopup {
}

_beforeClosePopup(event) {
if (objects.equalsRecursive(this._initialFieldState, this._getFieldState())) {
return;
}
this.smartField.acceptInputFromField(this._field);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,5 +250,74 @@ describe('ProposalFieldAdapter', () => {
}
expect(spy).toHaveBeenCalledWith('acceptInput', jasmine.objectContaining(eventData), jasmine.anything());
}

// select a value, then open and close the touch popup multiple times

it('write \'foo\' (touchMode: true, withErrorStatus: false, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
'foo',
false));

it('write \'foo\' (touchMode: true, withErrorStatus: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
'foo',
true));

it('write \'some\' (touchMode: true, withErrorStatus: false, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
'some',
false));

it('write \'some\' (touchMode: true, withErrorStatus: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
'some',
true));

it('lookup \'foo\' (touchMode: true, withErrorStatus: false, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
{text: 'foo', lookup: true},
false));

it('lookup \'foo\' (touchMode: true, withErrorStatus: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose(
{text: 'foo', lookup: true},
true));

async function testTouchProposalFieldOpenClose(inputOrText, withErrorStatus) {
const expectInput = (input) => {
const {text} = input;
expect(field.value).toBe(text);
if (withErrorStatus) {
expect(field.errorStatus).not.toBeNull();
} else {
expect(field.errorStatus).toBeNull();
}
};
const callbacks = {
afterInput: (input) => {
if (withErrorStatus) {
field.setErrorStatus(Status.warning('I am a WARNING!'));
}
expectInput(input);
},
afterSelectLookupRow: (text, lookupRow) => expectAcceptInputEvent(text, lookupRow),
afterAcceptCustomText: (text) => expectAcceptInputEvent(text)
};

const input = proposalFieldSpecHelper.ensureInput(inputOrText);

await proposalFieldSpecHelper.testProposalFieldInputs(field, [input], true, callbacks);

const callCount = spy.calls.count();

let i = 0;
while (i < 5) {
const popup = await proposalFieldSpecHelper.openPopup(field);
popup.doneAction.doAction();
expectInput(input);
expect(spy).toHaveBeenCalledTimes(callCount);
i++;
}
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -576,5 +576,87 @@ describe('ProposalField', () => {
}
});
}

// select a value, then open and close the touch popup multiple times

it('write \'ok\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose('ok'));

it('write \'warning\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose('warning'));

it('write \'throw\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose('throw'));

it('write \'no error\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose('no error'));

it('lookup \'ok\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose({text: 'ok', lookup: true}));

it('lookup \'warning\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose({text: 'warning', lookup: true}));

it('lookup \'throw\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose({text: 'throw', lookup: true}));

it('lookup \'no error\' (touchMode: true, open and close multiple times)', async () =>
await testTouchProposalFieldOpenClose({text: 'no error', lookup: true}));

async function testTouchProposalFieldOpenClose(inputOrText) {
const expectInput = (input) => {
const {text, lookup} = input;

// displayText always equals text
expect(field.displayText).toBe(text);

// value equals text iff there is no validation error (see validator)
if ('throw' === text) {
expect(field.value).not.toBe(text);
} else {
expect(field.value).toBe(text);
}

// correct errorStatus is set (see validator)
if ('ok' === text) {
expect(field.errorStatus).not.toBeNull();
expect(field.errorStatus.severity).toBe(Status.Severity.OK);
expect(field.errorStatus.message).toBe('This has severity OK.');
} else if ('warning' === text) {
expect(field.errorStatus).not.toBeNull();
expect(field.errorStatus.severity).toBe(Status.Severity.WARNING);
expect(field.errorStatus.message).toBe('This has severity WARNING.');
} else if ('throw' === text) {
expect(field.errorStatus).not.toBeNull();
expect(field.errorStatus.severity).toBe(Status.Severity.ERROR);
expect(field.errorStatus.message).toBe('This is an exception.');
} else {
expect(field.errorStatus).toBeNull();
}

// lookupRow is set and contains the correct values iff a lookupRow was selected
if (lookup) {
expect(field.lookupRow).not.toBeNull();
expect(field.lookupRow.text).toBe(text);
expect(field.lookupRow.key).toBe(text);
} else {
expect(field.lookupRow).toBeNull();
}
};

const input = proposalFieldSpecHelper.ensureInput(inputOrText);

await proposalFieldSpecHelper.testProposalFieldInputs(field, [input], true, {
afterInput: expectInput
});

let i = 0;
while (i < 5) {
const popup = await proposalFieldSpecHelper.openPopup(field);
popup.doneAction.doAction();
expectInput(input);
i++;
}
}
});
});

0 comments on commit 2797744

Please sign in to comment.