From f248b78945b1bc7fdb84bfaf74d8756fd3086411 Mon Sep 17 00:00:00 2001 From: cv <34649812+cpvalente@users.noreply.github.com> Date: Tue, 18 Apr 2023 23:15:15 +0200 Subject: [PATCH] refactor: changes in time input --- .../components/input/time-input/TimeInput.tsx | 20 +-- .../common/utils/__tests__/dateConfig.test.js | 159 +++++++++++------- apps/client/src/common/utils/dateConfig.ts | 59 ++++--- 3 files changed, 140 insertions(+), 98 deletions(-) diff --git a/apps/client/src/common/components/input/time-input/TimeInput.tsx b/apps/client/src/common/components/input/time-input/TimeInput.tsx index cee4ee974..bbdeb6e69 100644 --- a/apps/client/src/common/components/input/time-input/TimeInput.tsx +++ b/apps/client/src/common/components/input/time-input/TimeInput.tsx @@ -26,19 +26,17 @@ export default function TimeInput(props: TimeInputProps) { const { emitError } = useEmitLog(); const inputRef = useRef(null); const [value, setValue] = useState(''); - // avoid wrong submit on cancel - let ignoreChange = false; + const ignoreChange = useRef(false); /** * @description Resets input value to given */ const resetValue = useCallback(() => { try { - // eslint-disable-next-line -- we use ignore change to stop submit on cancel - ignoreChange = true; setValue(millisToString(time)); } catch (error) { - emitError(`Unable to parse date: ${error}`); + setValue(millisToString(0)); + emitError(`Unable to parse time ${time}: ${error}`); } }, [emitError, time]); @@ -97,11 +95,6 @@ export default function TimeInput(props: TimeInputProps) { */ const validateAndSubmit = useCallback( (newValue: string) => { - if (ignoreChange) { - // eslint-disable-next-line -- we use this to prevent a wrong submit - ignoreChange = false; - return; - } const success = handleSubmit(newValue); if (success) { const ms = forgivingStringToMillis(newValue); @@ -111,7 +104,7 @@ export default function TimeInput(props: TimeInputProps) { resetValue(); } }, - [delay, handleSubmit, resetValue], + [delay, handleSubmit, name, resetValue], ); /** @@ -127,6 +120,7 @@ export default function TimeInput(props: TimeInputProps) { validateAndSubmit((event.target as HTMLInputElement).value); } if (event.key === 'Escape') { + ignoreChange.current = true; inputRef.current?.blur(); resetValue(); } @@ -136,6 +130,10 @@ export default function TimeInput(props: TimeInputProps) { const onBlurHandler = useCallback( (event: FocusEvent) => { + if (ignoreChange.current) { + ignoreChange.current = false; + return; + } validateAndSubmit((event.target as HTMLInputElement).value); }, [validateAndSubmit], diff --git a/apps/client/src/common/utils/__tests__/dateConfig.test.js b/apps/client/src/common/utils/__tests__/dateConfig.test.js index 6180c5a20..4eed5ec8f 100644 --- a/apps/client/src/common/utils/__tests__/dateConfig.test.js +++ b/apps/client/src/common/utils/__tests__/dateConfig.test.js @@ -203,12 +203,6 @@ describe('test forgivingStringToMillis()', () => { { value: '-0', expect: 0 }, { value: '1', expect: 60 * 1000 }, { value: '-1', expect: 60 * 1000 }, - { value: '000000', expect: 0 }, - { value: '000001', expect: 1000 }, - { value: '000100', expect: 1000 * 60 }, - { value: '010000', expect: 1000 * 60 * 60 }, - { value: '230000', expect: 1000 * 60 * 60 * 23 }, - { value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, { value: '0h0m0s', expect: 0 }, { value: '0h0m1s', expect: 1000 }, { value: '0h1m0s', expect: 1000 * 60 }, @@ -225,10 +219,6 @@ describe('test forgivingStringToMillis()', () => { expect(typeof forgivingStringToMillis(s.value)).toBe('number'); expect(forgivingStringToMillis(s.value)).toBe(s.expect); }); - it(`handles ${s.value} to right`, () => { - expect(typeof forgivingStringToMillis(s.value, false)).toBe('number'); - expect(forgivingStringToMillis(s.value, false)).toBe(s.expect); - }); } }); @@ -244,16 +234,113 @@ describe('test forgivingStringToMillis()', () => { it(`handles ${s.value} to the left`, () => { expect(forgivingStringToMillis(s.value)).toBe(s.expect); }); - it(`handles ${s.value} to the right`, () => { + } + }); + + describe('it infers separators when non existent', () => { + const docs = [ + { value: '1', expect: 1000 * 60 }, // 00:01:00 + { value: '12', expect: 1000 * 60 * 12 }, // 00:12:00 + { value: '123', expect: 1000 * 60 * 23 + 1000 * 60 * 60 }, // 01:23:00 + { value: '1234', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 }, // 12:34:00 + { value: '12345', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 5 * 1000 }, // 12:34:05 + { value: '123456', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 56 * 1000 }, // 12:34:56 + ]; + + for (const s of docs) { + it(`handles basic strings digits: ${s.value}`, () => { expect(forgivingStringToMillis(s.value)).toBe(s.expect); }); } + + const sixDigits = [ + { value: '000000', expect: 0 }, + { value: '000001', expect: 1000 }, + { value: '000100', expect: 1000 * 60 }, + { value: '010000', expect: 1000 * 60 * 60 }, + { value: '230000', expect: 1000 * 60 * 60 * 23 }, + { value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, + ]; + + for (const s of sixDigits) { + it(`handles string with 6 digits: ${s.value}`, () => { + expect(forgivingStringToMillis(s.value)).toBe(s.expect); + }); + } + + const fiveDigits = [ + { value: '00000', expect: 0 }, + { value: '00001', expect: 1000 }, // 00:00:01 + { value: '00010', expect: 1000 * 60 }, // 00:01:00 + { value: '00100', expect: 1000 * 60 * 10 }, // 00:10:00 + { value: '01000', expect: 1000 * 60 * 60 }, // 01:00:00 + { value: '10000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00 + { value: '23000', expect: 1000 * 60 * 60 * 23 }, // 23:00:00 + { value: '12121', expect: 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:01 + ]; + + for (const s of fiveDigits) { + it(`handles string with 5 digits: ${s.value}`, () => { + expect(forgivingStringToMillis(s.value)).toBe(s.expect); + }); + } + + const fourDigits = [ + { value: '0000', expect: 0 }, + { value: '0001', expect: 1000 * 60 }, // 00:01:00 + { value: '0010', expect: 1000 * 60 * 10 }, // 00:10:00 + { value: '0100', expect: 1000 * 60 * 60 }, // 01:00:00 + { value: '1000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00 + { value: '2300', expect: 1000 * 60 * 60 * 23 }, // 23:00:00 + { value: '1212', expect: 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:00 + ]; + + for (const s of fourDigits) { + it(`handles string with 4 digits: ${s.value}`, () => { + expect(forgivingStringToMillis(s.value)).toBe(s.expect); + }); + } + + const threeDigits = [ + { value: '000', expect: 0 }, + { value: '001', expect: 1000 * 60 }, // 00:01:00 + { value: '010', expect: 1000 * 60 * 10 }, // 00:10:00 + { value: '100', expect: 1000 * 60 * 60 }, // 01:00:00 + { value: '230', expect: 2 * 1000 * 60 * 60 + 30 * 1000 * 60 }, // 02:30:00 + { value: '121', expect: 21 * 60 * 1000 + 1000 * 60 * 60 }, // 01:21:00 + ]; + + for (const s of threeDigits) { + it(`handles string with 3 digits: ${s.value}`, () => { + expect(forgivingStringToMillis(s.value)).toBe(s.expect); + }); + } + + const twoDigits = [ + { value: '00', expect: 0 }, + { value: '01', expect: 1000 * 60 }, // 00:01:00 + { value: '10', expect: 1000 * 60 * 10 }, // 00:10:00 + { value: '23', expect: 1000 * 60 * 23 }, // 00:23:00 + ]; + + for (const s of twoDigits) { + it(`handles string with 2 digits: ${s.value}`, () => { + expect(forgivingStringToMillis(s.value)).toBe(s.expect); + }); + } + + const singleDigit = [...Array(10).keys()]; + + for (const s of singleDigit) { + it(`handles string with a single digits ${s}`, () => { + expect(forgivingStringToMillis(`${s}`)).toBe(s * 1000 * 60); + }); + } }); describe('handles overflows', () => { const ts = [ // minutes overflow - { value: '120', expect: 1000 * 60 * 120 }, { value: '2.0.0', expect: 1000 * 60 * 120 }, { value: '99', expect: 1000 * 60 * 99 }, { value: '1.39.0', expect: 1000 * 60 * 99 }, @@ -272,57 +359,9 @@ describe('test forgivingStringToMillis()', () => { it(`handles ${s.value} to the left`, () => { expect(forgivingStringToMillis(s.value)).toBe(s.expect); }); - it(`handles ${s.value} to the right`, () => { - expect(forgivingStringToMillis(s.value, false)).toBe(s.expect); - }); } }); - describe('test with fillRight (legacy)', () => { - describe('function handles separators', () => { - const testData = [ - { value: '1:2:3:10', expect: 3723000 }, - { value: '2,10', expect: 130000 }, - { value: '2.10', expect: 130000 }, - { value: '2 10', expect: 130000 }, - ]; - - for (const s of testData) { - it(`handles ${s.value}`, () => { - expect(typeof forgivingStringToMillis(s.value, false)).toBe('number'); - expect(forgivingStringToMillis(s.value, false)).toBe(s.expect); - }); - } - }); - - describe('parses strings correctly', () => { - const ts = [ - { value: '1.2', expect: 60 * 1000 + 2 * 1000 }, - { value: '1.70', expect: 60 * 1000 + 70 * 1000 }, - ]; - - for (const s of ts) { - it(`handles ${s.value}`, () => { - expect(forgivingStringToMillis(s.value, false)).toBe(s.expect); - }); - } - }); - - describe('handles overflows', () => { - const ts = [ - // minutes overflow - { value: '0.120', expect: 120 * 1000 }, - { value: '0.99', expect: 99 * 1000 }, - ]; - - for (const s of ts) { - it(`handles ${s.value}`, () => { - expect(forgivingStringToMillis(s.value, false)).toBe(s.expect); - }); - } - }); - }); - describe('test with fillLeft', () => { describe('function handles separators', () => { const testData = [ diff --git a/apps/client/src/common/utils/dateConfig.ts b/apps/client/src/common/utils/dateConfig.ts index 4adc17fe4..5bf35edfb 100644 --- a/apps/client/src/common/utils/dateConfig.ts +++ b/apps/client/src/common/utils/dateConfig.ts @@ -83,17 +83,21 @@ const parse = (valueAsString: string): number => { }; /** - * @description Parses a time string to millis + * @description Parses a time string to millis, auto-filling to the left * @param {string} value - time string - * @param {boolean} fillLeft - autofill left = hours / right = seconds * @returns {number} - time string in millis */ -export const forgivingStringToMillis = (value: string, fillLeft = true): number => { +export const forgivingStringToMillis = (value: string): number => { let millis = 0; - const hours = parseInt(value.match(/(\d+)h/)?.[0] ?? 0, 10); - const minutes = parseInt(value.match(/(\d+)m/)?.[0] ?? 0, 10); - const seconds = parseInt(value.match(/(\d+)s/)?.[0] ?? 0, 10); + const hoursMatch = value.match(/(\d+)h/); + const hours = hoursMatch ? parse(hoursMatch[1]) : 0; + + const minutesMatch = value.match(/(\d+)m/); + const minutes = minutesMatch ? parse(minutesMatch[1]) : 0; + + const secondsMatch = value.match(/(\d+)s/); + const seconds = secondsMatch ? parse(secondsMatch[1]) : 0; if (hours > 0 || minutes > 0 || seconds > 0) { millis = hours * mth + minutes * mtm + seconds * mts; @@ -108,31 +112,32 @@ export const forgivingStringToMillis = (value: string, fillLeft = true): number millis += parse(second) * mtm; millis += parse(third) * mts; } else if (first != null && second == null && third == null) { - // if string has one section, - // could be a complete string like 121010 - 12:10:10 - if (first.length === 6) { - const hours = first.substring(0, 2); - const minutes = first.substring(2, 4); - const seconds = first.substring(4); - millis = parse(hours) * mth; - millis += parse(minutes) * mtm; - millis += parse(seconds) * mts; - } else { - // otherwise lets treat as [minutes] + // we only have one section, infer separators + + const length = first.length; + if (length === 1) { millis = parse(first) * mtm; + } else if (length === 2) { + millis = parse(first) * mtm; + } else if (length === 3) { + millis = parse(first[0]) * mth + parse(first.substring(1)) * mtm; + } else if (length === 4) { + millis = parse(first.substring(0, 2)) * mth + parse(first.substring(2)) * mtm; + } else if (length === 5) { + const hours = parse(first.substring(0, 2)); + const minutes = parse(first.substring(2, 4)); + const seconds = parse(first.substring(4)); + millis = hours * mth + minutes * mtm + seconds * mts; + } else if (length >= 6) { + const hours = parse(first.substring(0, 2)); + const minutes = parse(first.substring(2, 4)); + const seconds = parse(first.substring(4)); + millis = hours * mth + minutes * mtm + seconds * mts; } } if (first != null && second != null && third == null) { - // if string has two sections - if (fillLeft) { - // treat as [hours] [minutes] - millis = parse(first) * mth; - millis += parse(second) * mtm; - } else { - // treat as [minutes] [seconds] - millis = parse(first) * mtm; - millis += parse(second) * mts; - } + millis = parse(first) * mth; + millis += parse(second) * mtm; } }