diff --git a/docs/column-functionalities/Formatters.md b/docs/column-functionalities/Formatters.md index 3d8608901..e2d8f3f82 100644 --- a/docs/column-functionalities/Formatters.md +++ b/docs/column-functionalities/Formatters.md @@ -20,7 +20,7 @@ For a [UI sample](#ui-sample), scroll down below. ### Provided Formatters `Slickgrid-Universal` ships with a few `Formatters` by default which helps with common fields, you can see the [entire list here](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37). -> **Note** you might not need a Formatter when a simple CSS style is needed, think about using `cssClass` column property instead. +> **Note** you might not need a Formatter when a simple CSS style and class might be enough, think about using `cssClass` column property as much as possible since it has much better perf. #### List of provided `Formatters` - `arrayObjectToCsv`: Takes an array of complex objects converts it to a comma delimited string. @@ -46,6 +46,7 @@ For a [UI sample](#ui-sample), scroll down below. - `dateTimeUs` : Takes a Date object and displays it as an US Date+Time format (MM/DD/YYYY HH:mm:ss) - `dateTimeShortUs`: Takes a Date object and displays it as an US Date+Time (without seconds) format (MM/DD/YYYY HH:mm:ss) - `dateTimeUsAmPm` : Takes a Date object and displays it as an US Date+Time+(am/pm) format (MM/DD/YYYY hh:mm:ss a) +- `dateUtc` : Takes a Date object and displays it as a TZ format (YYYY-MM-DDThh:mm:ssZ) - `decimal`: Display the value as x decimals formatted, defaults to 2 decimals. You can pass "minDecimal" and/or "maxDecimal" to the "params" property. - `dollar`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value. - `dollarColored`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value, change color of text to red/green on negative/positive value @@ -70,10 +71,12 @@ For a [UI sample](#ui-sample), scroll down below. - `translateBoolean`: Takes a boolean value, cast it to upperCase string and finally translates it (i18n). - `tree`: Formatter that must be used when the column is a Tree Data column -**Note:** The list might not always be up to date, you can refer to the [Formatters export](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37) to know exactly which ones are available. +> **Note:** The list is certainly not up to date (especially for Dates), please refer to the [Formatters export](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37) to know exactly which formatters are available. + +> **Note** all Date formatters are formatted using [Tempo](https://tempo.formkit.com/#format-tokens). There are also many more Date formats not shown above, simply visit the [formatters.index](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/formatters.index.ts#L101) to see all available Date/Time formats. ### Usage -To use any of them, you need to import `Formatters` from `Slickgrid-Universal` and add a `formatter: ...` in your column definitions as shown below: +To use any of them, you simply need to import `Formatters` from `Slickgrid-Universal` and add a `formatter: Formatters.xyz` (where `xyx` is the name of the built-in formatter) in your column definitions as shown below: #### TypeSript ```ts @@ -102,38 +105,6 @@ export class Example { } ``` -#### SalesForce (ES6) -For SalesForce the code is nearly the same, the only difference is to add the `Slicker` prefix, so instead of `Formatters.abc` we need to use `Slicker.Formatters.abc` - -```ts -// ... SF_Slickgrid import - - -export class Example { - const Slicker = window.Slicker; - - columnDefinitions: Column[]; - gridOptions: GridOption; - dataset: any[]; - - constructor() { - // define the grid options & columns and then create the grid itself - this.defineGrid(); - } - - defineGrid() { - this.columnDefinitions = [ - { id: 'title', name: 'Title', field: 'title' }, - { id: 'duration', name: 'Duration (days)', field: 'duration' }, - { id: '%', name: '% Complete', field: 'percentComplete', formatter: Slicker.Formatters.percentComplete }, - { id: 'start', name: 'Start', field: 'start', formatter: Slicker.Formatters.dateIso }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Slicker.Formatters.dateIso }, - { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Slicker.Formatters.checkmarkMaterial } - ]; - } -} -``` - ### Extra Arguments/Params What if you want to pass extra arguments that you want to use within the Formatter? You should use `params` for that. For example, let say you have a custom formatter to build a select list (dropdown), you could do it this way: ```ts diff --git a/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md b/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md index df1ecbec5..9b0e29d1d 100644 --- a/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md +++ b/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md @@ -4,7 +4,9 @@ - See the [Editors - Wiki](../Editors.md) for more general info about Editors (validators, event handlers, ...) ### Information -The Date Editor is provided through an external library named [Vanilla-Calendar-Picker](https://github.com/ghiscoding/vanilla-calendar-picker) (a fork of [Vanilla-Calendar-Pro](https://vanilla-calendar.pro)) and all options from that library can be added to your `editorOptions` (see below), so in order to add things like minimum date, disabling dates, ... just review all the [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/docs/reference/additionally/settings) and then add them into `editorOptions`. Also just so you know, `editorOptions` is use by all other editors as well to expose external library like Autocompleter, Multiple-Select, etc... +The Date Editor is provided through an external library named [Vanilla-Calendar-Picker](https://github.com/ghiscoding/vanilla-calendar-picker) (a fork of [Vanilla-Calendar-Pro](https://vanilla-calendar.pro)) and all options from that library can be added to your `editorOptions` (see below), so in order to add things like minimum date, disabling dates, ... just review all the [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/docs/reference/additionally/settings) and then add them into `editorOptions`. We use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format (when `type`, `outputType` and/or `saveType` are provided in your column definition) + +> **Note** Also just so you know, `editorOptions` is used by all other editors as well to expose external library like Autocompleter, Multiple-Select, etc... ### Demo [Demo Page](https://ghiscoding.github.io/slickgrid-universal/#/example12) | [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts) diff --git a/docs/column-functionalities/filters/Compound-Filters.md b/docs/column-functionalities/filters/Compound-Filters.md index adfa04e1d..bd268a26c 100644 --- a/docs/column-functionalities/filters/Compound-Filters.md +++ b/docs/column-functionalities/filters/Compound-Filters.md @@ -51,7 +51,7 @@ The column definition `type` will affect the list of Operators shown, for exampl ### How to use CompoundDate Filter -Again set the column definition flag `filterable: true` and use the filter type `Filters.compoundDate`. Here is an example with a full column definition: +As any other columns, set the column definition flag `filterable: true` and use the filter type `Filters.compoundDate`. Here is an example with a full column definition: ```ts // define you columns, in this demo Effort Driven will use a Select Filter this.columnDefinitions = [ @@ -75,6 +75,8 @@ this.gridOptions = { }; ``` +> **Note** we use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format via the `type` option when provided in your column definition. + #### Dealing with different input/ouput dates (example: UTC) What if your date input (from your dataset) has a different output on the screen (UI)? In that case, you will most probably have a Formatter and type representing the input type, we also provided an `outputType` that can be used to deal with that case. diff --git a/docs/column-functionalities/filters/Range-Filters.md b/docs/column-functionalities/filters/Range-Filters.md index f0b3f37ad..0a204da01 100644 --- a/docs/column-functionalities/filters/Range-Filters.md +++ b/docs/column-functionalities/filters/Range-Filters.md @@ -134,6 +134,8 @@ this.gridOptions = { ### Using a Date Range Filter The date range filter allows you to search data between 2 dates (it uses [Vanilla-Calendar Range](https://vanilla-calendar.pro/) feature). +> **Note** we use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format via the `type` option when provided in your column definition. + ##### Component import { Filters, Formatters, GridOption, OperatorType, VanillaCalendarOption } from '@slickgrid-universal/common'; diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index e5fbfad0f..19afdd861 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -9,7 +9,7 @@ To get started follow any of these instruction Wikis depending on your choice of | Angular | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/angular-slickgrid/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/Angular-Slickgrid/) | | Aurelia | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/aurelia-slickgrid/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/aurelia-slickgrid/) | | React | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/slickgrid-react/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/slickgrid-react/) | -| Salesforce | Installation | [Print Screen](https://github.com/ghiscoding/slickgrid-universal/wiki#salesforce-demo---print-screen) | +| Salesforce | [Salesforce Installation](getting-started/installation-salesforce.md) | [Print Screen](https://github.com/ghiscoding/slickgrid-universal/wiki#salesforce-demo---print-screen) | | ---- | | | **General Subjects** diff --git a/docs/migrations/migration-to-5.x.md b/docs/migrations/migration-to-5.x.md index 5c5f250d2..5e0f3b810 100644 --- a/docs/migrations/migration-to-5.x.md +++ b/docs/migrations/migration-to-5.x.md @@ -31,6 +31,8 @@ The goal of this new release was mainly to improve UI/UX (mostly for Dark Mode) - Bootstrap >=v5.x (or any other UI framework) - SASS >=v1.35 (`dart-sass`) - migrated from Flatpickr to Vanilla-Calendar (visit [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/) for demos and docs) + - migrated from MomentJS to [Tempo](https://tempo.formkit.com/) (by the FormKit +team) > **Note** for the entire list of tasks & code changes applied in this release, you may want to take a look at the [Roadmap to 5.0](https://github.com/ghiscoding/slickgrid-universal/discussions/1482) Discussion. @@ -228,6 +230,7 @@ if you want to read the Editor class (e.g. `Editors.longText`), you can now refe ## Grid Functionalities +### Sanitizer (DOMPurify) `DOMPurify` is now completely optional via the `sanitizer` grid option and you must now provide it yourself. The main reason to make it optional was because most users would use `dompurify` but some users who require SSR support would want to use `isomorphic-dompurify`. You could also skip the `sanitizer` configuration, but that is not recommended. > **Note** even if the `sanitizer` is optional, we **strongly suggest** that you configure it as a global grid option to avoid possible XSS attacks from your data and also to be CSP compliant. Note that for Salesforce users, you do not have to configure it since Salesforce already use DOMPurify internally. @@ -239,4 +242,9 @@ this.gridOptions = { }; ``` -> **Note** If you're wondering about the `ADD_ATTR: ['level']`, well the "level" is a custom attribute used by SlickGrid Grouping/Draggable Grouping to track the grouping level depth and it must be kept. \ No newline at end of file +> **Note** If you're wondering about the `ADD_ATTR: ['level']`, well the "level" is a custom attribute used by SlickGrid Grouping/Draggable Grouping to track the grouping level depth and it must be kept. + +### From MomentJS to Tempo +I wanted to replace MomentJS for a long time now (it's been deprecated for years and is CJS only), but it was really hard to find a good replacement (I tried DayJS, Luxon, date-fns and they all had problems)... and here comes [Tempo](https://tempo.formkit.com/)! With Tempo, I was finally able to migrate by taking advantage of `parse()` and `format()` Tempo functions which are the most important for our use case. The library also has plenty of extra optional functions as well, like `addDay()`, `diffDays()`, ... Another great thing about Tempo is that they use the same format [tokens](https://tempo.formkit.com/#format-tokens) as MomentJS, so the conversion on that side was super easy. + +This migration should be transparent to most users like you, however if you were using MomentJS then I would suggest to consider trying [Tempo](https://tempo.formkit.com/) in your project in order to modernize your project and also lower your dependencies count. The other great advantage of Tempo is that it's ESM and it helps a lot in decreasing our build size footprint because of ESM Tree Shacking feature. \ No newline at end of file diff --git a/examples/vite-demo-vanilla-bundle/package.json b/examples/vite-demo-vanilla-bundle/package.json index 8ff60fb9a..3c5430e1d 100644 --- a/examples/vite-demo-vanilla-bundle/package.json +++ b/examples/vite-demo-vanilla-bundle/package.json @@ -12,6 +12,7 @@ "dependencies": { "@faker-js/faker": "^8.4.1", "@fnando/sparkline": "^0.3.10", + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/composite-editor-component": "workspace:~", @@ -27,7 +28,6 @@ "bulma": "^1.0.0", "dompurify": "^3.1.2", "fetch-jsonp": "^1.3.0", - "moment-tiny": "^2.30.4", "multiple-select-vanilla": "^3.1.3", "rxjs": "^7.8.1", "vanilla-calendar-picker": "^2.11.4", diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example10.ts b/examples/vite-demo-vanilla-bundle/src/examples/example10.ts index de452dee5..9fcf8b337 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example10.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example10.ts @@ -13,8 +13,8 @@ import { import { BindingEventService } from '@slickgrid-universal/binding'; import { GraphqlService, type GraphqlPaginatedResult, type GraphqlServiceApi, type GraphqlServiceOption, } from '@slickgrid-universal/graphql'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; +import { addDay, format } from '@formkit/tempo'; import { type MultipleSelectOption } from 'multiple-select-vanilla'; -import moment from 'moment-tiny'; import { ExampleGridOptions } from './example-grid-options'; import type { TranslateService } from '../translate.service'; @@ -130,8 +130,8 @@ export default class Example10 { }, ]; - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); this.gridOptions = { enableAutoTooltip: true, @@ -303,8 +303,8 @@ export default class Example10 { } setFiltersDynamically() { - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.sgb.filterService.updateFilters([ @@ -325,8 +325,8 @@ export default class Example10 { } resetToOriginalPresets() { - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); this.sgb?.filterService.updateFilters([ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example11.ts b/examples/vite-demo-vanilla-bundle/src/examples/example11.ts index ee875ac47..38d521ea9 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example11.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example11.ts @@ -28,7 +28,6 @@ import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { type MultipleSelectOption } from 'multiple-select-vanilla'; -import moment from 'moment-tiny'; import exampleModal from './example11-modal.html?raw'; import Example11Modal from './example11-modal'; @@ -82,7 +81,7 @@ export default class Example11 { sgb: SlickVanillaGridBundle; gridContainerElm: HTMLDivElement; viewSelectElm: HTMLSelectElement; - currentYear = moment().year(); + currentYear = new Date().getFullYear(); defaultPredefinedPresets = [ { label: 'Tasks Finished in Previous Years (wo/Product,Country)', diff --git a/package.json b/package.json index ebb8a8ef4..0ea3a079e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@4tw/cypress-drag-drop": "^2.2.5", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", + "@formkit/tempo": "^0.1.1", "@jest/types": "^29.6.3", "@lerna-lite/cli": "^3.3.3", "@lerna-lite/publish": "^3.3.3", @@ -81,7 +82,6 @@ "jest-extended": "^4.0.2", "jsdom": "^24.0.0", "jsdom-global": "^3.0.2", - "moment-tiny": "^2.30.4", "npm-run-all2": "^6.1.2", "pnpm": "^8.15.8", "rimraf": "^5.0.5", diff --git a/packages/common/package.json b/packages/common/package.json index eb1bb2ace..ce66a21d8 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -66,6 +66,7 @@ "not dead" ], "dependencies": { + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", "@slickgrid-universal/event-pub-sub": "workspace:~", "@slickgrid-universal/utils": "workspace:~", @@ -74,7 +75,6 @@ "autocompleter": "^9.2.1", "dequal": "^2.0.3", "excel-builder-vanilla": "3.0.1", - "moment-tiny": "^2.30.4", "multiple-select-vanilla": "^3.1.3", "sortablejs": "^1.15.2", "un-flatten-tree": "^2.0.12", diff --git a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts index 4650ec100..b433620e2 100644 --- a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts +++ b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts @@ -1,10 +1,10 @@ +import { format } from '@formkit/tempo'; import type { AutocompleteItem } from 'autocompleter'; import type { IOptions } from 'vanilla-calendar-picker'; -import moment from 'moment-tiny'; import type { AutocompleterOption, Column, ColumnEditor, ColumnFilter } from '../interfaces/index'; -import { formatDateByFieldType, mapMomentDateFormatWithFieldType } from '../services'; import { FieldType } from '../enums'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; /** * add loading class ".slick-autocomplete-loading" to the Kraaden Autocomplete input element @@ -37,23 +37,26 @@ export function setPickerDates(dateInputElm: HTMLInputElement, pickerOptions: IO const currentDateOrDates = dateValues; const outputFieldType = columnDef.outputType || colEditorOrFilter.type || columnDef.type || FieldType.dateUtc; const inputFieldType = colEditorOrFilter.type || columnDef.type; - const isoFormat = mapMomentDateFormatWithFieldType(FieldType.dateIso) as string; - const inputFormat = inputFieldType ? mapMomentDateFormatWithFieldType(inputFieldType) : ''; + const isoFormat = mapTempoDateFormatWithFieldType(FieldType.dateIso) as string; + const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; const initialDates = Array.isArray(currentDateOrDates) ? currentDateOrDates : [(currentDateOrDates || '') as string]; if (initialDates.length && initialDates[0]) { - const pickerDates = []; + const pickerDates: Date[] = []; for (const initialDate of initialDates) { - const momentDate = moment(initialDate, inputFormat); - pickerDates.push(momentDate); + const date = initialDate instanceof Date ? initialDate : tryParseDate(initialDate, inputFormat); + if (date) { + pickerDates.push(date); + } } - const singleinputFormat = Array.isArray(inputFormat) ? inputFormat[0] : inputFormat; - pickerOptions.settings!.selected = { - dates: [pickerDates.map(p => p.format(isoFormat)).join(':')], - month: pickerDates[0].month(), - year: pickerDates[0].year(), - time: singleinputFormat.toLowerCase().includes('h') ? pickerDates[0].format('HH:mm') : undefined, - }; + if (pickerDates.length) { + pickerOptions.settings!.selected = { + dates: [pickerDates.map(p => format(p, isoFormat)).join(':')], + month: pickerDates[0].getMonth(), + year: pickerDates[0].getFullYear(), + time: inputFormat === 'ISO8601' || (inputFormat || '').toLowerCase().includes('h') ? format(pickerDates[0], 'HH:mm') : undefined, + }; + } dateInputElm.value = initialDates.length ? pickerDates.map(p => formatDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; } } \ No newline at end of file diff --git a/packages/common/src/editors/__tests__/dateEditor.spec.ts b/packages/common/src/editors/__tests__/dateEditor.spec.ts index 7199bb7e4..622f3d670 100644 --- a/packages/common/src/editors/__tests__/dateEditor.spec.ts +++ b/packages/common/src/editors/__tests__/dateEditor.spec.ts @@ -1,4 +1,4 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; import { VanillaCalendar } from 'vanilla-calendar-picker'; import { Editors } from '../index'; @@ -258,8 +258,8 @@ describe('DateEditor', () => { editor.focus(); const editorInputElm = editor.editorDomElement; editorInputElm.value = '2024-04-02T16:02:02.239Z'; - editor.calendarInstance!.actions!.clickDay!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock] } as unknown as VanillaCalendar); - editor.calendarInstance!.actions!.changeToInput!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], hide: jest.fn() } as unknown as VanillaCalendar); + editor.calendarInstance!.actions!.clickDay!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], selectedHours: 11, selectedMinutes: 2 } as unknown as VanillaCalendar); + editor.calendarInstance!.actions!.changeToInput!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], selectedHours: 11, selectedMinutes: 2, hide: jest.fn() } as unknown as VanillaCalendar); expect(editor.isValueChanged()).toBe(true); expect(editor.isValueTouched()).toBe(true); @@ -354,7 +354,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD'), isActive: true }); + expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD'), isActive: true }); }); it('should apply the value to the startDate property with "outputType" format with a field having dot notation (complex object) that passes validation', () => { @@ -364,13 +364,13 @@ describe('DateEditor', () => { mockColumn.field = 'employee.startDate'; mockItemData = { id: 1, employee: { startDate: '2001-04-05T11:33:42.000Z' }, isActive: true }; - const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0)); + const newDate = new Date(Date.UTC(2001, 10, 23, 16, 2, 2, 0)); editor = new DateEditor(editorArguments); jest.runAllTimers(); editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, employee: { startDate: moment(newDate).format('DD/MM/YYYY HH:mm') }, isActive: true }); + expect(mockItemData).toEqual({ id: 1, employee: { startDate: format(newDate, 'D/M/YYYY HH:mm') }, isActive: true }); }); it('should apply the value to the startDate property with output format defined by "saveOutputType" when it passes validation', () => { @@ -385,7 +385,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a'), isActive: true }); + expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD hh:mm:ss a', 'en-US'), isActive: true }); }); it('should return item data with an empty string in its value when it fails the custom validation', () => { diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index 8ae629f3d..48a548188 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -1,7 +1,7 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { createDomElement, emptyElement, extend, setDeepValue } from '@slickgrid-universal/utils'; +import { parse } from '@formkit/tempo'; import { VanillaCalendar, type IOptions } from 'vanilla-calendar-picker'; -import moment from 'moment-tiny'; import { Constants } from './../constants'; import { FieldType } from '../enums/index'; @@ -16,10 +16,11 @@ import type { GridOption, VanillaCalendarOption, } from './../interfaces/index'; -import { formatDateByFieldType, getDescendantProperty, mapMomentDateFormatWithFieldType, } from './../services/utilities'; +import { getDescendantProperty, } from './../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import { SlickEventData, type SlickGrid } from '../core/index'; import { setPickerDates } from '../commonEditorFilter'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; /* * An example of a date picker editor using Vanilla-Calendar-Picker @@ -105,20 +106,17 @@ export class DateEditor implements Editor { if (this.args && this.columnDef) { const compositeEditorOptions = this.args.compositeEditorOptions; const columnId = this.columnDef?.id ?? ''; - const gridOptions = (this.args.grid.getOptions() || {}) as GridOption; + const gridOptions: GridOption = this.args.grid.getOptions() || {}; this.defaultDate = this.args.item?.[this.columnDef.field]; const outputFieldType = this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); - if (Array.isArray(outputFormat)) { - outputFormat = outputFormat[0]; - } + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const currentLocale = this._translaterService?.getCurrentLanguage?.() || gridOptions.locale || 'en'; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) - if (outputFormat && (outputFormat === 'Z' || outputFormat.toLowerCase().includes('h'))) { + // add the time picker when format is UTC (TZ - ISO8601) or has the 'h' (meaning hours) + if (outputFormat && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } - const pickerFormat = mapMomentDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); + const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); const pickerOptions: IOptions = { input: true, @@ -131,19 +129,19 @@ export class DateEditor implements Editor { }, changeToInput: (_e, self) => { if (self.HTMLInputElement) { - let chosenDate = ''; + let selectedDate = ''; if (self.selectedDates[0]) { - chosenDate = self.selectedDates[0]; + selectedDate = self.selectedDates[0]; self.HTMLInputElement.value = formatDateByFieldType(self.selectedDates[0], undefined, outputFieldType); } else { self.HTMLInputElement.value = ''; } - if (this.hasTimePicker) { - const momentDate = moment(chosenDate, pickerFormat); - momentDate.hours(+(self.selectedHours || 0)); - momentDate.minute(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatDateByFieldType(momentDate, undefined, outputFieldType); + if (selectedDate && this.hasTimePicker) { + const tempoDate = parse(selectedDate, pickerFormat); + tempoDate.setHours(+(self.selectedHours || 0)); + tempoDate.setMinutes(+(self.selectedMinutes || 0)); + self.HTMLInputElement.value = formatDateByFieldType(tempoDate, undefined, outputFieldType); } if (this._lastClickIsDate) { @@ -164,7 +162,7 @@ export class DateEditor implements Editor { }, }; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format includes time (hours/minutes) if (this.hasTimePicker) { pickerOptions.settings!.selection = { time: 24 diff --git a/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts b/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts index 877afcc6e..b838316b3 100644 --- a/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts +++ b/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts @@ -1,5 +1,3 @@ -import moment from 'moment-tiny'; - import { getParsedSearchTermsByFieldType } from '../filterConditionProcesses'; describe('getParsedSearchTermsByFieldType method', () => { @@ -13,15 +11,6 @@ describe('getParsedSearchTermsByFieldType method', () => { expect(result2).toEqual(true); }); - it('should get a moment date object when parsing any date type', () => { - const inputDate = '2001-03-03T10:11:22.456Z'; - const result = getParsedSearchTermsByFieldType([inputDate], 'dateUtc'); - - expect(result![0]).toBeObject(); - expect(moment.isMoment(result![0])).toBeTrue(); - expect(result![0].format('YYYY-MM-DD')).toBe('2001-03-03'); - }); - it('should get parsed result as a number array when providing an array of searchTerms that are string of numbers', () => { const input1 = ['0']; const input2 = ['0', 12]; diff --git a/packages/common/src/filter-conditions/dateFilterCondition.ts b/packages/common/src/filter-conditions/dateFilterCondition.ts index d8dbf9009..89892605a 100644 --- a/packages/common/src/filter-conditions/dateFilterCondition.ts +++ b/packages/common/src/filter-conditions/dateFilterCondition.ts @@ -1,30 +1,31 @@ -import moment from 'moment-tiny'; +import { dayStart } from '@formkit/tempo'; import { FieldType, OperatorType, type SearchTerm } from '../enums/index'; import type { FilterConditionOption } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; import { testFilterCondition } from './filterUtilities'; +import { mapTempoDateFormatWithFieldType, tryParseDate } from '../services'; /** * Execute Date filter condition check on each cell and use correct date format depending on it's field type (or filterSearchType when that is provided) */ -export function executeDateFilterCondition(options: FilterConditionOption, parsedSearchDates: any[]): boolean { +export function executeDateFilterCondition(options: FilterConditionOption, parsedSearchDates: Array): boolean { const filterSearchType = options && (options.filterSearchType || options.fieldType) || FieldType.dateIso; - const FORMAT = mapMomentDateFormatWithFieldType(filterSearchType); - const SINGLE_FORMAT = Array.isArray(FORMAT) ? FORMAT[0] : FORMAT; + const FORMAT = mapTempoDateFormatWithFieldType(filterSearchType); const [searchDate1, searchDate2] = parsedSearchDates; - // cell value in moment format - const dateCell = moment(options.cellValue, FORMAT, true); + // cell value in Date format + const dateCell = tryParseDate(options.cellValue, FORMAT, true); // return when cell value is not a valid date - if ((!searchDate1 && !searchDate2) || !dateCell.isValid()) { + if ((!searchDate1 && !searchDate2) || !dateCell) { return false; } // when comparing with Dates only (without time), we need to disregard the time portion, we can do so by setting our time to start at midnight // ref, see https://stackoverflow.com/a/19699447/1212166 - const dateCellTimestamp = SINGLE_FORMAT.toLowerCase().includes('h') ? dateCell.valueOf() : dateCell.clone().startOf('day').valueOf(); + const dateCellTimestamp = FORMAT === 'ISO8601' || FORMAT.toLowerCase().includes('h') + ? dateCell.valueOf() + : dayStart(new Date(dateCell)).valueOf(); // having 2 search dates, we assume that it's a date range filtering and we'll compare against both dates if (searchDate1 && searchDate2) { @@ -39,18 +40,20 @@ export function executeDateFilterCondition(options: FilterConditionOption, parse } // comparing against a single search date - const dateSearchTimestamp1 = SINGLE_FORMAT.toLowerCase().includes('h') ? searchDate1.valueOf() : searchDate1.clone().startOf('day').valueOf(); + const dateSearchTimestamp1 = FORMAT === 'ISO8601' || FORMAT.toLowerCase().includes('h') + ? searchDate1.valueOf() + : dayStart(new Date(searchDate1)).valueOf(); return testFilterCondition(options.operator || '==', dateCellTimestamp, dateSearchTimestamp1); } /** - * From our search filter value(s), get the parsed value(s), they are parsed as Moment object(s). + * From our search filter value(s), get the parsed value(s), they are parsed as Date objects. * This is called only once per filter before running the actual filter condition check on each cell */ export function getFilterParsedDates(inputSearchTerms: SearchTerm[] | undefined, inputFilterSearchType: typeof FieldType[keyof typeof FieldType]): SearchTerm[] { const searchTerms = Array.isArray(inputSearchTerms) && inputSearchTerms || []; const filterSearchType = inputFilterSearchType || FieldType.dateIso; - const FORMAT = mapMomentDateFormatWithFieldType(filterSearchType); + const FORMAT = mapTempoDateFormatWithFieldType(filterSearchType); const parsedSearchValues: any[] = []; @@ -58,18 +61,18 @@ export function getFilterParsedDates(inputSearchTerms: SearchTerm[] | undefined, const searchValues = (searchTerms.length === 2) ? searchTerms : (searchTerms[0] as string).split('..'); const searchValue1 = (Array.isArray(searchValues) && searchValues[0] || '') as Date | string; const searchValue2 = (Array.isArray(searchValues) && searchValues[1] || '') as Date | string; - const searchDate1 = moment(searchValue1, FORMAT, true); - const searchDate2 = moment(searchValue2, FORMAT, true); + const searchDate1 = tryParseDate(searchValue1, FORMAT, true); + const searchDate2 = tryParseDate(searchValue2, FORMAT, true); // return if any of the 2 values are invalid dates - if (!searchDate1.isValid() || !searchDate2.isValid()) { + if (!searchDate1 || !searchDate2) { return []; } parsedSearchValues.push(searchDate1, searchDate2); } else { // return if the search term is an invalid date - const searchDate1 = moment(searchTerms[0] as Date | string, FORMAT, true); - if (!searchDate1.isValid()) { + const searchDate1 = tryParseDate(searchTerms[0] as Date | string, FORMAT, true); + if (!searchDate1) { return []; } parsedSearchValues.push(searchDate1); diff --git a/packages/common/src/filter-conditions/filterConditionProcesses.ts b/packages/common/src/filter-conditions/filterConditionProcesses.ts index ffae472c1..95b1bd3d2 100644 --- a/packages/common/src/filter-conditions/filterConditionProcesses.ts +++ b/packages/common/src/filter-conditions/filterConditionProcesses.ts @@ -44,7 +44,7 @@ export const executeFilterConditionTest: FilterCondition = ((options: FilterCond }) as FilterCondition; /** - * From our search filter value(s), get their parsed value(s), for example a "dateIso" filter will be parsed as Moment object. + * From our search filter value(s), get their parsed value(s), for example a "dateIso" filter will be parsed as Date object. * Then later when we execute the filtering checks, we won't need to re-parse all search value(s) again and again. * So this is called only once, for each search filter that is, prior to running the actual filter condition checks on each cell afterward. */ diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index 4c1697f66..7c03a7901 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -1,4 +1,5 @@ import 'jest-extended'; +import { format } from '@formkit/tempo'; import { VanillaCalendar } from 'vanilla-calendar-picker'; import { Filters } from '../filters.index'; @@ -7,6 +8,7 @@ import { Column, FilterArguments, GridOption } from '../../interfaces/index'; import { CompoundDateFilter } from '../compoundDateFilter'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; import { SlickGrid } from '../../core/index'; +import { mapTempoDateFormatWithFieldType } from '../../services/dateUtils'; const containerId = 'demo-container'; @@ -321,7 +323,7 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-02T00:00:00.000Z'); + expect(format(filter.currentDateOrDates![0], mapTempoDateFormatWithFieldType(FieldType.dateTimeIso))).toBe('2000-01-02 00:00:00'); expect(filterInputElm.value).toBe('2000-01-02'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-02'], shouldTriggerQuery: true }); }); @@ -341,7 +343,7 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-02T00:00:00.000Z'); + expect(format(filter.currentDateOrDates![0], mapTempoDateFormatWithFieldType(FieldType.dateTimeIso))).toBe('2000-01-02 00:00:00'); expect(filterInputElm.value).toBe('2000-01-02'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-02'], shouldTriggerQuery: true }); }); @@ -449,11 +451,8 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - // expect(filter.currentDateOrDates.toISOString()).toBe('2001-01-02T21:02'); - expect(filterInputElm.value).toBe('02/01/2001 16:02'); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { - columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true - }); + expect(filterInputElm.value).toBe('2/1/2001 16:02'); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true }); }); it('should have a value with date & time in the picker when using no "outputType" which will default to UTC date', () => { diff --git a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts index 463028318..4035d0ec2 100644 --- a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts +++ b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts @@ -153,7 +153,7 @@ describe('DateRangeFilter', () => { it('should trigger input change event and expect the callback to be called with the date provided in the input', () => { mockColumn.filter!.operator = 'RangeInclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -183,7 +183,7 @@ describe('DateRangeFilter', () => { it('should pass a different operator then trigger an input change event and expect the callback to be called with the date provided in the input', () => { mockColumn.filter!.operator = 'RangeExclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -197,7 +197,7 @@ describe('DateRangeFilter', () => { }); it('should create the input filter with a default search terms when passed as a filter argument', () => { - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filterArguments.searchTerms = ['2001-01-02', '2001-01-13']; mockColumn.filter!.operator = 'RangeInclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); @@ -309,7 +309,7 @@ describe('DateRangeFilter', () => { mockColumn.outputType = FieldType.dateTimeIsoAmPm; mockColumn.filter!.operator = '>'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -333,7 +333,7 @@ describe('DateRangeFilter', () => { mockColumn.outputType = FieldType.dateTimeIsoAmPm; mockColumn.filter!.operator = '>'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -355,7 +355,7 @@ describe('DateRangeFilter', () => { filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']; mockColumn.filter!.operator = '<='; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index a69115551..0fbefdc3a 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -1,7 +1,7 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { createDomElement, emptyElement, extend, } from '@slickgrid-universal/utils'; +import { format, parse } from '@formkit/tempo'; import { VanillaCalendar, type IOptions } from 'vanilla-calendar-picker'; -import moment, { type Moment } from 'moment-tiny'; import { FieldType, @@ -19,7 +19,8 @@ import type { OperatorDetail, } from '../interfaces/index'; import { buildSelectOperator, compoundOperatorNumeric } from './filterUtilities'; -import { formatDateByFieldType, mapMomentDateFormatWithFieldType, mapOperatorToShorthandDesignation } from '../services/utilities'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; +import { mapOperatorToShorthandDesignation } from '../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import type { SlickGrid } from '../core/index'; import { setPickerDates } from '../commonEditorFilter'; @@ -237,17 +238,14 @@ export class DateFilter implements Filter { const columnId = this.columnDef?.id ?? ''; const columnFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; const outputFieldType = this.columnDef.outputType || this.columnFilter.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); - if (Array.isArray(outputFormat)) { - outputFormat = outputFormat[0]; - } + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const inputFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) - if (outputFormat && this.inputFilterType !== 'range' && outputFormat.toLowerCase().includes('h')) { + // add the time picker when format is UTC (TZ - ISO8601) or has the 'h' (meaning hours) + if (outputFormat && this.inputFilterType !== 'range' && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } - const pickerFormat = mapMomentDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); + const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); // get current locale, if user defined a custom locale just use or get it the Translate Service if it exist else just use English const currentLocale = ((this.filterOptions?.locale ?? this.translaterService?.getCurrentLanguage?.()) || this.gridOptions.locale || 'en') as string; @@ -286,7 +284,7 @@ export class DateFilter implements Filter { }, changeToInput: (_e, self) => { if (self.HTMLInputElement) { - let outDates: Array = []; + let outDates: Array = []; let firstDate = ''; let lastDate = ''; // when using date range @@ -294,8 +292,8 @@ export class DateFilter implements Filter { self.selectedDates.sort((a, b) => +new Date(a) - +new Date(b)); firstDate = self.selectedDates[0]; lastDate = self.selectedDates[self.selectedDates.length - 1]; - const firstDisplayDate = moment(self.selectedDates[0]).format(outputFormat); - const lastDisplayDate = moment(lastDate).format(outputFormat); + const firstDisplayDate = format(self.selectedDates[0], outputFormat, 'en-US'); + const lastDisplayDate = format(lastDate, outputFormat, 'en-US'); self.HTMLInputElement.value = `${firstDisplayDate} — ${lastDisplayDate}`; outDates = [firstDate, lastDate]; } else if (self.selectedDates[0]) { @@ -307,11 +305,11 @@ export class DateFilter implements Filter { } if (this.hasTimePicker && firstDate) { - const momentDate = moment(firstDate, pickerFormat); - momentDate.hours(+(self.selectedHours || 0)); - momentDate.minute(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatDateByFieldType(momentDate, undefined, outputFieldType); - outDates = [momentDate]; + const tempoDate = parse(firstDate, pickerFormat); + tempoDate.setHours(+(self.selectedHours || 0)); + tempoDate.setMinutes(+(self.selectedMinutes || 0)); + self.HTMLInputElement.value = formatDateByFieldType(tempoDate, undefined, outputFieldType); + outDates = [tempoDate]; } if (this.inputFilterType === 'compound') { @@ -322,7 +320,8 @@ export class DateFilter implements Filter { this._currentValue = this._currentDateStrings.join('..'); } } - this._currentDateOrDates = outDates.map(dateStr => dateStr instanceof moment ? (dateStr as Moment).toDate() : new Date(dateStr as string)); + + this._currentDateOrDates = outDates.map(d => d instanceof Date ? d : parse(d, pickerFormat)); // when using the time picker, we can simulate a keyup event to avoid multiple backend request // since backend request are only executed after user start typing, changing the time should be treated the same way @@ -373,7 +372,7 @@ export class DateFilter implements Filter { }; } - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format includes time (hours/minutes) if (this.hasTimePicker) { pickerOptions.settings!.selection ??= {}; pickerOptions.settings!.selection.time = 24; diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts index 37cf2d41d..78831202d 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 AM'); + expect(result).toBe('01/05/19 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 AM'); + expect(result).toBe('01/05/19 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 8:36:7 PM'); + expect(result).toBe('01/05/19 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts index 2edfdee9f..0e80bfe7b 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 am'); + expect(result).toBe('01/05/19 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 am'); + expect(result).toBe('01/05/19 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 8:36:7 pm'); + expect(result).toBe('01/05/19 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts index 68595f5f0..b8a12bb1f 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShort Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShort(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7'); + expect(result).toBe('01/05/19 02:36:07'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7'); + expect(result).toBe('01/05/19 02:36:07'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 20:36:7'); + expect(result).toBe('01/05/19 20:36:07'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts index b665b3753..ad4c64f24 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 AM'); + expect(result).toBe('05/01/19 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 AM'); + expect(result).toBe('05/01/19 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 8:36:7 PM'); + expect(result).toBe('05/01/19 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts index d2b86931d..e47f4f1ab 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 am'); + expect(result).toBe('05/01/19 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 am'); + expect(result).toBe('05/01/19 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 8:36:7 pm'); + expect(result).toBe('05/01/19 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts index 99001ff1c..265e675b1 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShort Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShort(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7'); + expect(result).toBe('05/01/19 02:36:07'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7'); + expect(result).toBe('05/01/19 02:36:07'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 20:36:7'); + expect(result).toBe('05/01/19 20:36:07'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts index e6369c5d1..ff666b297 100644 --- a/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateUtc Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07Z'); const result = Formatters.dateUtc(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-04-30T21:36:07.000-05:00'); + expect(result).toBe('2019-05-01T02:36:07.000Z'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07Z'); const result = Formatters.dateUtc(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-04-30T21:36:07.000-05:00'); + expect(result).toBe('2019-05-01T02:36:07.000Z'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07Z'); const result = Formatters.dateUtc(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01T15:36:07.000-05:00'); + expect(result).toBe('2019-05-01T20:36:07.000Z'); }); }); diff --git a/packages/common/src/formatters/formatterUtilities.ts b/packages/common/src/formatters/formatterUtilities.ts index 116168c28..c0f970311 100644 --- a/packages/common/src/formatters/formatterUtilities.ts +++ b/packages/common/src/formatters/formatterUtilities.ts @@ -1,12 +1,12 @@ +import { format } from '@formkit/tempo'; import { getHtmlStringOutput, isPrimitiveOrHTML, stripTags } from '@slickgrid-universal/utils'; -import moment from 'moment-tiny'; import { FieldType } from '../enums/fieldType.enum'; import type { Column, ExcelExportOption, Formatter, FormatterResultWithHtml, FormatterResultWithText, GridOption, TextExportOption } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; import { multipleFormatter } from './multipleFormatter'; import { Constants } from '../constants'; import { type SlickGrid } from '../core/index'; +import { mapTempoDateFormatWithFieldType, toUtcDate, tryParseDate } from '../services/dateUtils'; export type FormatterType = 'group' | 'cell'; export type NumberType = 'decimal' | 'currency' | 'percent' | 'regular'; @@ -96,22 +96,23 @@ export function getValueFromParamsOrFormatterOptions(optionName: string, columnD /** From a FieldType, return the associated date Formatter */ export function getAssociatedDateFormatter(fieldType: typeof FieldType[keyof typeof FieldType], defaultSeparator: string): Formatter { - let defaultDateFormat = mapMomentDateFormatWithFieldType(fieldType); - if (Array.isArray(defaultDateFormat)) { - defaultDateFormat = defaultDateFormat[0]; - } + const defaultDateFormat = mapTempoDateFormatWithFieldType(fieldType, true); return (_row: number, _cell: number, value: any, columnDef: Column, _dataContext: any, grid: SlickGrid) => { const gridOptions = ((grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}) as GridOption; const customSeparator = gridOptions?.formatterOptions?.dateSeparator ?? defaultSeparator; const inputType = columnDef?.type ?? FieldType.date; - const inputDateFormat = mapMomentDateFormatWithFieldType(inputType); + const inputDateFormat = mapTempoDateFormatWithFieldType(inputType, true); const isParsingAsUtc = columnDef?.params?.parseDateAsUtc ?? false; - const isDateValid = moment(value, inputDateFormat, false).isValid(); + const date = tryParseDate(value, inputDateFormat); let outputDate = value; - if (value && isDateValid) { - outputDate = isParsingAsUtc ? moment.utc(value).format(defaultDateFormat) : moment(value).format(defaultDateFormat); + if (date) { + let d = value; + if (isParsingAsUtc) { + d = toUtcDate(date); + } + outputDate = format(d, defaultDateFormat, 'en-US'); } // user can customize the separator through the "formatterOptions" diff --git a/packages/common/src/services/__tests__/dateUtils.spec.ts b/packages/common/src/services/__tests__/dateUtils.spec.ts new file mode 100644 index 000000000..205c23839 --- /dev/null +++ b/packages/common/src/services/__tests__/dateUtils.spec.ts @@ -0,0 +1,170 @@ +import 'jest-extended'; + +import { FieldType } from '../../enums/index'; +import { mapTempoDateFormatWithFieldType, parseUtcDate } from '../dateUtils'; + +describe('Service/Utilies', () => { + describe('mapTempoDateFormatWithFieldType method', () => { + it('should return a Date in dateTime/dateTimeIso format', () => { + const output1 = mapTempoDateFormatWithFieldType(FieldType.dateTime); + const output2 = mapTempoDateFormatWithFieldType(FieldType.dateTimeIso); + expect(output1).toBe('YYYY-MM-DD HH:mm:ss'); + expect(output2).toBe('YYYY-MM-DD HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortIso format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortIso); + expect(output).toBe('YYYY-MM-DD HH:mm'); + }); + + it('should return a Date in dateTimeIsoAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeIsoAmPm); + expect(output).toBe('YYYY-MM-DD hh:mm:ss a'); + }); + + it('should return a Date in dateTimeIsoAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeIsoAM_PM); + expect(output).toBe('YYYY-MM-DD hh:mm:ss A'); + }); + + it('should return a Date in dateEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuro); + expect(output).toBe('DD/MM/YYYY'); + }); + + it('should return a Date in dateEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuroShort); + expect(output).toEqual('D/M/YY'); + }); + + it('should return a Date in dateEuroShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuroShort, true); + expect(output).toEqual('DD/MM/YY'); + }); + + it('should return a Date in dateTimeEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuro); + expect(output).toBe('DD/MM/YYYY HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortEuro); + expect(output).toEqual('D/M/YYYY H:m'); + }); + + it('should return a Date in dateTimeShortEuro format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortEuro, true); + expect(output).toEqual('DD/MM/YYYY HH:mm'); + }); + + it('should return a Date in dateTimeEuroAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroAmPm); + expect(output).toBe('DD/MM/YYYY hh:mm:ss a'); + }); + + it('should return a Date in dateTimeEuroAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroAM_PM); + expect(output).toBe('DD/MM/YYYY hh:mm:ss A'); + }); + + it('should return a Date in dateTimeEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShort); + expect(output).toEqual('D/M/YY H:m:s'); + }); + + it('should return a Date in dateTimeEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShort, true); + expect(output).toEqual('DD/MM/YY HH:mm:ss'); + }); + + it('should return a Date in dateTimeEuroShortAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm); + expect(output).toEqual('D/M/YY h:m:s a'); + }); + + it('should return a Date in dateTimeEuroShortAmPm format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm, true); + expect(output).toEqual('DD/MM/YY hh:mm:ss a'); + }); + + it('should return a Date in dateUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUs); + expect(output).toBe('MM/DD/YYYY'); + }); + + it('should return a Date in dateUsShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUsShort); + expect(output).toEqual('M/D/YY'); + }); + + it('should return a Date in dateUsShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUsShort, true); + expect(output).toEqual('MM/DD/YY'); + }); + + it('should return a Date in dateTimeUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUs); + expect(output).toBe('MM/DD/YYYY HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortUs); + expect(output).toEqual('M/D/YYYY H:m'); + }); + + it('should return a Date in dateTimeShortUs format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortUs, true); + expect(output).toEqual('MM/DD/YYYY HH:mm'); + }); + + it('should return a Date in dateTimeUsAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsAmPm); + expect(output).toBe('MM/DD/YYYY hh:mm:ss a'); + }); + + it('should return a Date in dateTimeUsAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsAM_PM); + expect(output).toBe('MM/DD/YYYY hh:mm:ss A'); + }); + + it('should return a Date in dateTimeUsShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShort); + expect(output).toEqual('M/D/YY H:m:s'); + }); + + it('should return a Date in dateTimeUsShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShort, true); + expect(output).toEqual('MM/DD/YY HH:mm:ss'); + }); + + it('should return a Date in dateTimeUsShortAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm); + expect(output).toEqual('M/D/YY h:m:s a'); + }); + + it('should return a Date in dateTimeUsShortAmPm format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm, true); + expect(output).toEqual('MM/DD/YY hh:mm:ss a'); + }); + + it('should return a Date in dateUtc format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUtc); + expect(output).toBe('ISO8601'); + }); + + it('should return a Date in date/dateIso format', () => { + const output1 = mapTempoDateFormatWithFieldType(FieldType.date); + const output2 = mapTempoDateFormatWithFieldType(FieldType.dateIso); + expect(output1).toBe('YYYY-MM-DD'); + expect(output2).toBe('YYYY-MM-DD'); + }); + }); + + describe('parseUtcDate method', () => { + it('should return a TZ date parsed as UTC but without milliseconds', () => { + const input = '2012-01-01'; + const output = parseUtcDate(input); + expect(output).toBe('2012-01-01T00:00:00Z'); + }); + }); +}); diff --git a/packages/common/src/services/__tests__/utilities.spec.ts b/packages/common/src/services/__tests__/utilities.spec.ts index 8b52fdb5e..10139ebe1 100644 --- a/packages/common/src/services/__tests__/utilities.spec.ts +++ b/packages/common/src/services/__tests__/utilities.spec.ts @@ -21,11 +21,9 @@ import { getDescendantProperty, getTranslationPrefix, isColumnDateType, - mapMomentDateFormatWithFieldType, mapOperatorByFieldType, mapOperatorToShorthandDesignation, mapOperatorType, - parseUtcDate, thousandSeparatorFormatted, unsubscribeAll, } from '../utilities'; @@ -708,122 +706,6 @@ describe('Service/Utilies', () => { }); }); - describe('mapMomentDateFormatWithFieldType method', () => { - it('should return a moment.js dateTime/dateTimeIso format', () => { - const output1 = mapMomentDateFormatWithFieldType(FieldType.dateTime); - const output2 = mapMomentDateFormatWithFieldType(FieldType.dateTimeIso); - expect(output1).toBe('YYYY-MM-DD HH:mm:ss'); - expect(output2).toBe('YYYY-MM-DD HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortIso format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortIso); - expect(output).toBe('YYYY-MM-DD HH:mm'); - }); - - it('should return a moment.js dateTimeIsoAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeIsoAmPm); - expect(output).toBe('YYYY-MM-DD hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeIsoAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeIsoAM_PM); - expect(output).toBe('YYYY-MM-DD hh:mm:ss A'); - }); - - it('should return a moment.js dateEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateEuro); - expect(output).toBe('DD/MM/YYYY'); - }); - - it('should return a moment.js dateEuroShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateEuroShort); - expect(output).toEqual(['DD/MM/YY', 'D/M/YY']); - }); - - it('should return a moment.js dateTimeEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuro); - expect(output).toBe('DD/MM/YYYY HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortEuro); - expect(output).toEqual(['DD/MM/YYYY HH:mm', 'D/M/YYYY HH:mm']); - }); - - it('should return a moment.js dateTimeEuroAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroAmPm); - expect(output).toBe('DD/MM/YYYY hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeEuroAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroAM_PM); - expect(output).toBe('DD/MM/YYYY hh:mm:ss A'); - }); - - it('should return a moment.js dateTimeEuroShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroShort); - expect(output).toEqual(['DD/MM/YY H:m:s', 'D/M/YY H:m:s']); - }); - - it('should return a moment.js dateTimeEuroShortAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm); - expect(output).toEqual(['DD/MM/YY h:m:s a', 'D/M/YY h:m:s a']); - }); - - it('should return a moment.js dateUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUs); - expect(output).toBe('MM/DD/YYYY'); - }); - - it('should return a moment.js dateUsShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUsShort); - expect(output).toEqual(['MM/DD/YY', 'M/D/YY']); - }); - - it('should return a moment.js dateTimeUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUs); - expect(output).toBe('MM/DD/YYYY HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortUs); - expect(output).toEqual(['MM/DD/YYYY HH:mm', 'M/D/YYYY HH:mm']); - }); - - it('should return a moment.js dateTimeUsAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsAmPm); - expect(output).toBe('MM/DD/YYYY hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeUsAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsAM_PM); - expect(output).toBe('MM/DD/YYYY hh:mm:ss A'); - }); - - it('should return a moment.js dateTimeUsShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsShort); - expect(output).toEqual(['MM/DD/YY H:m:s', 'M/D/YY H:m:s']); - }); - - it('should return a moment.js dateTimeUsShortAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm); - expect(output).toEqual(['MM/DD/YY h:m:s a', 'M/D/YY h:m:s a']); - }); - - it('should return a moment.js dateUtc format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUtc); - expect(output).toBe('YYYY-MM-DDTHH:mm:ss.SSSZ'); - }); - - it('should return a moment.js date/dateIso format', () => { - const output1 = mapMomentDateFormatWithFieldType(FieldType.date); - const output2 = mapMomentDateFormatWithFieldType(FieldType.dateIso); - expect(output1).toBe('YYYY-MM-DD'); - expect(output2).toBe('YYYY-MM-DD'); - }); - }); - describe('mapOperatorType method', () => { it('should return OperatoryType associated to "<"', () => { const expectation = OperatorType.lessThan; @@ -1121,25 +1003,6 @@ describe('Service/Utilies', () => { }); }); - describe('parseUtcDate method', () => { - it('should return null when date provided is not an ISO date (date only accepted)', () => { - const input1 = '2012-01-01 02:02:02'; - const input2 = '2012-01-01T02:02:02Z'; - - const output1 = parseUtcDate(input1); - const output2 = parseUtcDate(input2); - - expect(output1).toBe(''); - expect(output2).toBe(''); - }); - - it('should return a date parsed as UTC when input is a date (without time) of ISO format', () => { - const input = '2012-01-01'; - const output = parseUtcDate(input, true); - expect(output).toBe('2012-01-01T00:00:00Z'); - }); - }); - describe('thousandSeparatorFormatted method', () => { it('should return original value when input provided is null', () => { const input = null as any; diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts new file mode 100644 index 000000000..f70a411ac --- /dev/null +++ b/packages/common/src/services/dateUtils.ts @@ -0,0 +1,189 @@ +import { format, offset, parse, removeOffset, tzDate } from '@formkit/tempo'; + +import { FieldType } from '../enums/index'; + +/** + * From a Date FieldType, return it's equivalent TempoJS format, + * refer to TempoJS docs for the format tokens being used: https://tempo.formkit.com/#format + * @param fieldType + * @param withZeroPadding - should we include zero padding in format (e.g.: 03:04:54) + */ +export function mapTempoDateFormatWithFieldType(fieldType: typeof FieldType[keyof typeof FieldType], withZeroPadding = false): string { + let map: string; + switch (fieldType) { + case FieldType.dateTime: + case FieldType.dateTimeIso: + map = 'YYYY-MM-DD HH:mm:ss'; + break; + case FieldType.dateTimeIsoAmPm: + map = 'YYYY-MM-DD hh:mm:ss a'; + break; + case FieldType.dateTimeIsoAM_PM: + map = 'YYYY-MM-DD hh:mm:ss A'; + break; + case FieldType.dateTimeShortIso: + map = 'YYYY-MM-DD HH:mm'; + break; + // all Euro Formats (date/month/year) + case FieldType.dateEuro: + map = 'DD/MM/YYYY'; + break; + case FieldType.dateEuroShort: + map = withZeroPadding + ? 'DD/MM/YY' + : 'D/M/YY'; + break; + case FieldType.dateTimeEuro: + map = 'DD/MM/YYYY HH:mm:ss'; + break; + case FieldType.dateTimeShortEuro: + map = withZeroPadding + ? 'DD/MM/YYYY HH:mm' + : 'D/M/YYYY H:m'; + break; + case FieldType.dateTimeEuroAmPm: + map = 'DD/MM/YYYY hh:mm:ss a'; + break; + case FieldType.dateTimeEuroAM_PM: + map = 'DD/MM/YYYY hh:mm:ss A'; + break; + case FieldType.dateTimeEuroShort: + map = withZeroPadding + ? 'DD/MM/YY HH:mm:ss' + : 'D/M/YY H:m:s'; + break; + case FieldType.dateTimeEuroShortAmPm: + map = withZeroPadding + ? 'DD/MM/YY hh:mm:ss a' + : 'D/M/YY h:m:s a'; + break; + case FieldType.dateTimeEuroShortAM_PM: + map = withZeroPadding + ? 'DD/MM/YY hh:mm:ss A' + : 'D/M/YY h:m:s A'; + break; + // all US Formats (month/date/year) + case FieldType.dateUs: + map = 'MM/DD/YYYY'; + break; + case FieldType.dateUsShort: + map = withZeroPadding + ? 'MM/DD/YY' + : 'M/D/YY'; + break; + case FieldType.dateTimeUs: + map = 'MM/DD/YYYY HH:mm:ss'; + break; + case FieldType.dateTimeUsAmPm: + map = 'MM/DD/YYYY hh:mm:ss a'; + break; + case FieldType.dateTimeUsAM_PM: + map = 'MM/DD/YYYY hh:mm:ss A'; + break; + case FieldType.dateTimeUsShort: + map = withZeroPadding + ? 'MM/DD/YY HH:mm:ss' + : 'M/D/YY H:m:s'; + break; + case FieldType.dateTimeUsShortAmPm: + map = withZeroPadding + ? 'MM/DD/YY hh:mm:ss a' + : 'M/D/YY h:m:s a'; + break; + case FieldType.dateTimeUsShortAM_PM: + map = withZeroPadding + ? 'MM/DD/YY hh:mm:ss A' + : 'M/D/YY h:m:s A'; + break; + case FieldType.dateTimeShortUs: + map = withZeroPadding + ? 'MM/DD/YYYY HH:mm' + : 'M/D/YYYY H:m'; + break; + case FieldType.dateUtc: + map = 'ISO8601'; + break; + case FieldType.date: + case FieldType.dateIso: + default: + map = 'YYYY-MM-DD'; + break; + } + return map; +} + +/** + * Format a date using Tempo and a defined input/output field types + * @param {string|Date} inputDate + * @param {FieldType} inputFieldType + * @param {FieldType} outputFieldType + * @returns + */ +export function formatDateByFieldType(inputDate: Date | string, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { + const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); + const date = inputDate instanceof Date ? inputDate : tryParseDate(inputDate, inputFormat as string); + + if (date && inputDate !== undefined) { + if (outputFieldType === FieldType.dateUtc) { + return date.toISOString(); + } + return format(date, outputFormat, 'en-US'); + } + return ''; +} + +/** + * Try to parse date with Tempo or return `false` (instead of throwing) if Date is invalid. + * When using strict mode, it will detect if the date is invalid when outside of the calendar (e.g. "2011-11-31"). + * However in non-strict mode, it will roll the date backward if out of calendar (e.g. "2011-11-31" would return "2011-11-30"). + * @param {string|Date} [inputDate] - input date (or null) + * @param {string} [inputFormat] - optional input format to use when parsing + * @param {Boolean} [strict] - are we using strict mode? + * @returns + */ +export function tryParseDate(inputDate?: string | Date, inputFormat?: string, strict = false): Date | false { + try { + if (!inputDate) { + return false; + } + return inputDate instanceof Date ? inputDate : parse({ + date: inputDate, + format: inputFormat as string, + dateOverflow: strict ? 'throw' : 'backward', + locale: 'en-US' + }); + } catch (_e) { + return false; + } +} + +/** + * Parse a Date as a UTC date (without local TZ offset) + * @param inputDate + * @returns + */ +export function toUtcDate(inputDate: string | Date) { + // to parse as UTC in Tempo, we need to remove the offset (which is a simple inversed offset to cancel itself) + return removeOffset(inputDate, offset(inputDate, 'utc')); +}; + +/** + * Parse a date passed as a string (Date only, without time) and return a TZ Date (without milliseconds) + * @param inputDateString + * @returns TZ UTC date formatted + */ +export function parseUtcDate(inputDateString: string): string { + let outputFormattedDate = ''; + + if (typeof inputDateString === 'string' && /^[0-9\-/]*$/.test(inputDateString)) { + // get the UTC datetime but make sure to decode the value so that it's valid text + const dateString = decodeURIComponent(inputDateString); + const date = tzDate(dateString, 'utc'); + if (date) { + outputFormattedDate = date.toISOString().replace(/(.*)([.\d]{4})(Z)/gi, '$1$3'); + } + } + + return outputFormattedDate; +} \ No newline at end of file diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts index 159701e05..996fc07c2 100644 --- a/packages/common/src/services/filter.service.ts +++ b/packages/common/src/services/filter.service.ts @@ -393,7 +393,7 @@ export class FilterService { /** * Loop through each form input search filter and parse their searchTerms, - * for example a CompoundDate Filter will be parsed as a Moment object. + * for example a CompoundDate Filter will be parsed as a Date object. * Also if we are dealing with a text filter input, * an operator can optionally be part of the filter itself and we need to extract it from there, * for example a filter of "John*" will be analyzed as { operator: StartsWith, searchTerms: ['John'] } @@ -570,7 +570,7 @@ export class FilterService { delete (treeObj as any)[inputItem[primaryDataId]].__used; }); - // Step 1. prepare search filter by getting their parsed value(s), for example if it's a date filter then parse it to a Moment object + // Step 1. prepare search filter by getting their parsed value(s), for example if it's a date filter then parse it to a Date object // loop through all column filters once and get parsed filter search value then save a reference in the columnFilter itself // it is much more effective to do it outside and prior to Step 2 so that we don't re-parse search filter for no reason while checking every row if (typeof columnFilters === 'object') { diff --git a/packages/common/src/services/index.ts b/packages/common/src/services/index.ts index dd3701f89..9d1ed0024 100644 --- a/packages/common/src/services/index.ts +++ b/packages/common/src/services/index.ts @@ -1,6 +1,7 @@ export * from './backendUtility.service'; export * from './collection.service'; export * from './container.service'; +export * from './dateUtils'; export * from './domUtilities'; export * from './excelExport.service'; export * from './extension.service'; diff --git a/packages/common/src/services/utilities.ts b/packages/common/src/services/utilities.ts index 32feaf5d8..d26b97a86 100644 --- a/packages/common/src/services/utilities.ts +++ b/packages/common/src/services/utilities.ts @@ -1,6 +1,5 @@ import type { EventSubscription } from '@slickgrid-universal/event-pub-sub'; import { flatten } from 'un-flatten-tree'; -import moment, { type Moment } from 'moment-tiny'; import { Constants } from '../constants'; import { FieldType, type OperatorString, OperatorType } from '../enums/index'; @@ -399,109 +398,6 @@ export function isColumnDateType(fieldType: typeof FieldType[keyof typeof FieldT } } -export function formatDateByFieldType(inputDate: Date | string | Moment, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { - const inputFormat = inputFieldType ? mapMomentDateFormatWithFieldType(inputFieldType) : undefined; - const outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); - const momentDate = (inputDate instanceof moment ? inputDate : moment(inputDate, inputFormat)) as Moment; - - if (momentDate.isValid() && inputDate !== undefined) { - if (outputFieldType === FieldType.dateUtc) { - return momentDate.toISOString(); - } - return momentDate.format(Array.isArray(outputFormat) ? outputFormat[0] : outputFormat); - } - return ''; -} - -/** - * From a Date FieldType, return it's equivalent moment.js format - * refer to moment.js for the format standard used: https://momentjs.com/docs/#/parsing/string-format/ - * @param fieldType - */ -export function mapMomentDateFormatWithFieldType(fieldType: typeof FieldType[keyof typeof FieldType]): string | string[] { - let map: string | string[]; - switch (fieldType) { - case FieldType.dateTime: - case FieldType.dateTimeIso: - map = 'YYYY-MM-DD HH:mm:ss'; - break; - case FieldType.dateTimeIsoAmPm: - map = 'YYYY-MM-DD hh:mm:ss a'; - break; - case FieldType.dateTimeIsoAM_PM: - map = 'YYYY-MM-DD hh:mm:ss A'; - break; - case FieldType.dateTimeShortIso: - map = 'YYYY-MM-DD HH:mm'; - break; - // all Euro Formats (date/month/year) - case FieldType.dateEuro: - map = 'DD/MM/YYYY'; - break; - case FieldType.dateEuroShort: - map = ['DD/MM/YY', 'D/M/YY']; - break; - case FieldType.dateTimeEuro: - map = 'DD/MM/YYYY HH:mm:ss'; - break; - case FieldType.dateTimeShortEuro: - map = ['DD/MM/YYYY HH:mm', 'D/M/YYYY HH:mm']; - break; - case FieldType.dateTimeEuroAmPm: - map = 'DD/MM/YYYY hh:mm:ss a'; - break; - case FieldType.dateTimeEuroAM_PM: - map = 'DD/MM/YYYY hh:mm:ss A'; - break; - case FieldType.dateTimeEuroShort: - map = ['DD/MM/YY H:m:s', 'D/M/YY H:m:s']; - break; - case FieldType.dateTimeEuroShortAmPm: - map = ['DD/MM/YY h:m:s a', 'D/M/YY h:m:s a']; - break; - case FieldType.dateTimeEuroShortAM_PM: - map = ['DD/MM/YY h:m:s A', 'D/M/YY h:m:s A']; - break; - // all US Formats (month/date/year) - case FieldType.dateUs: - map = 'MM/DD/YYYY'; - break; - case FieldType.dateUsShort: - map = ['MM/DD/YY', 'M/D/YY']; - break; - case FieldType.dateTimeUs: - map = 'MM/DD/YYYY HH:mm:ss'; - break; - case FieldType.dateTimeUsAmPm: - map = 'MM/DD/YYYY hh:mm:ss a'; - break; - case FieldType.dateTimeUsAM_PM: - map = 'MM/DD/YYYY hh:mm:ss A'; - break; - case FieldType.dateTimeUsShort: - map = ['MM/DD/YY H:m:s', 'M/D/YY H:m:s']; - break; - case FieldType.dateTimeUsShortAmPm: - map = ['MM/DD/YY h:m:s a', 'M/D/YY h:m:s a']; - break; - case FieldType.dateTimeUsShortAM_PM: - map = ['MM/DD/YY h:m:s A', 'M/D/YY h:m:s A']; - break; - case FieldType.dateTimeShortUs: - map = ['MM/DD/YYYY HH:mm', 'M/D/YYYY HH:mm']; - break; - case FieldType.dateUtc: - map = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; - break; - case FieldType.date: - case FieldType.dateIso: - default: - map = 'YYYY-MM-DD'; - break; - } - return map; -} - /** * Mapper for query operators (ex.: <= is "le", > is "gt") * @param string operator @@ -666,26 +562,6 @@ export function objectWithoutKey(obj: T, omitKey: keyof T): T { }, {}) as unknown as T; } -/** - * Parse a date passed as a string (Date only, without time) and return a Date object (if valid) - * @param inputDateString - * @returns string date formatted - */ -export function parseUtcDate(inputDateString: any, useUtc?: boolean): string { - let date = ''; - - if (typeof inputDateString === 'string' && /^[0-9\-/]*$/.test(inputDateString)) { - // get the UTC datetime with moment.js but we need to decode the value so that it's valid text - const dateString = decodeURIComponent(inputDateString); - const dateMoment = moment(new Date(dateString)); - if (dateMoment.isValid() && dateMoment.year().toString().length === 4) { - date = (useUtc) ? dateMoment.utc().format() : dateMoment.format(); - } - } - - return date; -} - /** * Format a number or a string into a string that is separated every thousand, * the default separator is a comma but user can optionally pass a different one diff --git a/packages/common/src/sortComparers/dateUtilities.ts b/packages/common/src/sortComparers/dateUtilities.ts index 2ebcffa98..2a03bb397 100644 --- a/packages/common/src/sortComparers/dateUtilities.ts +++ b/packages/common/src/sortComparers/dateUtilities.ts @@ -1,28 +1,26 @@ -import moment, { type Moment, type MomentBuiltinFormat } from 'moment-tiny'; - import { FieldType } from '../enums/fieldType.enum'; import type { SortComparer } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; +import { mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; -export function compareDates(value1: any, value2: any, sortDirection: number, format?: string | string[] | MomentBuiltinFormat, strict?: boolean) { +export function compareDates(value1: any, value2: any, sortDirection: number, format?: string, strict?: boolean) { let diff = 0; if (value1 === value2) { diff = 0; } else { - // use moment to validate the date - let date1: Moment | Date = moment(value1, format, strict); - let date2: Moment | Date = moment(value2, format, strict); + // try to parse the Date and validate it + let date1: Date | boolean = tryParseDate(value1, format, strict); + let date2: Date | boolean = tryParseDate(value2, format, strict); - // when moment date is invalid, we'll create a temporary old Date - if (!(date1 as Moment).isValid()) { + // when date is invalid (false), we'll create a temporary old Date + if (!date1) { date1 = new Date(1001, 1, 1); } - if (!(date2 as Moment).isValid()) { + if (!date2) { date2 = new Date(1001, 1, 1); } - // we can use valueOf on both moment & Date to sort + // we can use Date valueOf to sort diff = date1.valueOf() - date2.valueOf(); } @@ -31,7 +29,7 @@ export function compareDates(value1: any, value2: any, sortDirection: number, fo /** From a FieldType, return the associated Date SortComparer */ export function getAssociatedDateSortComparer(fieldType: typeof FieldType[keyof typeof FieldType]): SortComparer { - const FORMAT = (fieldType === FieldType.date) ? undefined : mapMomentDateFormatWithFieldType(fieldType); + const FORMAT = (fieldType === FieldType.date) ? undefined : mapTempoDateFormatWithFieldType(fieldType); return ((value1: any, value2: any, sortDirection: number) => { if (FORMAT === undefined) { diff --git a/packages/common/src/sortComparers/sortComparers.index.ts b/packages/common/src/sortComparers/sortComparers.index.ts index 6d08376a1..0256c7b90 100644 --- a/packages/common/src/sortComparers/sortComparers.index.ts +++ b/packages/common/src/sortComparers/sortComparers.index.ts @@ -12,7 +12,7 @@ export const SortComparers = { /** SortComparer method to sort values as regular strings */ boolean: booleanSortComparer, - /** SortComparer method to sort values by Date object type (uses Moment.js ISO_8601 standard format, optionally include time) */ + /** SortComparer method to sort values by Date object type (uses Tempo ISO_8601 standard format, optionally include time) */ date: getAssociatedDateSortComparer(FieldType.date), /** diff --git a/packages/custom-footer-component/package.json b/packages/custom-footer-component/package.json index 39e9372b4..c6d4bd404 100644 --- a/packages/custom-footer-component/package.json +++ b/packages/custom-footer-component/package.json @@ -49,9 +49,9 @@ "not dead" ], "dependencies": { + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", - "@slickgrid-universal/common": "workspace:~", - "moment-tiny": "^2.30.4" + "@slickgrid-universal/common": "workspace:~" }, "devDependencies": { "@slickgrid-universal/event-pub-sub": "workspace:~" diff --git a/packages/custom-footer-component/src/slick-footer.component.ts b/packages/custom-footer-component/src/slick-footer.component.ts index d485ca9e2..88df52939 100644 --- a/packages/custom-footer-component/src/slick-footer.component.ts +++ b/packages/custom-footer-component/src/slick-footer.component.ts @@ -1,4 +1,4 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; import type { CustomFooterOption, GridOption, @@ -110,7 +110,7 @@ export class SlickFooterComponent { /** Render element attribute values */ renderMetrics(metrics: Metrics) { // get translated text & last timestamp - const lastUpdateTimestamp = moment(metrics.endTime).format(this.customFooterOptions.dateFormat); + const lastUpdateTimestamp = metrics?.endTime ? format(metrics.endTime, this.customFooterOptions.dateFormat, 'en-US') : ''; this._bindingHelper.setElementAttributeValue('span.last-update-timestamp', 'textContent', lastUpdateTimestamp); this._bindingHelper.setElementAttributeValue('span.item-count', 'textContent', metrics.itemCount); this._bindingHelper.setElementAttributeValue('span.total-count', 'textContent', metrics.totalItemCount); @@ -233,7 +233,7 @@ export class SlickFooterComponent { protected createFooterLastUpdate(): HTMLSpanElement { // get translated text & last timestamp const lastUpdateText = this.customFooterOptions?.metricTexts?.lastUpdate ?? 'Last Update'; - const lastUpdateTimestamp = moment(this.metrics?.endTime).format(this.customFooterOptions.dateFormat); + const lastUpdateTimestamp = this.metrics?.endTime ? format(this.metrics?.endTime, this.customFooterOptions.dateFormat, 'en-US') : ''; const lastUpdateContainerElm = createDomElement('span'); lastUpdateContainerElm.appendChild(createDomElement('span', { className: 'text-last-update', textContent: lastUpdateText })); diff --git a/packages/excel-export/package.json b/packages/excel-export/package.json index 6ab0cdf68..4dc765aa2 100644 --- a/packages/excel-export/package.json +++ b/packages/excel-export/package.json @@ -51,8 +51,7 @@ "dependencies": { "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/utils": "workspace:~", - "excel-builder-vanilla": "^3.0.1", - "moment-tiny": "^2.30.4" + "excel-builder-vanilla": "^3.0.1" }, "devDependencies": { "@slickgrid-universal/event-pub-sub": "workspace:~" diff --git a/packages/graphql/package.json b/packages/graphql/package.json index d534f1499..3851d696e 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -52,9 +52,6 @@ "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/utils": "workspace:~" }, - "devDependencies": { - "moment-tiny": "^2.30.4" - }, "funding": { "type": "ko_fi", "url": "https://ko-fi.com/ghiscoding" diff --git a/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts b/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts index 96c5cbf65..d009fb0f7 100644 --- a/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts +++ b/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts @@ -1,4 +1,3 @@ -import moment from 'moment-tiny'; import GraphqlQueryBuilder from '../graphqlQueryBuilder'; function removeSpaces(textS) { @@ -81,8 +80,8 @@ describe('GraphqlQueryBuilder', () => { it('should be able to Query a Date field', () => { const now = new Date(); - const expectation = `FetchLeeAndSam { lee: user(modified: "${moment(now).toISOString()}") { name, modified }, - sam: user(modified: "${moment(now).toISOString()}") { name, modified } }`; + const expectation = `FetchLeeAndSam { lee: user(modified: "${now.toISOString()}") { name, modified }, + sam: user(modified: "${now.toISOString()}") { name, modified } }`; const fetchLeeAndSam = new GraphqlQueryBuilder('FetchLeeAndSam'); diff --git a/packages/odata/src/services/grid-odata.service.ts b/packages/odata/src/services/grid-odata.service.ts index f54cace12..ee60952c1 100644 --- a/packages/odata/src/services/grid-odata.service.ts +++ b/packages/odata/src/services/grid-odata.service.ts @@ -323,7 +323,7 @@ export class GridOdataService implements BackendService { fieldName = stripTags(fieldName.innerHTML); } const fieldType = columnDef.type || FieldType.string; - let searchTerms = (columnFilter && columnFilter.searchTerms ? [...columnFilter.searchTerms] : null) || []; + let searchTerms = (columnFilter?.searchTerms ? [...columnFilter.searchTerms] : null) || []; let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : ''; if (typeof fieldSearchValue === 'undefined') { fieldSearchValue = ''; @@ -643,7 +643,7 @@ export class GridOdataService implements BackendService { protected normalizeSearchValue(fieldType: typeof FieldType[keyof typeof FieldType], searchValue: any, version: number) { switch (fieldType) { case FieldType.date: - searchValue = parseUtcDate(searchValue as string, true); + searchValue = parseUtcDate(searchValue as string); searchValue = version >= 4 ? searchValue : `DateTime'${searchValue}'`; break; case FieldType.string: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f65dfe597..baef0f867 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@commitlint/config-conventional': specifier: ^19.2.2 version: 19.2.2 + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@jest/types': specifier: ^29.6.3 version: 29.6.3 @@ -86,9 +89,6 @@ importers: jsdom-global: specifier: ^3.0.2 version: 3.0.2(jsdom@24.0.0) - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 npm-run-all2: specifier: ^6.1.2 version: 6.1.2 @@ -134,6 +134,9 @@ importers: '@fnando/sparkline': specifier: ^0.3.10 version: 0.3.10 + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../../packages/binding @@ -179,9 +182,6 @@ importers: fetch-jsonp: specifier: ^1.3.0 version: 1.3.0 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 multiple-select-vanilla: specifier: ^3.1.3 version: 3.1.3 @@ -222,6 +222,9 @@ importers: packages/common: dependencies: + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../binding @@ -246,9 +249,6 @@ importers: excel-builder-vanilla: specifier: 3.0.1 version: 3.0.1 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 multiple-select-vanilla: specifier: ^3.1.3 version: 3.1.3 @@ -301,15 +301,15 @@ importers: packages/custom-footer-component: dependencies: + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../binding '@slickgrid-universal/common': specifier: workspace:~ version: link:../common - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 devDependencies: '@slickgrid-universal/event-pub-sub': specifier: workspace:~ @@ -351,9 +351,6 @@ importers: excel-builder-vanilla: specifier: ^3.0.1 version: 3.0.1 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 devDependencies: '@slickgrid-universal/event-pub-sub': specifier: workspace:~ @@ -367,10 +364,6 @@ importers: '@slickgrid-universal/utils': specifier: workspace:~ version: link:../utils - devDependencies: - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 packages/odata: dependencies: @@ -1350,6 +1343,9 @@ packages: resolution: {integrity: sha512-Rwz2swatdSU5F4sCOvYG8EOWdjtLgq5d8nmnqlZ3PXdWJI9Zq9BRUvJ/9ygjajJG8qOyNpMFX3GEVFjZIuB1Jg==} dev: false + /@formkit/tempo@0.1.1: + resolution: {integrity: sha512-nepRwKnCIjukLkblqh339sSXYI6P3cqktF/T7ECj3vURW+Pd+YzTv/I/lwjRqPGmlQJG93LORodIAkvgoExIAg==} + /@humanwhocodes/config-array@0.13.0: resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -6893,9 +6889,6 @@ packages: hasBin: true dev: true - /moment-tiny@2.30.4: - resolution: {integrity: sha512-8m0gGjP9pfhzYPL07NJrnS5qHJIE9x/cVlI3no2X9QI0r/vDHKJ4aVf7D+eUxSryMXZKr5P3hDBStmesr6wzhA==} - /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true diff --git a/test/cypress/e2e/example02.cy.ts b/test/cypress/e2e/example02.cy.ts index 773bab389..6f87492e0 100644 --- a/test/cypress/e2e/example02.cy.ts +++ b/test/cypress/e2e/example02.cy.ts @@ -1,4 +1,5 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; + import { removeExtraSpaces } from '../plugins/utilities'; describe('Example 02 - Grouping & Aggregators', () => { @@ -37,7 +38,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 500 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 500 of 500 items`); }); }); @@ -51,7 +52,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); }); }); @@ -63,7 +64,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('span:nth(1)') .click(); - const currentDateTime = moment().format('YYYY-MM-DD, hh:mm a'); + const currentDateTime = format(new Date(), 'YYYY-MM-DD, hh:mm a'); cy.get('.grid2') .find('.slick-custom-footer') .find('.right-footer') @@ -86,7 +87,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 148 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 148 of 500 items`); }); }); @@ -103,7 +104,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); }); cy.get('.ms-drop').should('contain', ''); @@ -181,7 +182,7 @@ describe('Example 02 - Grouping & Aggregators', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^Avg: [0-9]\%$/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^Avg: [0-9]%$/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2`) .find('.slick-cell:nth(3)').contains('Avg: '); }); diff --git a/test/cypress/e2e/example03.cy.ts b/test/cypress/e2e/example03.cy.ts index 5b79063cc..7a0cdddb1 100644 --- a/test/cypress/e2e/example03.cy.ts +++ b/test/cypress/e2e/example03.cy.ts @@ -63,7 +63,7 @@ describe('Example 03 - Draggable Grouping', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); }); - it('should collapse all rows and make sure Duration group is sorted in descending order', () => { + it('should collapse all rows again and make sure Duration group is sorted in descending order', () => { cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); diff --git a/test/cypress/e2e/example04.cy.ts b/test/cypress/e2e/example04.cy.ts index 1ae72e0f3..df67f17d4 100644 --- a/test/cypress/e2e/example04.cy.ts +++ b/test/cypress/e2e/example04.cy.ts @@ -173,7 +173,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should expect to have exact Column Header Titles in the grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() @@ -195,7 +195,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should recheck again and still have exact Column Header Titles in the grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() diff --git a/test/cypress/e2e/example05.cy.ts b/test/cypress/e2e/example05.cy.ts index 3abe612de..0e2baaa99 100644 --- a/test/cypress/e2e/example05.cy.ts +++ b/test/cypress/e2e/example05.cy.ts @@ -189,7 +189,7 @@ describe('Example 05 - Tree Data (from a flat dataset with parentId references)' } }); - it('should open the Grid Menu "Clear all Filters" command', () => { + it('should open the Grid Menu "Clear all Filters" command a second time', () => { cy.get('.grid5') .find('button.slick-grid-menu-button') .trigger('click') @@ -261,7 +261,7 @@ describe('Example 05 - Tree Data (from a flat dataset with parentId references)' .contains(/^((?!Task 500).)*$/); }); - it('should open the Grid Menu "Clear all Filters" command', () => { + it('should open the Grid Menu "Clear all Filters" command again', () => { cy.get('.grid5') .find('button.slick-grid-menu-button') .trigger('click') diff --git a/test/cypress/e2e/example06.cy.ts b/test/cypress/e2e/example06.cy.ts index 471e36bc1..412e9a7e3 100644 --- a/test/cypress/e2e/example06.cy.ts +++ b/test/cypress/e2e/example06.cy.ts @@ -348,7 +348,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' .click(); }); - it('should have pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { + it('should have again the pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { cy.get('.slick-viewport-top.slick-viewport-left') .scrollTo('center', { force: true } as any); @@ -415,7 +415,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' cy.get('.right-footer .total-count').contains('31'); }); - it('should enable auto-recalc Tree Totals', () => { + it('should re-enable auto-recalc Tree Totals', () => { cy.get('[data-test="clear-filters-btn"]') .click(); }); @@ -440,7 +440,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' cy.get('.right-footer .total-count').contains('31'); }); - it('should type filter "b" and expect totals to be updated with a lower Sum(6MB) / Avg(3MB) of only what is displayed', () => { + it('should type filter "b" again and still expect totals to be updated with a lower Sum(6MB) / Avg(3MB) of only what is displayed', () => { cy.get('.search-filter.filter-file') .type('i'); // will become "bi" diff --git a/test/cypress/e2e/example07.cy.ts b/test/cypress/e2e/example07.cy.ts index 113740a5b..7dc0c15bc 100644 --- a/test/cypress/e2e/example07.cy.ts +++ b/test/cypress/e2e/example07.cy.ts @@ -554,7 +554,7 @@ describe('Example 07 - Row Move & Checkbox Selector Selector Plugins', () => { }); }); - it('should expect "Clear all Filters" command to be hidden in the Grid Menu', () => { + it('should expect "Clear all Filters" command to be hidden again in the Grid Menu', () => { const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; cy.get('.grid7') diff --git a/test/cypress/e2e/example08.cy.ts b/test/cypress/e2e/example08.cy.ts index 446557580..4e627c022 100644 --- a/test/cypress/e2e/example08.cy.ts +++ b/test/cypress/e2e/example08.cy.ts @@ -36,7 +36,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-right > [style="top: ${GRID_ROW_HEIGHT * 0}px;"]> .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() @@ -62,7 +62,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-left > [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() @@ -90,7 +90,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-right > [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() diff --git a/test/cypress/e2e/example10.cy.ts b/test/cypress/e2e/example10.cy.ts index 0e9dd16be..141eafe66 100644 --- a/test/cypress/e2e/example10.cy.ts +++ b/test/cypress/e2e/example10.cy.ts @@ -1,11 +1,11 @@ -import moment from 'moment-tiny'; +import { addDay, format } from '@formkit/tempo'; function removeSpaces(textS) { return `${textS}`.replace(/\s+/g, ''); } -const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); -const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); +const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); +const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); describe('Example 10 - GraphQL Grid', () => { it('should display Example title', () => { diff --git a/test/cypress/e2e/example11.cy.ts b/test/cypress/e2e/example11.cy.ts index ef133f210..7a52b5e70 100644 --- a/test/cypress/e2e/example11.cy.ts +++ b/test/cypress/e2e/example11.cy.ts @@ -1,4 +1,3 @@ -import moment from 'moment-tiny'; import { changeTimezone, zeroPadding } from '../plugins/utilities'; describe('Example 11 - Batch Editing', () => { @@ -7,7 +6,7 @@ describe('Example 11 - Batch Editing', () => { const EDITABLE_CELL_RGB_COLOR = 'rgba(227, 240, 251, 0.57)'; const UNSAVED_RGB_COLOR = 'rgb(251, 253, 209)'; const fullTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Start', 'Finish', 'Completed', 'Product', 'Country of Origin', 'Action']; - const currentYear = moment().year(); + const currentYear = new Date().getFullYear(); beforeEach(() => { // create a console.log spy for later use @@ -644,7 +643,7 @@ describe('Example 11 - Batch Editing', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/i); cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$[0-9\.]*/); + cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$[0-9.]*/); cy.get('.slick-pane-left') .find('.slick-grid-menu-button') @@ -740,7 +739,7 @@ describe('Example 11 - Batch Editing', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/i); cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$?[0-9\.]*/); + cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$?[0-9.]*/); cy.get('.slick-pane-left') .find('.slick-grid-menu-button') diff --git a/test/cypress/e2e/example18.cy.ts b/test/cypress/e2e/example18.cy.ts index 93ae93e19..12f7f3b1a 100644 --- a/test/cypress/e2e/example18.cy.ts +++ b/test/cypress/e2e/example18.cy.ts @@ -18,10 +18,10 @@ describe('Example 18 - Real-Time Trading Platform', () => { for (let i = 0; i < 5; i++) { cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/CAD|USD$/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9\.]*\)?/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); } }); @@ -54,10 +54,10 @@ describe('Example 18 - Real-Time Trading Platform', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: CAD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\,\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: USD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\,\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); }); }); diff --git a/test/cypress/e2e/example19.cy.ts b/test/cypress/e2e/example19.cy.ts index 07e37f9d5..98bd53eee 100644 --- a/test/cypress/e2e/example19.cy.ts +++ b/test/cypress/e2e/example19.cy.ts @@ -184,7 +184,7 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .should('have.text', '{"fromRow":0,"fromCell":0,"toRow":95,"toCell":98}'); }); - it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { + it('should click on cell CR5 again then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { cy.getCell(5, 95, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CR95') .click(); diff --git a/test/jest-global-mocks.ts b/test/jest-global-mocks.ts index fc87b1809..df4472510 100644 --- a/test/jest-global-mocks.ts +++ b/test/jest-global-mocks.ts @@ -37,9 +37,3 @@ Object.defineProperty(window, 'matchMedia', { dispatchEvent: jest.fn(), })), }); - -// Jest has a hard time with MomentJS because they export as default, to bypass this problem we can mock the require .default -jest.mock('moment-tiny', () => { - const actual = jest.requireActual('moment-tiny'); - return { __esModule: true, ...actual, default: actual }; -}); \ No newline at end of file