You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/test/nbrowser/TypeChange.ntest.js

466 lines
19 KiB

import { assert } from 'mocha-webdriver';
import { $, gu, test } from 'test/nbrowser/gristUtil-nbrowser';
// Helper that returns the cell text prefixed by "!" if the cell is invalid.
async function valText(cell) {
const isInvalid = await cell.find('.field_clip').hasClass("invalid");
const text = await cell.getText();
return (isInvalid ? "!" : "") + text;
}
describe('TypeChange.ntest', function() {
const cleanup = test.setupTestSuite(this);
before(async function() {
await gu.supportOldTimeyTestCode();
await gu.useFixtureDoc(cleanup, "Hello.grist", true);
});
afterEach(function() {
return gu.checkForErrors();
});
it('should not use transform to convert type for an empty column', async function() {
await gu.openSidePane('field');
await gu.getCellRC(0, 0).click();
await gu.sendKeys([$.ALT, '=']);
await gu.waitForServer();
gu.sendKeys($.ESCAPE);
// Click on new column
await gu.getCellRC(0, 1).click();
// Change type
await gu.userActionsCollect();
await gu.setType('Numeric');
await gu.userActionsVerify([["UpdateRecord", "_grist_Tables_column", 7, {"type": "Numeric"}]]);
// Errors should not be present in converted column
assert.isFalse(await gu.getCellRC(0, 1).find('.field_clip').hasClass('invalid'));
// Ensure that column transform is not occurring.
assert.isFalse(await $('.type_transform_prompt').isPresent());
});
it('should use transform to convert type for non-empty columns', async function() {
// Enter text into numeric column
await gu.getCellRC(0, 1).click();
await gu.sendKeys('one', $.ENTER);
await gu.waitForServer();
assert.hasClass(await gu.getCellRC(0, 1).find('.field_clip'), 'invalid', true);
// Change numeric to text
await gu.userActionsCollect();
await gu.setType('Text');
// Accept, check that column is text and has no errors
await gu.applyTypeConversion();
assert.hasClass(await gu.getCellRC(0, 1).find('.field_clip'), 'invalid', false);
await gu.userActionsVerify([
["AddColumn", "Table1", "gristHelper_Converted", { type: 'Any' }],
["AddColumn", "Table1", "gristHelper_Transform", { type: 'Any' }],
["ModifyColumn", "Table1", "gristHelper_Converted", {
"formula": "",
"isFormula": false,
"type": "Text",
"visibleCol": 0
}],
["ModifyColumn", "Table1", "gristHelper_Transform", {
"formula": "rec.gristHelper_Converted",
"isFormula": true,
"type": "Text",
"visibleCol": 0
}],
["ConvertFromColumn", "Table1", "F", "gristHelper_Converted", "Text", "", 0],
// Repeated conversion just before applying
["ConvertFromColumn", "Table1", "F", "gristHelper_Converted", "Text", "", 0],
["CopyFromColumn", "Table1", "gristHelper_Transform", "F",
"{\"widget\":\"TextBox\",\"alignment\":\"left\"}"],
["RemoveColumn", "Table1", "gristHelper_Transform"],
["RemoveColumn", "Table1", "gristHelper_Converted"],
]);
// Check that selected reads text
await gu.assertType('Text');
});
it('should allow cancelling type changes', async function() {
// Enter bools into text column
await gu.getCellRC(0, 1).click();
await gu.sendKeys('false', $.ENTER);
await gu.getCellRC(1, 1).click();
await gu.sendKeys('true', $.ENTER);
// Change text to bool
await gu.setType('Toggle');
// Check that column appears bool during transform
assert.isDisplayed(await gu.getCellRC(1, 1).find('.widget_checkmark').wait(), true);
assert.isDisplayed(await gu.getCellRC(0, 1).find('.widget_checkmark'), false);
assert.hasClass(await gu.getCellRC(0, 1).find('.field_clip'), 'invalid', false);
// Cancel transform, check that column is still text
await $('.test-type-transform-cancel').wait().click();
assert.equal(await gu.getCellRC(0, 1).find('.field_clip').text(), 'false');
// Check that selected reads text
await gu.assertType('Text');
});
it('should allow revising type changes', async function() {
// Change text to integer
await gu.setType('Integer');
// Revise formula to get text length and accept
await $('.test-type-transform-revise').wait().click();
await $('.test-type-transform-formula').click();
await gu.waitAppFocus(false);
await gu.sendKeys($.SELECT_ALL, $.DELETE, 'return len($F) + 1');
// Check that updating the type conversion works
await $('.test-type-transform-update').click();
await gu.waitForServer();
assert.equal(await gu.getCellRC(0, 1).find('.field_clip').text(), '6');
// Check that applying the type conversion without first updating works
// (the weird formula keeps other tests consistent with past behaviour)
await $('.test-type-transform-formula').click();
await gu.waitAppFocus(false);
await gu.sendKeys($.SELECT_ALL, $.DELETE, 'return len($F.replace("0", "0.0"))');
await gu.waitForServer();
await gu.applyTypeConversion();
// Check that column is integer and has no errors
assert.equal(await gu.getCellRC(0, 1).find('.field_clip').text(), '5');
assert.isFalse(await gu.getCellRC(0, 1).find('.field_clip').hasClass('invalid'));
});
it('should allow configuring reference changes', async function() {
// Prepare new table and section
await gu.actions.addNewSection('New', 'Table');
await gu.waitForServer();
await $('.test-viewlayout-section-6').click();
await gu.addRecord(['green']);
await gu.addRecord(['blue']);
// Change type to reference column
await gu.actions.viewSection('Table1').selectSection();
await gu.getCellRC(0, 3).click();
await gu.waitAppFocus(true);
await gu.sendKeys('blue', $.ENTER);
await gu.getCellRC(1, 3).click();
await gu.sendKeys('green', $.ENTER);
await gu.waitForServer();
await gu.userActionsCollect();
await gu.setType('Reference');
// Assert the correct column is selected and that the formula matches the selected
assert.equal(await $('.test-fbuilder-ref-table-select .test-select-row').getText(), 'Table2');
assert.equal(await $('.test-fbuilder-ref-col-select .test-select-row').getText(), 'A');
await $('.test-type-transform-revise').click();
var aceText = await gu.getAceText($('.test-type-transform-formula').elem());
assert.equal(aceText, "rec.gristHelper_Converted");
// Apply transform and check that field is a reference
await gu.applyTypeConversion();
await gu.userActionsVerify([
["AddColumn", "Table1", "gristHelper_Converted", { type: 'Any' }],
["AddColumn", "Table1", "gristHelper_Transform", { type: 'Any' }],
["ModifyColumn", "Table1", "gristHelper_Converted", {
"formula": "",
"isFormula": false,
"type": "Ref:Table2",
"visibleCol": 9
}],
["ModifyColumn", "Table1", "gristHelper_Transform", {
"formula": "rec.gristHelper_Converted",
"isFormula": true,
"type": "Ref:Table2",
"visibleCol": 9
}],
["ConvertFromColumn", "Table1", "C", "gristHelper_Converted", "Ref:Table2", "", 9],
// Set display formula for transform column.
["SetDisplayFormula", "Table1", null, 13, "$gristHelper_Transform.A"],
// Repeated conversion just before applying
["ConvertFromColumn", "Table1", "C", "gristHelper_Converted", "Ref:Table2", "", 9],
["CopyFromColumn", "Table1", "gristHelper_Transform", "C", "{\"widget\":\"Reference\",\"alignment\":\"left\"}"],
// We used to unset field display formula, but we don't actually use it during transforms.
["RemoveColumn", "Table1", "gristHelper_Transform"],
["RemoveColumn", "Table1", "gristHelper_Converted"],
]);
assert.hasClass(await gu.getCellRC(0, 3).find('.field_clip div'), 'test-ref-link-icon');
// Check conversion back to text
await gu.setType('Text');
await $('.test-type-transform-revise').click();
aceText = await gu.getAceText($('.test-type-transform-formula').elem());
assert.equal(aceText, 'rec.gristHelper_Converted');
await gu.applyTypeConversion();
assert.equal(await gu.getCellRC(0, 3).find('.field_clip').getText(), 'blue');
});
it('should allow configuring date and datetime changes', async function() {
await gu.toggleSidePanel("left", "close");
await gu.getCellRC(0, 2).scrollIntoView({inline: "end"}).click();
await gu.sendKeys('4/2/93', $.ENTER);
await gu.getCellRC(1, 2).click();
await gu.sendKeys('4/26/16', $.ENTER);
// Convert to Date
await gu.setType('Date');
// Guessed date format M/D/YY
assert.equal(await gu.dateFormat(), 'Custom');
assert.equal(await $('$Widget_dateCustomFormat input').val(), 'M/D/YY');
// Change manually to a more formal date format
await gu.dateFormat('MM/DD/YYYY');
assert.equal(await gu.dateFormat(), 'MM/DD/YYYY');
await gu.waitForServer();
// Check formula
await $('.test-type-transform-revise').wait().click();
var aceText = await gu.getAceText($('.test-type-transform-formula').elem());
assert.equal(aceText, "rec.gristHelper_Converted");
// Apply transform and check that field has correct value
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993', '04/26/2016'
]);
// Convert back to text
await gu.setType('Text');
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993', '04/26/2016'
]);
await gu.getCellRC(0, 2).click();
await gu.sendKeys($.ENTER, ' 12:00am');
await gu.getCellRC(1, 2).click();
await gu.sendKeys($.ENTER, ' 4:00am', $.ENTER);
// Convert to DateTime and assert formula matches options
await gu.setType('DateTime');
await $('.test-tz-autocomplete').click();
await gu.sendKeys('Los_Ang', $.ENTER);
await gu.waitForServer();
assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
assert.equal(await gu.dateFormat(), 'MM/DD/YYYY');
assert.equal(await gu.timeFormat(), 'h:mma');
await $('.test-type-transform-revise').click();
aceText = await gu.getAceText($('.test-type-transform-formula').elem());
assert.equal(aceText, "rec.gristHelper_Converted");
// Apply transform and check that field has correct value
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993 12:00am', '04/26/2016 4:00am',
]);
assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
// Convert DateTime to Date and check that we are getting the right date.
await gu.setType('Date');
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993', '04/26/2016'
]);
// Convert Date to DateTime and check that we are getting midnight in selected timezone.
await gu.setType('DateTime');
await gu.timeFormat('HH:mm z');
await gu.waitForServer();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993 00:00 EST', '04/26/2016 00:00 EDT'
]);
await $('.test-tz-autocomplete').click();
await gu.sendKeys('Los_Ang', $.ENTER);
await gu.waitForServer();
assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
assert.equal(await gu.dateFormat(), 'MM/DD/YYYY');
assert.equal(await gu.timeFormat(), 'HH:mm z');
await gu.waitForServer();
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: [2], mapper: valText}), [
'04/02/1993 00:00 PST', '04/26/2016 00:00 PDT'
]);
});
it('should trigger a transform when reference table is changed', async function() {
// Set up conditions for the test
await gu.actions.viewSection('Table1').selectSection();
await gu.enterGridValues(2, 3, [['red', 'yellow']]);
await gu.actions.addNewSection('New', 'Table');
await gu.actions.viewSection('TABLE3').selectSection();
await gu.enterGridValues(0, 1, [['yellow', 'red', 'green', 'blue']]);
await gu.actions.viewSection('Table1').selectSection();
await gu.clickCellRC(0, 3);
await gu.openSidePane('field');
await gu.setType('Reference');
await gu.setRefTable('Table2');
await gu.waitForServer();
await gu.setVisibleCol('A');
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3], mapper: valText}), [
'blue', 'green', '!red', '!yellow'
]);
// Check that row ids shows 2, 1, (AltText), (AltText)
await gu.setVisibleCol('Row ID');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['Table2[2]', 'Table2[1]', 'red', 'yellow']);
await gu.setVisibleCol('A');
// Should trigger the transform
await gu.setRefTable('Table3');
await gu.waitForServer();
await gu.setVisibleCol('B');
await assert.isPresent($('.type_transform_prompt'));
// Transform should follow the format Ref:<oldTable> -> Text -> Ref:<newTable>
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['blue', 'green', 'red', 'yellow']);
// Check that the cells are no longer invalid.
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3], mapper: valText}), [
'blue', 'green', 'red', 'yellow'
]);
// Check that row ids have changed, despite text remaining the same.
await gu.setVisibleCol('Row ID');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['Table3[4]', 'Table3[3]', 'Table3[2]', 'Table3[1]']);
});
it('should allow undoing a reference transform in one step', async function() {
await gu.setVisibleCol('B');
await gu.setType('Text');
await gu.applyTypeConversion();
await gu.setType('Reference');
await gu.applyTypeConversion();
await gu.undo();
// Undoing once should return the column to Text with the correct values.
await gu.assertType('Text');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['blue', 'green', 'red', 'yellow']);
});
it('should cancel an in-progress transformation on undo', async function() {
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['blue', 'green', 'red', 'yellow']);
await gu.setType('Reference');
await gu.assertType('Reference');
await assert.isPresent($('.test-type-transform-top'), true);
await gu.undo();
await assert.isPresent($('.test-type-transform-top'), false);
await gu.assertType('Text');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [3]}),
['blue', 'green', 'red', 'yellow']);
});
// NOTE: This tests a bug fix where integer values not present in the reference
// column were mistaken for row ids and converted to row values instead of AltText values.
it('should properly convert from integer to reference', async function() {
// Set up conditions for the test
await gu.actions.viewSection('TABLE3').selectSection();
await gu.enterGridValues(0, 2, [['3', '3', '4', '1']]);
await gu.waitForServer();
await gu.setType('Integer');
await gu.applyTypeConversion();
// Begin convert to reference.
await gu.setType('Reference');
await gu.assertType('Reference');
await assert.isPresent($('.test-type-transform-top'), true);
// Convert to a reference and check that the values are valid and as expected
// before and after the conversion. The last row should be invalid since there
// is no matching record in the destination col.
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '!1'
]);
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '!1'
]);
});
// NOTE: This tests a bug fix where reference transforms to numeric types gave
// error values by default.
it('should properly convert from reference to integer/numeric', async function() {
await gu.clickCellRC(0, 2);
// Convert to an integer and check that the values are valid and as expected before
// and after the conversion. This ensures that AltText values can be cast back into ints.
await gu.setType('Integer');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '1'
]);
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '1'
]);
// Switch back to a reference column
await gu.setType('Reference');
await gu.assertType('Reference');
await assert.isPresent($('.test-type-transform-top'), true);
await gu.applyTypeConversion();
// Convert to numeric and check the values are valid and as expected.
// This ensures that AltText values can be cast back into floats.
await gu.setType('Numeric');
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '1'
]);
await gu.applyTypeConversion();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: [2], mapper: valText}), [
'3', '3', '4', '1'
]);
});
// NOTE: This tests a bug fix where numeric types were not properly converted to
// boolean values.
it('should properly convert from integer/numeric to boolean', async function() {
// Update the Numeric column to include some falsy/truthy numbers and alttext.
await gu.clickCellRC(0, 2);
await gu.sendKeys('0');
await gu.clickCellRC(4, 2);
await gu.sendKeys('False', $.ENTER);
await gu.waitForServer();
await gu.sendKeys('true', $.ENTER);
await gu.waitForServer();
// Assert that the values are set up properly.
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4, 5, 6], cols: [2], mapper: valText}), [
'0', '3', '4', '1', '!False', '!true'
]);
// Convert the column to boolean. Assert all the values are valid and as expected.
await gu.setType('Toggle');
await gu.applyTypeConversion();
await gu.setWidget('TextBox');
// Check that the values have transformed without errors, and are as expected.
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4, 5, 6], cols: [2], mapper: valText}), [
'false', '!3', '!4', 'true', 'false', 'true'
]);
// Check that sorting by the column has the expected effect.
await gu.openColumnMenu('C');
await $(`.grist-floating-menu .test-sort-asc`).click();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4, 5, 6], cols: [2], mapper: valText}), [
'false', 'false', 'true', 'true', '!3', '!4'
]);
// Undo the widget option and type conversion and assert that the values are properly restored.
// (but still sorted)
await gu.undo(2);
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4, 5, 6], cols: [2], mapper: valText}), [
'0', '1', '3', '4', '!False', '!true'
]);
});
});