mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) New date filter with a calendar view
Summary: Implements the new date filtering panel. Design results from long discussion between: Alex, Anais, Cyprien and Dmitry. Test environment: https://grist-new-date-range-filter.fly.dev/ Test Plan: Include various new tests. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3720
This commit is contained in:
170
test/client/ui/RelativeDatesOptions.ts
Normal file
170
test/client/ui/RelativeDatesOptions.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import {DEPS, relativeDatesOptions} from 'app/client/ui/RelativeDatesOptions';
|
||||
|
||||
import sinon, { SinonStub } from 'sinon';
|
||||
import {assert} from 'chai';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
const valueFormatter = (val: any) => moment(val * 1000).format('YYYY-MM-DD');
|
||||
const toGristDate = (val: moment.Moment) => Math.floor(val.valueOf() / 1000);
|
||||
|
||||
|
||||
function getOptions(date: string) {
|
||||
const m = moment(date);
|
||||
const dateUTC = moment.utc([m.year(), m.month(), m.date()]);
|
||||
return relativeDatesOptions(toGristDate(dateUTC), valueFormatter);
|
||||
}
|
||||
|
||||
function checkOption(options: Array<{label: string, spec: any}>, label: string, spec: any) {
|
||||
try {
|
||||
assert.deepInclude(options, {label, spec});
|
||||
} catch (e) {
|
||||
const json = `{\n ${options.map(o => JSON.stringify({label: o.label, spec: o.spec})).join('\n ')}\n}`;
|
||||
assert.fail(`expected ${json} to include\n ${JSON.stringify({label, spec})}`);
|
||||
}
|
||||
}
|
||||
|
||||
function optionNotIncluded(options: any[], label: string) {
|
||||
assert.notInclude(options.map(o => o.label), label);
|
||||
}
|
||||
|
||||
describe('RelativeDatesOptions', function() {
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
let getCurrentTimeSub: SinonStub;
|
||||
|
||||
function setCurrentDate(now: string) {
|
||||
getCurrentTimeSub.returns(moment(now));
|
||||
}
|
||||
|
||||
before(() => {
|
||||
getCurrentTimeSub = sandbox.stub(DEPS, 'getCurrentTime');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('relativeDateOptions', function() {
|
||||
it('should limit \'X days ago/from now\' to 90 days ago/from now', function() {
|
||||
setCurrentDate('2022-09-26');
|
||||
|
||||
checkOption(getOptions('2022-09-10'), '16 days ago', [{quantity: -16, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-06-28'), '90 days ago', [{quantity: -90, unit: 'day'}]);
|
||||
|
||||
|
||||
// check no options of the form 'X days ago'
|
||||
optionNotIncluded(getOptions('2022-06-27'), '91 days ago');
|
||||
assert.notOk(getOptions('2022-06-27').find(o => /^[0-9]+ days ago$/.test(o.label)));
|
||||
|
||||
checkOption(getOptions('2022-09-26'), 'Today', [{quantity: 0, unit: 'day'}]);
|
||||
checkOption(getOptions('2022-09-27'), 'Tomorrow', [{quantity: 1, unit: 'day'}]);
|
||||
checkOption(getOptions('2022-10-02'), '6 days from now', [{quantity: 6, unit: 'day'}]);
|
||||
});
|
||||
|
||||
it('should limit \'WEEKDAY of X weeks ago/from now\' to 4 weeks ago/from now', function() {
|
||||
setCurrentDate('2022-09-26');
|
||||
|
||||
checkOption(getOptions('2022-09-20'), 'Tuesday of last week', [
|
||||
{quantity: -1, unit: 'week'}, {quantity: 2, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-09-21'), 'Wednesday of last week', [
|
||||
{quantity: -1, unit: 'week'}, {quantity: 3, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-08-31'), 'Wednesday of 4 weeks ago', [
|
||||
{quantity: -4, unit: 'week'}, {quantity: 3, unit: 'day'}]);
|
||||
|
||||
assert.notDeepInclude(getOptions('2022-08-24'), {
|
||||
label: 'Wednesday of 5 weeks ago',
|
||||
spec: [{quantity: -5, unit: 'week'}, {quantity: 3, unit: 'day'}]
|
||||
});
|
||||
assert.notOk(getOptions('2022-08-24').find(o => /Wednesday/.test(o.label)));
|
||||
|
||||
checkOption(getOptions('2022-09-29'), 'Thursday of this week', [
|
||||
{quantity: 0, unit: 'week'}, {quantity: 4, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-10-13'), 'Thursday of 2 weeks from now', [
|
||||
{quantity: 2, unit: 'week'}, {quantity: 4, unit: 'day'}]);
|
||||
|
||||
});
|
||||
|
||||
it('should limit \'N day of X month ago/from no\' to 3 months ago/from now', function() {
|
||||
setCurrentDate('2022-09-26');
|
||||
|
||||
checkOption(getOptions('2022-09-27'), '27th day of this month', [
|
||||
{quantity: 0, unit: 'month'}, {quantity: 26, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-06-16'), '16th day of 3 months ago', [
|
||||
{quantity: -3, unit: 'month'}, {quantity: 15, unit: 'day'}]);
|
||||
|
||||
assert.notOk(getOptions('2022-05-16').find(o => /months? ago/.test(o.label)));
|
||||
|
||||
checkOption(getOptions('2022-10-16'), '16th day of next month', [
|
||||
{quantity: 1, unit: 'month'}, {quantity: 15, unit: 'day'}]);
|
||||
|
||||
checkOption(getOptions('2022-11-16'), '16th day of 2 months from now', [
|
||||
{quantity: 2, unit: 'month'}, {quantity: 15, unit: 'day'}]);
|
||||
|
||||
assert.notOk(getOptions('2023-01-16').find(o => /months? from now/.test(o.label)));
|
||||
});
|
||||
|
||||
it('should limit \'1st day of year\' to 1st of Jan', function() {
|
||||
setCurrentDate('2022-09-26');
|
||||
|
||||
checkOption(getOptions('2022-01-01'), '1st day of this year', [
|
||||
{quantity: 0, unit: 'year'}]);
|
||||
|
||||
checkOption(getOptions('2021-01-01'), '1st day of last year', [
|
||||
{quantity: -1, unit: 'year'}]);
|
||||
|
||||
checkOption(getOptions('2024-01-01'), '1st day of 2 years from now', [
|
||||
{quantity: 2, unit: 'year'}]);
|
||||
});
|
||||
|
||||
it('should limit \'Last day of X year ago/from now\' to 31st of Dec', function() {
|
||||
setCurrentDate('2022-09-26');
|
||||
|
||||
checkOption(getOptions('2022-12-31'), 'Last day of this year', [
|
||||
{quantity: 0, unit: 'year', endOf: true}]);
|
||||
|
||||
checkOption(getOptions('2019-12-31'), 'Last day of 3 years ago', [
|
||||
{quantity: -3, unit: 'year', endOf: true}]);
|
||||
|
||||
checkOption(getOptions('2027-12-31'), 'Last day of 5 years from now', [
|
||||
{quantity: 5, unit: 'year', endOf: true}]);
|
||||
|
||||
});
|
||||
|
||||
it('should offer 1st day of any month, limited to 12 months ago/from now', function() {
|
||||
setCurrentDate('2022-09-29');
|
||||
|
||||
checkOption(getOptions('2022-09-01'), '1st day of this month', [
|
||||
{quantity: 0, unit: 'month'}]);
|
||||
|
||||
checkOption(getOptions('2021-09-01'), '1st day of 12 months ago', [
|
||||
{quantity: -12, unit: 'month'}]);
|
||||
|
||||
assert.notOk(getOptions('2021-08-01').find(o => /1st day of [0-9]+ months? ago/.test(o.label)));
|
||||
|
||||
checkOption(getOptions('2022-11-01'), '1st day of 2 months from now', [{
|
||||
quantity: 2, unit: 'month'}]);
|
||||
});
|
||||
|
||||
it('should offer last day of the month, limited to 12 months ago/from now', function() {
|
||||
setCurrentDate('2022-09-29');
|
||||
|
||||
checkOption(getOptions('2022-09-30'), 'Last day of this month', [
|
||||
{quantity: 0, unit: 'month', endOf: true}]);
|
||||
|
||||
checkOption(getOptions('2022-08-31'), 'Last day of last month', [
|
||||
{quantity: -1, unit: 'month', endOf: true}]);
|
||||
|
||||
assert.notOk(getOptions('2021-08-31').find(o => /Last day of [0-9]+ months? ago/.test(o.label)));
|
||||
|
||||
checkOption(getOptions('2022-12-31'), 'Last day of 3 months from now', [
|
||||
{quantity: 3, unit: 'month', endOf: true}]);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
62
test/common/RelativeDates.ts
Normal file
62
test/common/RelativeDates.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {DEPS, getMatchingDoubleRelativeDate} from 'app/client/ui/RelativeDatesOptions';
|
||||
import sinon from 'sinon';
|
||||
import {assert} from 'chai';
|
||||
import moment from 'moment-timezone';
|
||||
import {diffUnit} from 'app/common/RelativeDates';
|
||||
|
||||
const CURRENT_TIME = moment.tz('2022-09-26T12:13:32.018Z', 'utc');
|
||||
const now = () => moment(CURRENT_TIME);
|
||||
|
||||
describe('RelativeDates', function() {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
before(() => {
|
||||
sinon.stub(DEPS, 'getCurrentTime').returns(now());
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('getMatchingDoubleRelativeDate', function() {
|
||||
it('should work correctly', function() {
|
||||
assert.deepEqual(
|
||||
getMatchingDoubleRelativeDate(getDateValue('10/1/2022'), {unit: 'month'}),
|
||||
[{unit: 'month', quantity: 1}]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
getMatchingDoubleRelativeDate(getDateValue('9/19/2022'), {unit: 'week'}),
|
||||
[{unit: 'week', quantity: -1}, {quantity: 1, unit: 'day'}]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
getMatchingDoubleRelativeDate(getDateValue('9/21/2022'), {unit: 'week'}),
|
||||
[{unit: 'week', quantity: -1}, {quantity: 3, unit: 'day'}]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
getMatchingDoubleRelativeDate(getDateValue('9/30/2022'), {unit: 'month'}),
|
||||
[{unit: 'month', quantity: 0}, {quantity: 29, unit: 'day'}]
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
getMatchingDoubleRelativeDate(getDateValue('10/1/2022'), {unit: 'month'}),
|
||||
[{unit: 'month', quantity: 1}]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diffUnit', function() {
|
||||
it('should work correctly', function() {
|
||||
assert.equal(diffUnit(moment('2022-09-30'), moment('2022-10-01'), 'month'), -1);
|
||||
assert.equal(diffUnit(moment('2022-10-01'), moment('2022-09-30'), 'month'), 1);
|
||||
assert.equal(diffUnit(moment('2022-09-30'), moment('2022-10-01'), 'week'), 0);
|
||||
assert.equal(diffUnit(moment('2022-09-30'), moment('2022-10-02'), 'week'), -1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getDateValue(date: string): number {
|
||||
return moment.tz(date, "MM-DD-YYYY", 'utc').valueOf()/1000;
|
||||
}
|
||||
@@ -2827,6 +2827,28 @@ export async function beginAclTran(api: UserAPI, docId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set the value of a column range filter bound. Helper also support picking relative date
|
||||
* from options for Date columns, simply pass {relative: '2 days ago'} as value.
|
||||
*/
|
||||
export async function setRangeFilterBound(minMax: 'min'|'max', value: string|{relative: string}|null) {
|
||||
await driver.find(`.test-filter-menu-${minMax}`).click();
|
||||
if (typeof value === 'string' || value === null) {
|
||||
await selectAll();
|
||||
await driver.sendKeys(value === null ? Key.DELETE : value);
|
||||
// send TAB to trigger blur event, that will force call on the debounced callback
|
||||
await driver.sendKeys(Key.TAB);
|
||||
} else {
|
||||
await waitToPass(async () => {
|
||||
// makes sure the relative options is opened
|
||||
if (!await driver.find('.grist-floatin-menu').isPresent()) {
|
||||
await driver.find(`.test-filter-menu-${minMax}`).click();
|
||||
}
|
||||
await driver.findContent('.grist-floating-menu li', value.relative).click();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // end of namespace gristUtils
|
||||
|
||||
stackWrapOwnMethods(gristUtils);
|
||||
|
||||
Reference in New Issue
Block a user