From 10b8d9ecd482aa10566d04c4279c742e3199cb19 Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 15 Nov 2021 19:17:55 -0500 Subject: [PATCH] Version 1.4.4: Fixed bug when inputing a value smaller than 0 in a field --- package-lock.json | 4 +- package.json | 2 +- .../__snapshots__/durationField.test.tsx.snap | 51 ++++++ src/__tests__/durationField.test.tsx | 64 ++++++++ .../durationFieldsContainer.test.tsx | 62 ++++++-- src/__tests__/durationPicker.test.tsx | 9 +- src/__tests__/utils.test.ts | 117 ++++++++------ src/constants.ts | 36 +++++ src/durationDialog.tsx | 19 ++- src/durationField.tsx | 19 +-- src/durationFieldsContainer.tsx | 49 +++--- src/durationPicker.tsx | 8 +- src/index.tsx | 1 + src/utils.ts | 146 +++++------------- 14 files changed, 361 insertions(+), 226 deletions(-) create mode 100644 src/__tests__/__snapshots__/durationField.test.tsx.snap create mode 100644 src/__tests__/durationField.test.tsx create mode 100644 src/constants.ts diff --git a/package-lock.json b/package-lock.json index d6ab4d1..7193d12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "material-duration-picker", - "version": "1.4.3", + "version": "1.4.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "material-duration-picker", - "version": "1.4.3", + "version": "1.4.4", "license": "MIT", "devDependencies": { "@material-ui/core": "^4.9.1", diff --git a/package.json b/package.json index c4ac60f..abd91e3 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "duration-picker", "durations" ], - "version": "1.4.3", + "version": "1.4.4", "license": "MIT", "main": "dist/index.js", "author": "tran-simon", diff --git a/src/__tests__/__snapshots__/durationField.test.tsx.snap b/src/__tests__/__snapshots__/durationField.test.tsx.snap new file mode 100644 index 0000000..4dddfe0 --- /dev/null +++ b/src/__tests__/__snapshots__/durationField.test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`durationField can match snapshot 1`] = ` + +
+ +
+ +
+
+
+`; + +exports[`durationField can match snapshot with custom labels 1`] = ` + +
+ +
+ +
+
+
+`; diff --git a/src/__tests__/durationField.test.tsx b/src/__tests__/durationField.test.tsx new file mode 100644 index 0000000..6565fc0 --- /dev/null +++ b/src/__tests__/durationField.test.tsx @@ -0,0 +1,64 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import * as React from "react"; +import {DurationField} from "../durationField"; + +describe('durationField', ()=>{ + + it('can match snapshot', async () => { + await waitFor(() => { + expect( + render( + { + }} + /> + ).asFragment() + ).toMatchSnapshot(); + }); + }) + + it('can match snapshot with custom labels', async () => { + await waitFor(() => { + expect( + render( + { + }} + /> + ).asFragment() + ).toMatchSnapshot(); + }); + }) + + + it('can confirm value when value is invalid', async () => { + + const mockOnConfirm = jest.fn() + + const utils = render( + + ) + + const field: any= utils.getByTitle('fieldTitle') + + expect(field.value).toBe('30') + fireEvent.change(field, {target: {value: NaN}}) + fireEvent.blur(field) + expect(field.value).toBe('') + expect(mockOnConfirm).toHaveBeenCalledWith(undefined) + }) +}) diff --git a/src/__tests__/durationFieldsContainer.test.tsx b/src/__tests__/durationFieldsContainer.test.tsx index 77e1ac5..ea3998b 100644 --- a/src/__tests__/durationFieldsContainer.test.tsx +++ b/src/__tests__/durationFieldsContainer.test.tsx @@ -1,22 +1,24 @@ import * as React from 'react' import {render, waitFor, screen, fireEvent} from '@testing-library/react' import {DurationFieldsContainer, DurationFieldsContainerProps} from "../durationFieldsContainer"; +import {durationToTime, timeToDuration} from "../utils"; +import {useState} from "react"; describe('DurationFieldsContainer', () => { - const setDurationMock = jest.fn() + const setValueMock = jest.fn() const Comp = ({ views = ['hours', 'minutes'], duration = {hours: 5, minutes: 2}, - setDuration = setDurationMock, + setValue= setValueMock, ...props - }: Partial) => { + }: any) => { return ( { expect(fields[0].value).toBe('61') - expect(setDurationMock).toHaveBeenCalledWith({ - weeks: undefined, - days: undefined, + expect(setValueMock).toHaveBeenCalledWith(durationToTime({ hours: 61, minutes: 1, - seconds: undefined - }) + })); }) it('can setDuration to smaller than views show', async () => { @@ -103,12 +102,45 @@ describe('DurationFieldsContainer', () => { expect(fields[1].value).toBe('0.5') - expect(setDurationMock).toHaveBeenCalledWith({ - weeks: undefined, - days: undefined, + expect(setValueMock).toHaveBeenCalledWith(durationToTime({ hours: 1, minutes: 0.5, - seconds: undefined - }) + })); + }) + + it('can set a view to a value smaller than 0', async ()=>{ + + const Comp = () => { + + const [value, setValue] = useState(60) + + return + } + + const utils = render( + + ); + + const fields: any[] = utils.getAllByTitle('fieldTitle') + + fireEvent.change(fields[0], {target: {value: '0.5'}}) + + expect(fields[0].value).toBe('0.5') + + fireEvent.blur(fields[0]) + + expect(fields[0].value).toBe('') + expect(fields[1].value).toBe('721') }) }) diff --git a/src/__tests__/durationPicker.test.tsx b/src/__tests__/durationPicker.test.tsx index 825b3b7..6f63223 100644 --- a/src/__tests__/durationPicker.test.tsx +++ b/src/__tests__/durationPicker.test.tsx @@ -2,13 +2,12 @@ import * as React from 'react' import {render, waitFor} from "@testing-library/react"; import {DurationPicker} from "../durationPicker"; import * as DurationDialog from '../durationDialog' +import {DurationField} from "../durationField"; +import {timeToDuration} from "../utils"; -jest.mock('../durationDialog', () => { - return { - DurationDialog: ({duration}: any) => (
{JSON.stringify(duration)}
) - } -}) +jest.spyOn(DurationDialog, 'DurationDialog') + .mockImplementation(({time}: any)=>
{JSON.stringify(timeToDuration(time))}
) describe('durationPicker', () => { const onValueChange = jest.fn() diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 7d09997..1fbd8f0 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,4 +1,5 @@ -import {durationToTime, getDurationOverflow, getDurationUnderflow, getValue, timeToDuration, toSecondsMultipliers} from "../utils"; +import {durationToTime, getValue, timeToDuration} from "../utils"; +import {toSecondsMultipliers} from "../constants"; describe('getValue', ()=>{ it('can getValue', ()=>{ @@ -7,6 +8,7 @@ describe('getValue', ()=>{ expect(getValue(3600, 'hours')).toEqual(1) expect(getValue(3600, 'minutes')).toEqual(60) expect(getValue(75, 'minutes')).toEqual(1); //1.25 rounded down + expect(getValue(75, 'minutes', false)).toEqual(1.25); //1.25 rounded down expect(getValue(75, 'seconds')).toEqual(75) }) it('can getValue with negatives', ()=>{ @@ -15,6 +17,7 @@ describe('getValue', ()=>{ expect(getValue(-3600, 'hours')).toEqual(-1) expect(getValue(-3600, 'minutes')).toEqual(-60) expect(getValue(-75, 'minutes')).toEqual(-1); //-1.25 rounded up + expect(getValue(-75, 'minutes', false)).toEqual(-1.25); //-1.25 rounded up expect(getValue(-75, 'seconds')).toEqual(-75) }) }) @@ -33,8 +36,8 @@ describe('timeToDuration', ()=>{ expect(timeToDuration(time)).toEqual(expected) }) - it('can timeToDuration with null', ()=>{ - expect(timeToDuration(null)).toEqual({}) + it('can timeToDuration with undefined', ()=>{ + expect(timeToDuration(undefined)).toEqual({}) }) it('can get timeToDuration with 0 values', ()=>{ @@ -63,8 +66,72 @@ describe('timeToDuration', ()=>{ expect(timeToDuration(time)).toEqual(expected) }) + it('can timeToDuration and keep zeroes', ()=>{ + expect(timeToDuration(0, ['hours', 'seconds'], true)).toEqual({ + hours: 0, + seconds: 0 + }) + }) + it('can timeToDuration with custom views', ()=>{ expect(timeToDuration(toSecondsMultipliers.days * 30, ['days'])).toEqual({days: 30}) + + expect(timeToDuration(durationToTime({ + days: 1, + hours: 2, + minutes: 3, + seconds: 30 + }) || 0 , ['hours', 'minutes'])).toEqual({ + hours: 26, + minutes: 3.5 + }) + + expect(timeToDuration(durationToTime({ + days: 1, + hours: 2, + minutes: 3, + seconds: 30 + }) || 0, ['days', 'minutes'])).toEqual({ + days: 1, + minutes: 123.5 + }) + + expect(timeToDuration(durationToTime({ + days: 1, + hours: 2, + minutes: 3, + seconds: 30 + }) || 0, ['hours', 'minutes'])).toEqual({ + hours: 26, + minutes: 3.5 + }) + + expect(timeToDuration(durationToTime({ + minutes: 1.5, + seconds: 30.5 + }) || 0, ['hours', 'seconds'])).toEqual({ + seconds: 120.5 + }); + + expect(timeToDuration(durationToTime({ + minutes: 3, + seconds: 30 + }) || 0, ['hours', 'minutes'])).toEqual({ + minutes: 3.5 + }); + + expect(timeToDuration(durationToTime({ + hours: 1, + minutes: 30, + }) || 0, ['days'])).toEqual({ + days: 0.0625 + }); + + expect(timeToDuration(durationToTime({ + hours: 1, + minutes: 30, + }) || 0, [])).toEqual({ + }); }) }) @@ -126,47 +193,3 @@ describe('durationToTime', ()=>{ expect(durationToTime({})).toEqual(undefined) }) }) - -describe('getDurationOverflow', ()=>{ - it('can getDurationOverflow', ()=>{ - const duration = { - weeks: undefined, - days: 2, - hours: 3, - minutes: 4, - seconds: 5 - } - - const expected = 2 * 24 * 60 + 3 * 60 - - expect(getDurationOverflow(duration, 'minutes')).toEqual(expected) - }) -}) - -describe('getDurationUnderflow', ()=>{ - it('can getDurationUnderflow', ()=>{ - const duration = { - weeks: undefined, - days: 2, - hours: 3, - minutes: 4, - seconds: 5 - } - - const expected = 4/60 + 5/60/60; - - expect(getDurationUnderflow(duration, 'hours')).toEqual(expected) - - expect(getDurationUnderflow({ - minutes: 1, - seconds: 1 - }, 'minutes')).toEqual(1/60) - - expect(getDurationUnderflow({ - days:3, - hours:2, - minutes: undefined, - seconds: 45 - }, 'days')).toEqual(2/24 + 45 / 60 / 60 / 24) - }) -}) diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..95a49c3 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,36 @@ +import {DurationView} from "./types"; + +export const VIEWS: DurationView[] = ['weeks', 'days', 'hours', 'minutes', 'seconds'] + +export const toSecondsMultipliers: { [key in DurationView]: number } = { + seconds: 1, + minutes: 60, + hours: 60 * 60, + days: 60 * 60 * 24, + weeks: 60 * 60 * 24 * 7 +} + + +export type Converter = { + [key in DurationView]: number +} + +const createConverter = (from: DurationView)=>{ + const converter: Converter = {} as Converter + + for (let i = 0; i < VIEWS.length; i++) { + const to = VIEWS[i] + converter[to] = toSecondsMultipliers[from] / toSecondsMultipliers[to] + } + return converter +} + +export type Converters = { + [key in DurationView]: Converter +} + +export const converters = VIEWS.reduce((converters, view)=>{ + converters[view] = createConverter(view); + return converters +}, {} as Converters) + diff --git a/src/durationDialog.tsx b/src/durationDialog.tsx index a9ad6b9..e086193 100644 --- a/src/durationDialog.tsx +++ b/src/durationDialog.tsx @@ -4,11 +4,12 @@ import {Button, Dialog, DialogActions, DialogContent, DialogProps, Toolbar, Typo import {DurationType, DurationView, Labels} from "./types"; import {DurationFieldsContainer, DurationFieldsContainerProps} from "./durationFieldsContainer"; import DefaultLabels from './defaultLabelsEn.json' +import {timeToDuration} from "./utils"; export type DurationDialogProps = DialogProps & { - duration: DurationType + time: number | undefined; onDismiss: () => void; - onAccept: (duration: DurationType) => void + onAccept: (time: number | undefined) => void views: DurationView[] labels?: Labels @@ -19,7 +20,7 @@ export type DurationDialogProps = DialogProps & { } export const DurationDialog = ({ - duration: _duration, + time: _time, onDismiss, onAccept, views, @@ -30,11 +31,13 @@ export const DurationDialog = ({ ...props }: DurationDialogProps) => { const theme = useTheme() - const [duration, setDuration] = useState(_duration) + const [time, setTime] = useState(_time) useEffect(() => { - setDuration(_duration) - }, [_duration, setDuration]) + setTime(_time) + }, [_time]) + + const duration = timeToDuration(time) const labels = { ...DefaultLabels, @@ -56,7 +59,7 @@ export const DurationDialog = ({ - +