gristlabs_grist-core/test/nbrowser/Dates.ntest.js
Paul Fitzpatrick 71c6537c49
tweak a date test that can occasionally fail (#592)
A date test was noted to fail, with a formula intended for
a cell ending up in the column header. This may help.
Entering formulas reliably requires waiting for a particular
focus state.
2023-07-26 08:08:55 -04:00

506 lines
20 KiB
JavaScript

import { assert, driver } from 'mocha-webdriver';
import { $, gu, test } from 'test/nbrowser/gristUtil-nbrowser';
describe('Dates.ntest', function() {
const cleanup = test.setupTestSuite(this);
let doc;
before(async function() {
await gu.supportOldTimeyTestCode();
doc = await gu.useFixtureDoc(cleanup, "Hello.grist", true);
await gu.toggleSidePanel("left", "close");
});
afterEach(function() {
return gu.checkForErrors();
});
it('should allow correct datetime reformatting', async function() {
await gu.openSidePane('field');
var cell = await gu.getCellRC(0, 0);
// Move to the first column
await cell.click();
await gu.sendKeys('2008-01-10 9:20pm', $.ENTER);
// Change type to 'DateTime'
await gu.setType('DateTime');
await $('.test-tz-autocomplete').wait().click();
await gu.sendKeys($.DELETE, 'UTC', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), '2008-01-10 9:20pm');
await $('.test-type-transform-apply').wait().click();
await gu.waitForServer();
// Change timezone to 'America/Los_Angeles' and check that the date is correct
await $('.test-tz-autocomplete').wait().click();
await gu.sendKeys('Los An', $.ENTER);
await gu.waitForServer();
assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
assert.equal(await cell.text(), '2008-01-10 1:20pm');
// Change format and check that date is reformatted
await gu.dateFormat('MMMM Do, YYYY');
await gu.timeFormat('HH:mm:ss z');
assert.equal(await gu.getCellRC(0, 0).text(), 'January 10th, 2008 13:20:00 PST');
// Change to custom format and check that the date is reformatted
await gu.dateFormat('Custom');
await $('$Widget_dateCustomFormat .kf_text').click();
await gu.sendKeys($.SELECT_ALL, 'dddd', $.ENTER);
await gu.timeFormat("Custom");
await $('$Widget_timeCustomFormat .kf_text').click();
await gu.sendKeys($.SELECT_ALL, 'Hmm', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'Thursday 1320');
});
it('should include a functioning datetime editor', async function() {
var cell = await gu.getCellRC(0, 0);
// DateTime editor should open, separate date and time, and replace incomplete format
// with YYYY-MM-DD
await cell.click();
await gu.sendKeys($.ENTER);
assert.equal(await $('.celleditor_text_editor').first().val(), '2008-01-10');
// Date should be changable by clicking the calendar dates
await $('.celleditor_text_editor').first().sendKeys($.DOWN); // Opens date picker even if window has no focus.
await $('.datepicker .day:contains(19)').wait().click();
await gu.sendKeys($.ENTER);
assert.equal(await cell.text(), 'Saturday 1320');
// Date editor should convert Moment formats to datepicker safe formats
// Date editor should allow tabbing between date and time entry boxes
await gu.dateFormat('MMMM Do, YYYY');
await gu.timeFormat('h:mma');
await cell.click();
await gu.sendKeys($.ENTER);
assert.deepEqual(await $('.celleditor_text_editor').array().val(),
['January 19th, 2008', '1:20pm']);
await gu.sendKeys($.SELECT_ALL, 'February 20th, 2009', $.TAB, '8:15am', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'February 20th, 2009 8:15am');
// DateTime editor should close and save value when the user clicks away
await cell.click();
await gu.sendKeys($.ENTER, $.SELECT_ALL, $.DELETE);
await gu.getCellRC(0, 3).click(); // click away
await gu.waitForServer();
// Since only the date value was removed, the cell should give AltText of the time value
assert.equal(await cell.text(), '8:15am');
assert.hasClass(await cell.find('.field_clip'), 'invalid');
// DateTime editor should close and revert value when the user presses escape
await cell.click();
await gu.sendKeys($.ENTER, 'April 2, 1993', $.ESCAPE);
assert.equal(await cell.text(), '8:15am');
});
it('should allow correct date reformatting', async function() {
var cell = await gu.getCellRC(0, 1);
// Move to the first column
await cell.click();
await gu.sendKeys('2016-01-08', $.ENTER);
// Change type to 'Date'
await gu.setType('Date');
await $('.test-type-transform-apply').wait().click();
await gu.waitForServer(); // Make sure type is set
// Check that the date is correct
await $('$Widget_dateFormat').wait();
assert.equal(await cell.text(), '2016-01-08');
// Change format and check that date is reformatted
await gu.dateFormat('MMMM Do, YYYY');
await gu.waitForServer();
assert.equal(await cell.text(), 'January 8th, 2016');
// Try another format
await gu.dateFormat('DD MMM YYYY');
await gu.waitForServer();
assert.equal(await cell.text(), '08 Jan 2016');
// Change to custom format and check that the date is reformatted
await gu.dateFormat('Custom');
await $('$Widget_dateCustomFormat .kf_text').click();
await gu.sendKeys($.SELECT_ALL, 'dddd', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'Friday');
});
it('should include a functioning date editor', async function() {
var cell = await gu.getCellRC(0, 1);
// Date editor should open and replace incomplete format with YYYY-MM-DD
await cell.click();
await gu.sendKeys($.ENTER);
assert.equal(await $('.celleditor_text_editor').val(), '2016-01-08');
// Date should be changable by clicking the calendar dates
await $('.celleditor_text_editor').sendKeys($.DOWN); // Opens date picker even if window has no focus.
await $('.datepicker .day:contains(19)').wait().click();
await gu.sendKeys($.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'Tuesday');
// Date editor should convert Moment formats to datepicker safe formats
// Date editor should save the date on enter press
await gu.dateFormat('MMMM Do, YYYY');
await cell.click();
await gu.sendKeys($.ENTER);
assert.equal(await $('.celleditor_text_editor').val(), 'January 19th, 2016');
await gu.sendKeys($.SELECT_ALL, 'February 20th, 2016', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'February 20th, 2016');
// Date editor should close and save value when the user clicks away
await cell.click();
await gu.sendKeys($.ENTER, $.SELECT_ALL, $.DELETE);
await gu.getCellRC(0, 3).click(); // click away
await gu.waitForServer();
assert.equal(await cell.text(), '');
// Date editor should close and revert value when the user presses escape
await cell.click();
await gu.sendKeys($.ENTER, 'April 2, 1993', $.ESCAPE);
assert.equal(await cell.text(), '');
});
it('should reload values correctly after reopen', async function() {
await gu.getCellRC(0, 0).click();
await gu.sendKeys('February 20th, 2009', $.TAB, '8:15am', $.ENTER);
await gu.getCellRC(0, 1).click();
await gu.sendKeys('January 19th, 1968', $.ENTER);
await gu.getCellRC(1, 1).click();
await gu.sendKeys($.DELETE);
await gu.waitForServer();
await gu.getCellRC(0, 2).click();
await gu.waitAppFocus(true);
await gu.sendKeys('=');
await $('.test-editor-tooltip-convert').click();
await gu.sendKeys('$A', $.ENTER);
await gu.waitForServer();
await gu.waitAppFocus(true);
await gu.getCellRC(0, 3).click();
await gu.sendKeys('=');
await gu.waitAppFocus(false);
await gu.sendKeys('$B', $.ENTER);
await gu.waitForServer();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: ['A', 'B', 'C', 'D']}), [
'February 20th, 2009 8:15am',
'January 19th, 1968',
'2009-02-20 08:15:00-08:00',
'1968-01-19',
'', '', '', ''
]);
// We don't have a quick way to shutdown a document and reopen from scratch. So instead, we'll
// make a copy of the document, and open that to test that values got saved correctly.
// TODO: it would be good to add a way to reload document from scratch, perhaps by reloading
// with a special URL fragment.
await gu.copyDoc(doc.id, true);
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: ['A', 'B', 'C', 'D']}), [
'February 20th, 2009 8:15am',
'January 19th, 1968',
'2009-02-20 08:15:00-08:00',
'1968-01-19',
'', '', '', ''
]);
});
it('should support shortcuts to insert date/time', async function() {
await gu.openSidePane('field');
// Check the types of the first two columns.
await gu.clickCellRC(0, 0);
await gu.assertType('DateTime');
await gu.clickCellRC(0, 1);
await gu.assertType('Date');
// Insert a few more columns: empty, Text, Numeric.
await addColumn();
await addColumn();
await addColumn();
await gu.clickCellRC(0, 3);
await gu.setType('Numeric');
await gu.clickCellRC(0, 4);
await gu.setType('Text');
// Override Date.now() and timezone in the current browser page to return a consistent value,
// used e.g. for the default for the year and month.
await driver.executeScript(
"Date.now = () => 1477548296087; " + // This value is 2016-10-27 02:04:56.087 EST
"exposeModulesForTests().then(() => { " +
"window.exposedModules.moment.tz.setDefault('America/New_York');" +
"});"
);
async function fillWithShortcuts() {
await gu.toggleSidePanel('right', 'close');
// Type the Date-only shortcut into each cell in the second row.
await gu.clickCellRC(1, 0);
for (var i = 0; i < 6; i++) {
await gu.sendKeys([$.MOD, ';'], $.TAB);
}
// Type the Date-Time shortcut into each cell in the third row.
await gu.clickCellRC(2, 0);
for (i = 0; i < 6; i++) {
await gu.sendKeys([$.MOD, $.SHIFT, ';'], $.TAB);
}
}
// Change document timezone to US/Hawaii (3 hours behind LA, which is TZ of the first column).
// We check that shortcuts for Text/Any columns use the document timezone.
await setGlobalTimezone('US/Hawaii');
await fillWithShortcuts();
// Compare the values. NOTE: this assumes EST timezone for the browser's local time.
assert.deepEqual(await gu.getGridValues({rowNums: [2, 3], cols: [0, 1, 2, 3, 4]}), [
// Note that column A has Los_Angeles timezone set, so time differs from Hawaii.
// Note that Date column gets the date in Hawaii, not local or UTC (both 2016-10-27).
// The originally empty column had its type guessed as Date when the current date was first entered,
// hence "2016-10-26" appears in both rows.
"October 26th, 2016 11:04pm", "October 26th, 2016", "2016-10-26", "0", "2016-10-26",
"October 26th, 2016 11:04pm", "October 26th, 2016", "2016-10-26", "0", "2016-10-26 20:04:56",
]);
// Undo the 8 cells we actually filled in, and check that the empty column reverted to Any
await gu.undo(8);
await gu.clickCellRC(1, 2);
await gu.assertType('Any');
// Change document timezone back to America/New_York.
await setGlobalTimezone('America/New_York');
await fillWithShortcuts();
// Compare the values. NOTE: this assumes EST timezone for the browser's local time.
assert.deepEqual(await gu.getGridValues({rowNums: [2, 3], cols: [0, 1, 2, 3, 4]}), [
// Note that column A has Los_Angeles timezone set, so date differs by one from New_York.
"October 26th, 2016 11:04pm", "October 27th, 2016", "2016-10-27", "0", "2016-10-27",
"October 26th, 2016 11:04pm", "October 27th, 2016", "2016-10-27", "0", "2016-10-27 02:04:56",
]);
});
it('should allow navigating the datepicker with the keyboard', async function() {
// Change the date using the datepicker.
let cell = await gu.getCellRC(0, 1);
await cell.scrollIntoView({inline: "end"}).click();
await gu.sendKeys($.ENTER);
await gu.waitAppFocus(false);
await gu.sendKeys($.UP, $.UP, $.LEFT, $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'January 11th, 1968');
// Do the same in the datetime editor.
cell = await gu.getCellRC(1, 0);
await cell.click();
await gu.sendKeys($.ENTER);
await gu.waitAppFocus(false);
await gu.sendKeys($.UP, $.RIGHT, $.RIGHT, $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'October 28th, 2016 11:04pm');
// Start navigating the datepicker, then start typing to return to using the cell editor.
cell = await gu.getCellRC(1, 1);
await cell.click();
// The first backspace should return to cell edit mode, then the following keys should
// change the year to 2009.
await gu.sendKeys($.ENTER);
await gu.waitAppFocus(false);
await gu.sendKeys($.DOWN, $.RIGHT, $.BACK_SPACE, '9', $.LEFT, $.BACK_SPACE, '0', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'October 27th, 2009');
});
// NOTE: This addresses a bug where typical date entry formats were not recognized.
// See https://phab.getgrist.com/T308
it('should allow using common formats to enter the date', async function() {
let cell = await gu.getCellRC(2, 1);
await cell.click();
await gu.sendKeys('April 2 1993', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'April 2nd, 1993');
cell = await gu.getCellRC(1, 0);
await cell.click();
await gu.sendKeys('December', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), `December 1st, 2016 11:04pm`);
cell = await gu.getCellRC(0, 1);
await cell.click();
await gu.sendKeys('7-Sep', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), `September 7th, 2016`);
await cell.click();
await gu.sendKeys('6/8', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), `June 8th, 2016`);
// The selected format should take precedence over the default format when
// parsing the date. Entering the same thing as before (6/8) will yield a different
// result after changing the format.
await gu.openSidePane('field');
cell = await gu.getCellRC(1, 1);
await cell.click();
await gu.dateFormat('DD-MM-YYYY');
await cell.click();
await gu.sendKeys('6/8', $.ENTER);
await gu.waitForServer();
await gu.dateFormat('MMMM Do, YYYY');
assert.equal(await cell.text(), `August 6th, 2016`);
cell = await gu.getCellRC(2, 1);
await cell.click();
await gu.sendKeys('1937', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), `January 1st, 1937`);
});
it('should not attempt to parse non-dates', async function() {
// Should allow AltText
let cell = await gu.getCellRC(2, 1);
await cell.click();
await gu.sendKeys('Applesauce', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), 'Applesauce');
await assert.hasClass(cell.find('.field_clip'), 'invalid');
// Should allow AltText even of numbers that cannot be parsed as dates.
// Manually entered numbers should not be read as timestamps.
cell = await gu.getCellRC(1, 0);
await cell.click();
await gu.sendKeys('100000', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), '100000 11:04pm');
await assert.hasClass(cell.find('.field_clip'), 'invalid');
// Should give AltText if just the time is entered but not the date.
cell = await gu.getCellRC(1, 0);
await cell.click();
await gu.sendKeys($.ENTER, $.TAB, '3', $.ENTER);
await gu.waitForServer();
assert.equal(await cell.text(), '100000 11:04pm 3');
await assert.hasClass(cell.find('.field_clip'), 'invalid');
});
it("should allow working with naive date object", async function() {
await gu.clickCellRC(0, 1);
await gu.sendKeys([$.ALT, '=']);
await gu.waitForServer();
await gu.waitAppFocus(false);
await gu.sendKeys("Diff", $.ENTER);
await gu.waitForServer();
await gu.sendKeys('=');
await gu.waitAppFocus(false);
await gu.sendKeys('($A-DTIME($B)).total_seconds()', $.ENTER);
await gu.waitForServer();
await gu.waitAppFocus();
assert.deepEqual(await gu.getCellRC(0, 2).text(), '-230211900');
// change global timezone should recompute formula
await setGlobalTimezone('Paris');
assert.deepEqual(await gu.getCellRC(0, 2).text(), '-230190300');
});
// NOTE: This tests a specific bug where AltText values in a column that has been coverted
// to a date column do not respond to updates until refresh. This bug was exposed via the
// error dom in FieldBuilder not being re-evaluated after a column transform.
it('should allow deleting AltText values in a newly changed Date column', async function() {
// Change the type to text and enter a text value.
await gu.clickCellRC(0, 1);
await gu.setType('Text');
await gu.applyTypeConversion();
await gu.clickCellRC(2, 1);
await gu.sendKeys('banana', $.ENTER);
await gu.waitForServer();
assert.equal(await gu.getCellRC(2, 1).text(), 'banana');
// Change back to Date and try to remove the text.
await gu.setType('Date');
await $('.test-type-transform-apply').wait().click();
await gu.waitForServer();
assert.equal(await gu.getCellRC(2, 1).text(), 'banana');
await gu.clickCellRC(2, 1);
await gu.sendKeys($.BACK_SPACE);
await gu.waitForServer();
assert.equal(await gu.getCellRC(2, 1).text(), '');
await gu.undo();
});
it("should report informative error when AltText is used for date", async function() {
// Enter a formula column that uses a date.
await gu.clickCellRC(0, 1);
await gu.sendKeys([$.ALT, '=']);
await gu.waitForServer();
await gu.sendKeys("Month", $.ENTER);
await gu.waitForServer();
await gu.sendKeys("=$B.month", $.ENTER);
await gu.waitForServer();
assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: ['B', 'Month']}), [
"June 8th, 2016", "6",
"August 6th, 2016", "8",
"banana", "#Invalid Date: banana",
"", "#AttributeError",
]);
});
it('should default timezone to document\'s timezone', async function() {
// add a DateTime column
await addDateTimeColumn();
await gu.timeFormat('HH:mm:ss');
// BUG: it is required to click somewhere after setting the type of a column for the shortcut to
// work
// TODO: removes gu.getCellRC(1, 3).click() below when its fixed
await gu.getCellRC(1, 3).click();
// get the current date
await gu.sendKeys([$.MOD, $.SHIFT, ';']);
await gu.waitForServer();
const date1 = await gu.getCellRC(1, 3).text();
// check default timezone
assert.equal(await $('.test-tz-autocomplete input').val(), 'Europe/Paris');
// set global document timezone to 'Europe/Paris'
await setGlobalTimezone('America/Los_Angeles');
// add another DateTime column
await addDateTimeColumn();
await gu.timeFormat('HH:mm:ss');
// todo: same as for gu.getCellRC(1, 3).click();
await gu.getCellRC(1, 4).click();
// get the current date
await gu.sendKeys([$.MOD, $.SHIFT, ';']);
await gu.waitForServer();
const date2 = await gu.getCellRC(1, 4).text();
// check default timezone
assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
// check that the delta between date1 and date2 is coherent with the delta between
// 'Europe/Paris' and 'America/Los_Angeles' timezones.
const delta = (new Date(date1) - new Date(date2)) / 1000 / 60 / 60;
assert.isAbove(delta, 6);
assert.isBelow(delta, 12);
});
});
async function addDateTimeColumn() {
await addColumn();
return gu.setType('DateTime');
}
async function addColumn() {
await gu.sendKeys([$.ALT, '=']);
await gu.waitForServer();
return gu.sendKeys($.ESCAPE);
}
async function setGlobalTimezone(name) {
await $('.test-user-icon').click(); // open the user menu
await $('.test-dm-doc-settings').click();
await $('.test-tz-autocomplete').click();
await $(`.test-acselect-dropdown li:contains(${name})`).click();
await gu.waitForServer();
await driver.navigate().back();
}