From 43fa4962c5b026f808bf4d3826a8e69509f1b62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 27 Mar 2020 11:47:02 +0000 Subject: [PATCH] feat(ui): update silence form duration on mouse wheel Fixes #1553 --- .../SilenceModal/DateTimeSelect/Duration.js | 121 ++++++---- .../SilenceModal/DateTimeSelect/HourMinute.js | 144 +++++++---- .../__snapshots__/index.test.js.snap | 34 +-- .../SilenceModal/DateTimeSelect/index.test.js | 228 ++++++++++++++++++ 4 files changed, 407 insertions(+), 120 deletions(-) diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js index 5076f083c..a1229aadf 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/Duration.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { useEffect, useRef } from "react"; import PropTypes from "prop-types"; import { observer } from "mobx-react"; @@ -7,60 +7,75 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; -const Duration = observer( - class Duration extends Component { - static propTypes = { - value: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - onInc: PropTypes.func.isRequired, - onDec: PropTypes.func.isRequired, +const Duration = observer(({ value, label, onInc, onDec }) => { + const rootRef = useRef(null); + + useEffect(() => { + const cancelWheel = (event) => event.preventDefault(); + + const elem = rootRef.current; + + elem.addEventListener("wheel", cancelWheel, { passive: false }); + + return () => { + elem.removeEventListener("wheel", cancelWheel); }; + }, []); - render() { - const { value, label, onInc, onDec } = this.props; - - return ( -
- - - - - - - - - - - - - -
- - - - -
-

{value}

-
- {label} -
- - - - -
-
- ); + const onWheel = (event) => { + if (event.deltaY < 0) { + onInc(); + } else { + onDec(); } - } -); + }; + + return ( +
+ + + + + + + + + + + + + +
+ + + + +
+

{value}

+
+ {label} +
+ + + + +
+
+ ); +}); +Duration.propTypes = { + value: PropTypes.number.isRequired, + label: PropTypes.string.isRequired, + onInc: PropTypes.func.isRequired, + onDec: PropTypes.func.isRequired, +}; export { Duration }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js index c5f175590..cee2033ac 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js +++ b/ui/src/Components/SilenceModal/DateTimeSelect/HourMinute.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { useEffect, useRef } from "react"; import PropTypes from "prop-types"; import { observer } from "mobx-react"; @@ -9,8 +9,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleUp } from "@fortawesome/free-solid-svg-icons/faAngleUp"; import { faAngleDown } from "@fortawesome/free-solid-svg-icons/faAngleDown"; -const IconTd = ({ icon, onClick }) => ( - +const IconTd = ({ icon, onClick, onWheel, className }) => ( + ( IconTd.propTypes = { icon: FontAwesomeIcon.propTypes.icon.isRequired, onClick: PropTypes.func.isRequired, + onWheel: PropTypes.func.isRequired, + className: PropTypes.string.isRequired, }; const HourMinute = observer( - class HourMinute extends Component { - static propTypes = { - dateValue: PropTypes.instanceOf(moment).isRequired, - onHourInc: PropTypes.func.isRequired, - onHourDec: PropTypes.func.isRequired, - onMinuteInc: PropTypes.func.isRequired, - onMinuteDec: PropTypes.func.isRequired, + ({ dateValue, onHourInc, onHourDec, onMinuteInc, onMinuteDec }) => { + const rootRef = useRef(null); + + useEffect(() => { + const cancelWheel = (event) => event.preventDefault(); + + const elem = rootRef.current; + + elem.addEventListener("wheel", cancelWheel, { passive: false }); + + return () => { + elem.removeEventListener("wheel", cancelWheel); + }; + }, []); + + const onHourWheel = (event) => { + if (event.deltaY < 0) { + onHourInc(); + } else { + onHourDec(); + } }; - render() { - const { - dateValue, - onHourInc, - onHourDec, - onMinuteInc, - onMinuteDec, - } = this.props; + const onMinuteWheel = (event) => { + if (event.deltaY < 0) { + onMinuteInc(); + } else { + onMinuteDec(); + } + }; - const hour = dateValue.hour(); - const minute = dateValue.minute(); + const hour = dateValue.hour(); + const minute = dateValue.minute(); - return ( -
- - - - - - - - - - - - - - -
- -
-

{hour > 9 ? hour : `0${hour}`}

-
-

:

-
-

{minute > 9 ? minute : `0${minute}`}

-
- -
-
- ); - } + return ( +
+ + + + + + + + + + + + + + +
+ +
+

{hour > 9 ? hour : `0${hour}`}

+
+

:

+
+

{minute > 9 ? minute : `0${minute}`}

+
+ +
+
+ ); } ); +HourMinute.propTypes = { + dateValue: PropTypes.instanceOf(moment).isRequired, + onHourInc: PropTypes.func.isRequired, + onHourDec: PropTypes.func.isRequired, + onMinuteInc: PropTypes.func.isRequired, + onMinuteDec: PropTypes.func.isRequired, +}; export { HourMinute }; diff --git a/ui/src/Components/SilenceModal/DateTimeSelect/__snapshots__/index.test.js.snap b/ui/src/Components/SilenceModal/DateTimeSelect/__snapshots__/index.test.js.snap index 0b3858635..f412069e2 100644 --- a/ui/src/Components/SilenceModal/DateTimeSelect/__snapshots__/index.test.js.snap +++ b/ui/src/Components/SilenceModal/DateTimeSelect/__snapshots__/index.test.js.snap @@ -36,7 +36,7 @@ exports[` 'Duration' tab matches snapshot 1`] = `
-
+
@@ -98,7 +98,7 @@ exports[` 'Duration' tab matches snapshot 1`] = `
-
+
@@ -160,7 +160,7 @@ exports[` 'Duration' tab matches snapshot 1`] = `
-
+
@@ -613,11 +613,11 @@ exports[` 'Ends' tab matches snapshot 1`] = ` -
+
- - - - - -
+ 'Ends' tab matches snapshot 1`] = ` + 'Ends' tab matches snapshot 1`] = `
+

00

@@ -667,14 +667,14 @@ exports[` 'Ends' tab matches snapshot 1`] = ` :
+

00

+ 'Ends' tab matches snapshot 1`] = ` + 'Starts' tab matches snapshot 1`] = ` -
+
- - - - - -
+ 'Starts' tab matches snapshot 1`] = ` + 'Starts' tab matches snapshot 1`] = `
+

00

@@ -1155,14 +1155,14 @@ exports[` 'Starts' tab matches snapshot 1`] = ` :
+

00

+ 'Starts' tab matches snapshot 1`] = ` + ", () => { expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); + it("'Duration' tab unmounts without crashing", () => { + const tree = MountedDateTimeSelect(); + tree.unmount(); + }); + it("clicking on the 'Starts' tab switches content to 'startsAt' selection", () => { const tree = MountedDateTimeSelect(); const tab = tree.find(".nav-link").at(0); @@ -74,6 +79,12 @@ describe("", () => { expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); + it("'Starts' tab unmounts without crashing", () => { + const tree = MountedDateTimeSelect(); + tree.find(".nav-link").at(0).simulate("click"); + tree.unmount(); + }); + it("clicking on the 'Ends' tab switches content to 'endsAt' selection", () => { const tree = MountedDateTimeSelect(); const tab = tree.find(".nav-link").at(1); @@ -89,6 +100,12 @@ describe("", () => { expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); + it("'Ends' tab unmounts without crashing", () => { + const tree = MountedDateTimeSelect(); + tree.find(".nav-link").at(1).simulate("click"); + tree.unmount(); + }); + it("clicking on the 'Duration' tabs switches content to duration selection", () => { const tree = MountedDateTimeSelect(); // first switch to 'Starts' @@ -143,6 +160,27 @@ const ValidateTimeButton = ( expect(diffMS).toBe(expectedDiff); }; +const ValidateTimeWheel = (tab, storeKey, className, deltaY, expectedDiff) => { + const elem = tab.find(className); + + const oldTimeValue = moment(silenceFormStore.data[storeKey]); + + elem.simulate("wheel", { deltaY: deltaY }); + // fire real event so cancel listener will trigger + const event = new Event("wheel", { deltaY: deltaY }); + tab + .find("div.components-hour-minute") + .at(0) + .getDOMNode() + .dispatchEvent(event); + + expect(silenceFormStore.data[storeKey].toISOString()).not.toBe( + oldTimeValue.toISOString() + ); + const diffMS = silenceFormStore.data[storeKey].diff(oldTimeValue); + expect(diffMS).toBe(expectedDiff); +}; + const MountedTabContentStart = () => { return mount(); }; @@ -164,20 +202,96 @@ describe("", () => { ValidateTimeButton(tree, "startsAt", 0, /angle-up/, 3600 * 1000); }); + it("scrolling up on the hour button adds 1h to startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-hour-up", + -1, + 3600 * 1000 + ); + }); + it("clicking on the minute inc button adds 1m to startsAt", () => { const tree = MountedTabContentStart(); ValidateTimeButton(tree, "startsAt", 1, /angle-up/, 60 * 1000); }); + it("scrolling up on the minute button adds 1m to startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-minute-up", + -2, + 60 * 1000 + ); + }); + it("clicking on the hour dec button subtracts 1h from startsAt", () => { const tree = MountedTabContentStart(); ValidateTimeButton(tree, "startsAt", 2, /angle-down/, -1 * 3600 * 1000); }); + it("scrolling down on the hour button subtracts 1h from startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-hour-down", + 1, + -1 * 3600 * 1000 + ); + }); + + it("scrolling up on the minute adds 1m to startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel(tree, "startsAt", "td.components-minute", -2, 60 * 1000); + }); + + it("scrolling down on the minute subtracts 1m from startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-minute", + 1, + -1 * 60 * 1000 + ); + }); + it("clicking on the minute dec button subtracts 1m from startsAt", () => { const tree = MountedTabContentStart(); ValidateTimeButton(tree, "startsAt", 3, /angle-down/, -1 * 60 * 1000); }); + + it("scrolling down on the minute button subtracts 1m from startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-minute-down", + 2, + -1 * 60 * 1000 + ); + }); + + it("scrolling up on the minute subtracts 1m from startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel(tree, "startsAt", "td.components-minute", -50, 60 * 1000); + }); + + it("scrolling down on the minute subtracts 1m from startsAt", () => { + const tree = MountedTabContentStart(); + ValidateTimeWheel( + tree, + "startsAt", + "td.components-minute", + 1, + -1 * 60 * 1000 + ); + }); }); const MountedTabContentEnd = () => { @@ -201,20 +315,90 @@ describe("", () => { ValidateTimeButton(tree, "endsAt", 0, /angle-up/, 3600 * 1000); }); + it("scrolling up on the hour button adds 1h to endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel(tree, "endsAt", "td.components-hour-up", -1, 3600 * 1000); + }); + + it("scrolling up on the hour adds 1h to endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel(tree, "endsAt", "td.components-hour", -1, 3600 * 1000); + }); + + it("scrolling down on the hour subtracts 1h from endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel( + tree, + "endsAt", + "td.components-hour", + 1, + -1 * 3600 * 1000 + ); + }); + it("clicking on the minute inc button adds 1m to endsAt", () => { const tree = MountedTabContentEnd(); ValidateTimeButton(tree, "endsAt", 1, /angle-up/, 60 * 1000); }); + it("scrolling up on the minute button adds 1m to endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel( + tree, + "endsAt", + "td.components-minute-up", + -10, + 60 * 1000 + ); + }); + it("clicking on the hour dec button subtracts 1h from endsAt", () => { const tree = MountedTabContentEnd(); ValidateTimeButton(tree, "endsAt", 2, /angle-down/, -1 * 3600 * 1000); }); + it("scrolling down on the hour button subtracts 1h from endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel( + tree, + "endsAt", + "td.components-hour-down", + 1, + -1 * 3600 * 1000 + ); + }); + it("clicking on the minute dec button subtracts 1m from endsAt", () => { const tree = MountedTabContentEnd(); ValidateTimeButton(tree, "endsAt", 3, /angle-down/, -1 * 60 * 1000); }); + + it("scrolling down on the minute button subtracts 1m from endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel( + tree, + "endsAt", + "td.components-minute-down", + 50, + -1 * 60 * 1000 + ); + }); + + it("scrolling up on the minute adds 1m to endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel(tree, "endsAt", "td.components-minute", -10, 60 * 1000); + }); + + it("scrolling down on the minute subtracts 1m from endsAt", () => { + const tree = MountedTabContentEnd(); + ValidateTimeWheel( + tree, + "endsAt", + "td.components-minute", + 15, + -1 * 60 * 1000 + ); + }); }); const ValidateDurationButton = (elemIndex, iconMatch, expectedDiff) => { @@ -233,30 +417,74 @@ const ValidateDurationButton = (elemIndex, iconMatch, expectedDiff) => { expect(diffMS).toBe(expectedDiff); }; +const ValidateDurationWheel = (elemIndex, deltaY, expectedDiff) => { + const tree = mount( + + ); + const elem = tree.find(".components-duration").at(elemIndex); + + const oldEndsAt = moment(silenceFormStore.data.endsAt); + + elem.simulate("wheel", { deltaY: deltaY }); + // fire real event so cancel listener will trigger + const event = new Event("wheel", { deltaY: deltaY }); + elem.getDOMNode().dispatchEvent(event); + + expect(silenceFormStore.data.endsAt.toISOString()).not.toBe( + oldEndsAt.toISOString() + ); + const diffMS = silenceFormStore.data.endsAt.diff(oldEndsAt); + expect(diffMS).toBe(expectedDiff); +}; + describe("", () => { it("clicking on the day inc button adds 1d to endsAt", () => { ValidateDurationButton(0, /angle-up/, 24 * 3600 * 1000); }); + it("scrolling up on the day button adds 1d to endsAt", () => { + ValidateDurationWheel(0, -1, 24 * 3600 * 1000); + }); + it("clicking on the day dec button subtracts 1d from endsAt", () => { ValidateDurationButton(2, /angle-down/, -1 * 24 * 3600 * 1000); }); + it("scrolling down on the day button subtracts 1d from endsAt", () => { + ValidateDurationWheel(0, 1, -1 * 24 * 3600 * 1000); + }); + it("clicking on the hour inc button adds 1h to endsAt", () => { ValidateDurationButton(3, /angle-up/, 3600 * 1000); }); + it("scrolling up on the hour inc button adds 1h to endsAt", () => { + ValidateDurationWheel(1, -2, 3600 * 1000); + }); + it("clicking on the hour dec button subtracts 1h from endsAt", () => { ValidateDurationButton(5, /angle-down/, -1 * 3600 * 1000); }); + it("scrolling down on the hour dec button subtracts 1h from endsAt", () => { + ValidateDurationWheel(1, 2, -1 * 3600 * 1000); + }); + it("clicking on the minute inc button adds 5m to endsAt", () => { ValidateDurationButton(6, /angle-up/, 5 * 60 * 1000); }); + it("scrolling up on the minute inc button adds 5m to endsAt", () => { + ValidateDurationWheel(2, -1, 5 * 60 * 1000); + }); + it("clicking on the minute dec button subtracts 5m from endsAt", () => { ValidateDurationButton(8, /angle-down/, -1 * 5 * 60 * 1000); }); + + it("scrolling down on the minute dec button subtracts 5m from endsAt", () => { + ValidateDurationWheel(2, 1, -1 * 5 * 60 * 1000); + }); }); const SetDurationTo = (hours, minutes) => {