From fd9ab59172ba549e737f888a23d9d4ae61020307 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Wed, 12 Apr 2023 23:01:21 +0200 Subject: [PATCH] refactor(date-converter): improved ux and layout --- playwright.config.ts | 2 + src/composable/validation.ts | 12 +- .../date-time-converter.e2e.spec.ts | 33 ++++ .../date-time-converter.models.test.ts | 142 ++++++++++++++ .../date-time-converter.models.ts | 46 +++++ .../date-time-converter.types.ts | 8 + .../date-time-converter.vue | 179 ++++++++++-------- 7 files changed, 346 insertions(+), 76 deletions(-) create mode 100644 src/tools/date-time-converter/date-time-converter.e2e.spec.ts create mode 100644 src/tools/date-time-converter/date-time-converter.models.test.ts create mode 100644 src/tools/date-time-converter/date-time-converter.models.ts create mode 100644 src/tools/date-time-converter/date-time-converter.types.ts diff --git a/playwright.config.ts b/playwright.config.ts index cb6d3072..a445d2d6 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -31,6 +31,8 @@ export default defineConfig({ trace: 'on-first-retry', testIdAttribute: 'data-test-id', + locale: 'en-GB', + timezoneId: 'Europe/Paris', }, /* Configure projects for major browsers */ diff --git a/src/composable/validation.ts b/src/composable/validation.ts index 527aafad..4858110c 100644 --- a/src/composable/validation.ts +++ b/src/composable/validation.ts @@ -25,7 +25,15 @@ export type ValidationAttrs = { validationStatus: string | undefined; }; -export function useValidation({ source, rules }: { source: Ref; rules: UseValidationRule[] }) { +export function useValidation({ + source, + rules, + watch: watchRefs = [], +}: { + source: Ref; + rules: UseValidationRule[]; + watch?: Ref[]; +}) { const state = reactive<{ message: string; status: undefined | 'error'; @@ -42,7 +50,7 @@ export function useValidation({ source, rules }: { source: Ref; rules: Use }); watch( - [source], + [source, ...watchRefs], () => { state.message = ''; state.status = undefined; diff --git a/src/tools/date-time-converter/date-time-converter.e2e.spec.ts b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts new file mode 100644 index 00000000..50155609 --- /dev/null +++ b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Date time converter - json to yaml', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/date-converter'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Date-time converter - IT Tools'); + }); + + test('Format is auto detected from a date and the date is correctly converted', async ({ page }) => { + const initialFormat = await page.getByTestId('date-time-converter-format-select').innerText(); + expect(initialFormat.trim()).toEqual('Timestamp'); + + await page.getByTestId('date-time-converter-input').fill('2023-04-12T23:10:24+02:00'); + + const detectedFormat = await page.getByTestId('date-time-converter-format-select').innerText(); + expect(detectedFormat.trim()).toEqual('ISO 8601'); + + expect((await page.getByTestId('JS locale date string').inputValue()).trim()).toEqual( + 'Wed Apr 12 2023 23:10:24 GMT+0200 (Central European Summer Time)', + ); + expect((await page.getByTestId('ISO 8601').inputValue()).trim()).toEqual('2023-04-12T23:10:24+02:00'); + expect((await page.getByTestId('ISO 9075').inputValue()).trim()).toEqual('2023-04-12 23:10:24'); + expect((await page.getByTestId('Unix timestamp').inputValue()).trim()).toEqual('1681333824'); + expect((await page.getByTestId('RFC 7231').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT'); + expect((await page.getByTestId('RFC 3339').inputValue()).trim()).toEqual('2023-04-12T23:10:24+02:00'); + expect((await page.getByTestId('Timestamp').inputValue()).trim()).toEqual('1681333824000'); + expect((await page.getByTestId('UTC format').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT'); + expect((await page.getByTestId('Mongo ObjectID').inputValue()).trim()).toEqual('64371e400000000000000000'); + }); +}); diff --git a/src/tools/date-time-converter/date-time-converter.models.test.ts b/src/tools/date-time-converter/date-time-converter.models.test.ts new file mode 100644 index 00000000..e3a82f88 --- /dev/null +++ b/src/tools/date-time-converter/date-time-converter.models.test.ts @@ -0,0 +1,142 @@ +import { describe, test, expect } from 'vitest'; +import { + isISO8601DateTimeString, + isISO9075DateString, + isRFC3339DateString, + isRFC7231DateString, + isUnixTimestamp, + isTimestamp, + isUTCDateString, + isMongoObjectId, +} from './date-time-converter.models'; + +describe('date-time-converter models', () => { + describe('isISO8601DateTimeString', () => { + test('should return true for valid ISO 8601 date strings', () => { + expect(isISO8601DateTimeString('2021-01-01T00:00:00.000Z')).toBe(true); + expect(isISO8601DateTimeString('2023-04-12T14:56:00+01:00')).toBe(true); + expect(isISO8601DateTimeString('20230412T145600+0100')).toBe(true); + expect(isISO8601DateTimeString('20230412T145600Z')).toBe(true); + expect(isISO8601DateTimeString('2016-02-01')).toBe(true); + expect(isISO8601DateTimeString('2016')).toBe(true); + }); + + test('should return false for invalid ISO 8601 date strings', () => { + expect(isISO8601DateTimeString()).toBe(false); + expect(isISO8601DateTimeString('')).toBe(false); + expect(isISO8601DateTimeString('qsdqsd')).toBe(false); + expect(isISO8601DateTimeString('2016-02-01-')).toBe(false); + expect(isISO8601DateTimeString('2021-01-01T00:00:00.')).toBe(false); + }); + }); + + describe('isISO9075DateString', () => { + test('should return true for valid ISO 9075 date strings', () => { + expect(isISO9075DateString('2022-01-01 12:00:00Z')).toBe(true); + expect(isISO9075DateString('2022-01-01 12:00:00.123456Z')).toBe(true); + expect(isISO9075DateString('2022-01-01 12:00:00+01:00')).toBe(true); + expect(isISO9075DateString('2022-01-01 12:00:00-05:00')).toBe(true); + }); + + test('should return false for invalid ISO 9075 date strings', () => { + expect(isISO9075DateString('2022/01/01T12:00:00Z')).toBe(false); + expect(isISO9075DateString('2022-01-01 12:00:00.123456789Z')).toBe(false); + expect(isISO9075DateString('2022-01-01 12:00:00+1:00')).toBe(false); + expect(isISO9075DateString('2022-01-01 12:00:00-05:')).toBe(false); + expect(isISO9075DateString('2022-01-01 12:00:00-05:00:00')).toBe(false); + expect(isISO9075DateString('2022-01-01')).toBe(false); + expect(isISO9075DateString('12:00:00Z')).toBe(false); + expect(isISO9075DateString('2022-01-01T12:00:00Zfoo')).toBe(false); + }); + }); + + describe('isRFC3339DateString', () => { + test('should return true for valid RFC 3339 date strings', () => { + expect(isRFC3339DateString('2022-01-01T12:00:00Z')).toBe(true); + expect(isRFC3339DateString('2022-01-01T12:00:00.123456789Z')).toBe(true); + expect(isRFC3339DateString('2022-01-01T12:00:00.123456789+01:00')).toBe(true); + expect(isRFC3339DateString('2022-01-01T12:00:00-05:00')).toBe(true); + }); + + test('should return false for invalid RFC 3339 date strings', () => { + expect(isRFC3339DateString('2022/01/01T12:00:00Z')).toBe(false); + expect(isRFC3339DateString('2022-01-01T12:00:00.123456789+1:00')).toBe(false); + expect(isRFC3339DateString('2022-01-01T12:00:00-05:')).toBe(false); + expect(isRFC3339DateString('2022-01-01T12:00:00-05:00:00')).toBe(false); + expect(isRFC3339DateString('2022-01-01')).toBe(false); + expect(isRFC3339DateString('12:00:00Z')).toBe(false); + expect(isRFC3339DateString('2022-01-01T12:00:00Zfoo')).toBe(false); + }); + }); + + describe('isRFC7231DateString', () => { + test('should return true for valid RFC 7231 date strings', () => { + expect(isRFC7231DateString('Sun, 06 Nov 1994 08:49:37 GMT')).toBe(true); + expect(isRFC7231DateString('Tue, 22 Apr 2014 07:00:00 GMT')).toBe(true); + }); + + test('should return false for invalid RFC 7231 date strings', () => { + expect(isRFC7231DateString('06 Nov 1994 08:49:37 GMT')).toBe(false); + expect(isRFC7231DateString('Sun, 06 Nov 94 08:49:37 GMT')).toBe(false); + expect(isRFC7231DateString('Sun, 06 Nov 1994 8:49:37 GMT')).toBe(false); + expect(isRFC7231DateString('Sun, 06 Nov 1994 08:49:37 GMT-0500')).toBe(false); + expect(isRFC7231DateString('Sun, 06 November 1994 08:49:37 GMT')).toBe(false); + expect(isRFC7231DateString('Sunday, 06 Nov 1994 08:49:37 GMT')).toBe(false); + expect(isRFC7231DateString('06 Nov 1994')).toBe(false); + }); + }); + + describe('isUnixTimestamp', () => { + test('should return true for valid Unix timestamps', () => { + expect(isUnixTimestamp('1649789394')).toBe(true); + expect(isUnixTimestamp('1234567890')).toBe(true); + expect(isUnixTimestamp('0')).toBe(true); + }); + + test('should return false for invalid Unix timestamps', () => { + expect(isUnixTimestamp('foo')).toBe(false); + expect(isUnixTimestamp('')).toBe(false); + }); + }); + + describe('isTimestamp', () => { + test('should return true for valid Unix timestamps in milliseconds', () => { + expect(isTimestamp('1649792026123')).toBe(true); + expect(isTimestamp('1234567890000')).toBe(true); + expect(isTimestamp('0')).toBe(true); + }); + + test('should return false for invalid Unix timestamps in milliseconds', () => { + expect(isTimestamp('foo')).toBe(false); + expect(isTimestamp('')).toBe(false); + }); + }); + + describe('isUTCDateString', () => { + test('should return true for valid UTC date strings', () => { + expect(isUTCDateString('Sun, 06 Nov 1994 08:49:37 GMT')).toBe(true); + expect(isUTCDateString('Tue, 22 Apr 2014 07:00:00 GMT')).toBe(true); + }); + + test('should return false for invalid UTC date strings', () => { + expect(isUTCDateString('06 Nov 1994 08:49:37 GMT')).toBe(false); + expect(isUTCDateString('16497920261')).toBe(false); + expect(isUTCDateString('foo')).toBe(false); + expect(isUTCDateString('')).toBe(false); + }); + }); + + describe('isMongoObjectId', () => { + test('should return true for valid Mongo ObjectIds', () => { + expect(isMongoObjectId('507f1f77bcf86cd799439011')).toBe(true); + expect(isMongoObjectId('507f1f77bcf86cd799439012')).toBe(true); + }); + + test('should return false for invalid Mongo ObjectIds', () => { + expect(isMongoObjectId('507f1f77bcf86cd79943901')).toBe(false); + expect(isMongoObjectId('507f1f77bcf86cd79943901z')).toBe(false); + expect(isMongoObjectId('foo')).toBe(false); + expect(isMongoObjectId('')).toBe(false); + }); + }); +}); diff --git a/src/tools/date-time-converter/date-time-converter.models.ts b/src/tools/date-time-converter/date-time-converter.models.ts new file mode 100644 index 00000000..3697c166 --- /dev/null +++ b/src/tools/date-time-converter/date-time-converter.models.ts @@ -0,0 +1,46 @@ +import _ from 'lodash'; + +export { + isISO8601DateTimeString, + isISO9075DateString, + isRFC3339DateString, + isRFC7231DateString, + isUnixTimestamp, + isTimestamp, + isUTCDateString, + isMongoObjectId, +}; + +const ISO8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; +const ISO9075_REGEX = + /^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,6})?(([+-])([0-9]{2}):([0-9]{2})|Z)?$/; + +const RFC3339_REGEX = + /^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,9})?(([+-])([0-9]{2}):([0-9]{2})|Z)$/; + +const RFC7231_REGEX = /^[A-Za-z]{3},\s[0-9]{2}\s[A-Za-z]{3}\s[0-9]{4}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\sGMT$/; + +function createRegexMatcher(regex: RegExp) { + return (date?: string) => !_.isNil(date) && regex.test(date); +} + +const isISO8601DateTimeString = createRegexMatcher(ISO8601_REGEX); +const isISO9075DateString = createRegexMatcher(ISO9075_REGEX); +const isRFC3339DateString = createRegexMatcher(RFC3339_REGEX); +const isRFC7231DateString = createRegexMatcher(RFC7231_REGEX); +const isUnixTimestamp = createRegexMatcher(/^[0-9]{1,10}$/); +const isTimestamp = createRegexMatcher(/^[0-9]{1,13}$/); +const isMongoObjectId = createRegexMatcher(/^[0-9a-fA-F]{24}$/); + +function isUTCDateString(date?: string) { + if (_.isNil(date)) { + return false; + } + + try { + return new Date(date).toUTCString() === date; + } catch (_ignored) { + return false; + } +} diff --git a/src/tools/date-time-converter/date-time-converter.types.ts b/src/tools/date-time-converter/date-time-converter.types.ts new file mode 100644 index 00000000..7c78b115 --- /dev/null +++ b/src/tools/date-time-converter/date-time-converter.types.ts @@ -0,0 +1,8 @@ +export type ToDateMapper = (value: string) => Date; + +export type DateFormat = { + name: string; + fromDate: (date: Date) => string; + toDate: (value: string) => Date; + formatMatcher: (dateString: string) => boolean; +}; diff --git a/src/tools/date-time-converter/date-time-converter.vue b/src/tools/date-time-converter/date-time-converter.vue index c83a8b5f..c18b1b71 100644 --- a/src/tools/date-time-converter/date-time-converter.vue +++ b/src/tools/date-time-converter/date-time-converter.vue @@ -1,44 +1,38 @@