(core) Fixing DELETE and BACKSPACE keys on ChoiceList and RefList editor.

Summary:
Choice/Reference List editor wasn't clearing itself when it received an empty string. It led
to a bug on the Card widget where pressing those keys resulted in the same behavior as
pressing Enter - it just opened the editor.
Grid view has it's own implementation for those keys, so it wasn't affected.

Test Plan: Added new test.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3908
pull/526/head
Jarosław Sadziński 11 months ago
parent dad41b2567
commit c592691e31

@ -66,7 +66,7 @@ export class ChoiceListEditor extends NewBaseEditor {
// If starting to edit by typing in a string, ignore previous tokens.
const cellValue = decodeObject(options.cellValue);
const startLabels: unknown[] = options.editValue || !Array.isArray(cellValue) ? [] : cellValue;
const startLabels: unknown[] = options.editValue !== undefined || !Array.isArray(cellValue) ? [] : cellValue;
const startTokens = startLabels.map(label => new ChoiceItem(String(label), !choiceSet.has(String(label))));
this._tokenField = TokenField.ctor<ChoiceItem>().create(this, {

@ -73,7 +73,7 @@ export class ReferenceListEditor extends NewBaseEditor {
// If starting to edit by typing in a string, ignore previous tokens.
const cellValue = decodeObject(options.cellValue);
const startRowIds: unknown[] = options.editValue || !Array.isArray(cellValue) ? [] : cellValue;
const startRowIds: unknown[] = options.editValue !== undefined || !Array.isArray(cellValue) ? [] : cellValue;
// If referenced table hasn't loaded yet, hold off on initializing tokens.
const needReload = (options.editValue === undefined && !this._utils.tableData.isLoaded);

@ -1,5 +1,6 @@
import {arrayRepeat} from 'app/plugin/gutil';
import * as gu from 'test/nbrowser/gristUtils';
import {ColumnType} from 'test/nbrowser/gristUtils';
import {setupTestSuite} from 'test/nbrowser/testUtils';
import {UserAPIImpl} from 'app/common/UserAPI';
import {assert, driver, Key} from 'mocha-webdriver';
@ -9,8 +10,10 @@ let doc: string;
const transparent = 'rgba(0, 0, 0, 0)';
const blue = '#0000FF';
const red = '#FF0000';
const types = ['Any', 'Text', 'Integer', 'Numeric', 'Toggle', 'Date', 'DateTime', 'Choice', 'Choice List',
'Reference', 'Reference List', 'Attachment'];
const types: Array<ColumnType> = [
'Any', 'Text', 'Integer', 'Numeric', 'Toggle', 'Date', 'DateTime', 'Choice', 'Choice List',
'Reference', 'Reference List', 'Attachment'
];
describe('MultiColumn', function() {
this.timeout(80000);
@ -96,7 +99,7 @@ describe('MultiColumn', function() {
await gu.assertFillColor(await gu.getCell('Test2', 1), transparent);
});
for (const type of ['Choice', 'Text', 'Reference', 'Numeric']) {
for (const type of ['Choice', 'Text', 'Reference', 'Numeric'] as Array<ColumnType>) {
it(`should reset all columns to first column type for ${type}`, async () => {
// We start with empty columns, then we will change first one
// to a data column, select all and then change all other to the same type.

@ -0,0 +1,123 @@
import {assert, Key} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import {setupTestSuite} from 'test/nbrowser/testUtils';
describe('TokenField', function() {
this.timeout(20000);
const cleanup = setupTestSuite();
let session: gu.Session;
before(async function() {
session = await gu.session().login();
await session.tempDoc(cleanup, "Favorite_Films.grist");
await gu.toggleSidePanel('right', 'open');
// Prepare test table, as a base for following tests.
await gu.addNewPage('Table', 'New Table');
await gu.sendKeys('one', Key.ENTER);
await gu.waitForServer();
await gu.sendKeys('two', Key.ENTER);
await gu.waitForServer();
await gu.sendKeys('three', Key.ENTER);
await gu.waitForServer();
});
it('should clear choice list on card view', async function() {
// Test for a bug. In Card or Card List widgets, if the cursor is on a Ref List or Choice List field and you
// hit Backspace or Delete , the behavior is the same as hitting enter (pulls up list of references/choices to add
// one more, does not clear cell).
// The bug was there because choice list editor and ref list editor didn't handle empty string as an edit value,
// which is a signal to clear the value. In a grid view, DELETE and BACKSPACE were handled by the grid itself.
const revert = await gu.begin();
await gu.getCell('A', 1).click();
// Convert A column to Choice List.
await gu.openColumnPanel();
await gu.setType('Choice List', {apply: true});
// Add a second value to the first row.
await gu.getCell('A', 1).click();
await gu.sendKeys(Key.ENTER);
await gu.sendKeys('two', Key.ENTER);
await gu.sendKeys(Key.ENTER);
await gu.waitForServer();
await gu.getCell('A', 1).click();
assert.equal(await gu.getCell('A', 1).getText(), 'one\ntwo');
// Change it to card view.
await gu.changeWidget('Card');
// Test that DELETE opens the editor and clears the value.
// Clicking on the cell twice will put it in edit mode, so we will first click other cell.
await gu.getDetailCell('B', 1).click();
await gu.getDetailCell('A', 1).click();
await gu.sendKeys(Key.DELETE);
await gu.checkTokenEditor('');
await gu.sendKeys(Key.ESCAPE);
// Now test BACKSPACE.
await gu.getDetailCell('B', 1).click();
await gu.getDetailCell('A', 1).click();
await gu.sendKeys(Key.BACK_SPACE);
await gu.checkTokenEditor('');
await gu.sendKeys(Key.ESCAPE);
// Value should still be there.
assert.equal(await gu.getDetailCell('A', 1).getText(), 'one\ntwo');
// But ENTER works fine, it just opens the editor.
await gu.sendKeys(Key.ENTER);
await gu.checkTokenEditor('one\ntwo');
await gu.sendKeys(Key.ESCAPE);
// Any other key also works
await gu.sendKeys('a');
await gu.checkTokenEditor('a');
await gu.sendKeys(Key.ESCAPE);
await revert();
});
it('should clear ref list on card view', async function() {
await gu.getCell(0, 1).click();
await gu.changeBehavior('Clear and reset');
// This is an empty column, so no transformation is needed.
await gu.setType('Reference List', {apply: false});
await gu.waitForServer();
await gu.setRefTable('Films');
await gu.waitForServer();
await gu.setRefShowColumn('Title');
await gu.waitForServer();
// Add two films.
await gu.sendKeys(Key.ENTER);
await gu.sendKeys('Toy', Key.ENTER);
await gu.sendKeys('Alien', Key.ENTER);
// Save.
await gu.sendKeys(Key.ENTER);
await gu.waitForServer();
// Make sure it works in Grid view.
await gu.getCell(0, 1).click();
await gu.sendKeys(Key.DELETE);
await gu.waitForServer();
assert.equal(await gu.getCell(0, 1).getText(), '');
await gu.undo();
await gu.sendKeys(Key.BACK_SPACE);
await gu.waitForServer();
assert.equal(await gu.getCell(0, 1).getText(), '');
await gu.undo();
// Now make sure it works in Card view.
await gu.changeWidget('Card');
assert.equal(await gu.getDetailCell('A', 1).getText(), 'Toy Story\nAlien');
await gu.sendKeys(Key.DELETE);
await gu.checkTokenEditor('');
await gu.sendKeys(Key.ESCAPE);
await gu.sendKeys(Key.BACK_SPACE);
await gu.checkTokenEditor('');
await gu.sendKeys(Key.ESCAPE);
await gu.waitForServer();
// Nothing should have changed.
assert.equal(await gu.getDetailCell('A', 1).getText(), 'Toy Story\nAlien');
});
});

@ -654,6 +654,20 @@ export async function checkTextEditor(value: RegExp|string) {
assert.match(await driver.find('.celleditor_text_editor').value(), valueRe);
}
/**
* Checks that token editor in a cell has a correct value. Converts all tokens to text including the input field
* and joins them with newlines.
*/
export async function checkTokenEditor(value: RegExp|string) {
assert.equal(await driver.findWait('.test-widget-text-editor', 500).isDisplayed(), true);
const valueRe = typeof value === 'string' ? exactMatch(value) : value;
const allTokens = await driver.findAll(
'.test-widget-text-editor .test-tokenfield .test-tokenfield-token', e => e.getText());
const inputToken = await driver.find('.test-widget-text-editor .test-tokenfield .test-tokenfield-input').value();
const combined = [...allTokens, inputToken].join('\n').trim();
assert.match(combined, valueRe);
}
/**
* Enter rows of values into a GridView, starting at the given cell. Values are specified as a
* list of rows, for examples `[['foo'], ['bar']]` will enter two rows, with one value in each.
@ -1181,7 +1195,10 @@ export async function selectWidget(
await waitForServer();
}
export async function changeWidget(type: string) {
export type WidgetType = 'Table' | 'Card' | 'Card List' | 'Chart' | 'Custom';
export async function changeWidget(type: WidgetType) {
await openWidgetPanel();
await driver.findContent('.test-right-panel button', /Change Widget/).click();
await selectWidget(type);
@ -1406,6 +1423,7 @@ export async function waitForSidePanel() {
export async function openWidgetPanel() {
await toggleSidePanel('right', 'open');
await driver.find('.test-right-tab-pagewidget').click();
await driver.find(".test-config-widget").click();
}
/**
@ -1585,11 +1603,15 @@ export async function deleteColumn(col: IColHeader|string) {
await waitForServer();
}
export type ColumnType =
'Any' | 'Text' | 'Numeric' | 'Integer' | 'Toggle' | 'Date' | 'DateTime' |
'Choice' | 'Choice List' | 'Reference' | 'Reference List' | 'Attachment';
/**
* Sets the type of the currently selected field to value.
*/
export async function setType(
type: RegExp|string,
type: RegExp|ColumnType,
options: {skipWait?: boolean, apply?: boolean} = {}
) {
const {skipWait, apply} = options;
@ -2957,10 +2979,13 @@ export async function setWidgetUrl(url: string) {
await waitForServer();
}
type BehaviorActions = 'Clear and reset' | 'Convert column to data' | 'Clear and make into formula' |
'Convert columns to data';
/**
* Opens a behavior menu and clicks one of the option.
*/
export async function changeBehavior(option: string|RegExp) {
export async function changeBehavior(option: BehaviorActions|RegExp) {
await openColumnPanel();
await driver.find('.test-field-behaviour').click();
await driver.findContent('.grist-floating-menu li', option).click();
await waitForServer();

Loading…
Cancel
Save