Merge pull request #1554 from prymitive/duration-wheel

feat(ui): update silence form duration on mouse wheel
This commit is contained in:
Łukasz Mierzwa
2020-03-27 12:27:23 +00:00
committed by GitHub
4 changed files with 407 additions and 120 deletions

View File

@@ -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 (
<div>
<table className="w-100">
<tbody>
<tr>
<td className="w-50 text-center">
<span onClick={onInc}>
<FontAwesomeIcon
icon={faAngleUp}
size="2x"
className="text-muted cursor-pointer"
/>
</span>
</td>
<td className="w-50" />
</tr>
<tr>
<td className="w-50 text-center">
<h2>{value}</h2>
</td>
<td className="w-50">
<span className="text-muted ml-2">{label}</span>
</td>
</tr>
<tr>
<td className="w-50 text-center">
<span onClick={onDec}>
<FontAwesomeIcon
icon={faAngleDown}
size="2x"
className="text-muted cursor-pointer"
/>
</span>
</td>
<td className="w-50" />
</tr>
</tbody>
</table>
</div>
);
const onWheel = (event) => {
if (event.deltaY < 0) {
onInc();
} else {
onDec();
}
}
);
};
return (
<div ref={rootRef} onWheel={onWheel} className="components-duration">
<table className="w-100">
<tbody>
<tr>
<td className="w-50 text-center">
<span onClick={onInc}>
<FontAwesomeIcon
icon={faAngleUp}
size="2x"
className="text-muted cursor-pointer"
/>
</span>
</td>
<td className="w-50" />
</tr>
<tr>
<td className="w-50 text-center">
<h2>{value}</h2>
</td>
<td className="w-50">
<span className="text-muted ml-2">{label}</span>
</td>
</tr>
<tr>
<td className="w-50 text-center">
<span onClick={onDec}>
<FontAwesomeIcon
icon={faAngleDown}
size="2x"
className="text-muted cursor-pointer"
/>
</span>
</td>
<td className="w-50" />
</tr>
</tbody>
</table>
</div>
);
});
Duration.propTypes = {
value: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
onInc: PropTypes.func.isRequired,
onDec: PropTypes.func.isRequired,
};
export { Duration };

View File

@@ -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 }) => (
<td>
const IconTd = ({ icon, onClick, onWheel, className }) => (
<td className={className} onWheel={onWheel}>
<span onClick={onClick}>
<FontAwesomeIcon
icon={icon}
@@ -23,61 +23,105 @@ const IconTd = ({ icon, onClick }) => (
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 (
<div className="d-flex justify-content-center align-items-center">
<table className="text-center border-0">
<tbody>
<tr>
<IconTd icon={faAngleUp} onClick={onHourInc} />
<td />
<IconTd icon={faAngleUp} onClick={onMinuteInc} />
</tr>
<tr>
<td>
<h2>{hour > 9 ? hour : `0${hour}`}</h2>
</td>
<td>
<h2 className="mx-2">:</h2>
</td>
<td>
<h2>{minute > 9 ? minute : `0${minute}`}</h2>
</td>
</tr>
<tr>
<IconTd icon={faAngleDown} onClick={onHourDec} />
<td />
<IconTd icon={faAngleDown} onClick={onMinuteDec} />
</tr>
</tbody>
</table>
</div>
);
}
return (
<div
ref={rootRef}
className="d-flex justify-content-center align-items-center components-hour-minute"
>
<table className="text-center border-0">
<tbody>
<tr>
<IconTd
icon={faAngleUp}
onClick={onHourInc}
onWheel={onHourWheel}
className="components-hour-up"
/>
<td />
<IconTd
icon={faAngleUp}
onClick={onMinuteInc}
onWheel={onMinuteWheel}
className="components-minute-up"
/>
</tr>
<tr>
<td className="components-hour" onWheel={onHourWheel}>
<h2>{hour > 9 ? hour : `0${hour}`}</h2>
</td>
<td>
<h2 className="mx-2">:</h2>
</td>
<td className="components-minute" onWheel={onMinuteWheel}>
<h2>{minute > 9 ? minute : `0${minute}`}</h2>
</td>
</tr>
<tr>
<IconTd
icon={faAngleDown}
onClick={onHourDec}
onWheel={onHourWheel}
className="components-hour-down"
/>
<td />
<IconTd
icon={faAngleDown}
onClick={onMinuteDec}
onWheel={onMinuteWheel}
className="components-minute-down"
/>
</tr>
</tbody>
</table>
</div>
);
}
);
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 };

View File

@@ -36,7 +36,7 @@ exports[`<DateTimeSelect /> 'Duration' tab matches snapshot 1`] = `
</ul>
<div class=\\"tab-content mb-3\\">
<div class=\\"d-flex flex-sm-row flex-column justify-content-around mt-2 mx-3\\">
<div>
<div class=\\"components-duration\\">
<table class=\\"w-100\\">
<tbody>
<tr>
@@ -98,7 +98,7 @@ exports[`<DateTimeSelect /> 'Duration' tab matches snapshot 1`] = `
</tbody>
</table>
</div>
<div>
<div class=\\"components-duration\\">
<table class=\\"w-100\\">
<tbody>
<tr>
@@ -160,7 +160,7 @@ exports[`<DateTimeSelect /> 'Duration' tab matches snapshot 1`] = `
</tbody>
</table>
</div>
<div>
<div class=\\"components-duration\\">
<table class=\\"w-100\\">
<tbody>
<tr>
@@ -613,11 +613,11 @@ exports[`<DateTimeSelect /> 'Ends' tab matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"d-flex justify-content-center align-items-center\\">
<div class=\\"d-flex justify-content-center align-items-center components-hour-minute\\">
<table class=\\"text-center border-0\\">
<tbody>
<tr>
<td>
<td class=\\"components-hour-up\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -637,7 +637,7 @@ exports[`<DateTimeSelect /> 'Ends' tab matches snapshot 1`] = `
</td>
<td>
</td>
<td>
<td class=\\"components-minute-up\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -657,7 +657,7 @@ exports[`<DateTimeSelect /> 'Ends' tab matches snapshot 1`] = `
</td>
</tr>
<tr>
<td>
<td class=\\"components-hour\\">
<h2>
00
</h2>
@@ -667,14 +667,14 @@ exports[`<DateTimeSelect /> 'Ends' tab matches snapshot 1`] = `
:
</h2>
</td>
<td>
<td class=\\"components-minute\\">
<h2>
00
</h2>
</td>
</tr>
<tr>
<td>
<td class=\\"components-hour-down\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -694,7 +694,7 @@ exports[`<DateTimeSelect /> 'Ends' tab matches snapshot 1`] = `
</td>
<td>
</td>
<td>
<td class=\\"components-minute-down\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -1101,11 +1101,11 @@ exports[`<DateTimeSelect /> 'Starts' tab matches snapshot 1`] = `
</div>
</div>
</div>
<div class=\\"d-flex justify-content-center align-items-center\\">
<div class=\\"d-flex justify-content-center align-items-center components-hour-minute\\">
<table class=\\"text-center border-0\\">
<tbody>
<tr>
<td>
<td class=\\"components-hour-up\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -1125,7 +1125,7 @@ exports[`<DateTimeSelect /> 'Starts' tab matches snapshot 1`] = `
</td>
<td>
</td>
<td>
<td class=\\"components-minute-up\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -1145,7 +1145,7 @@ exports[`<DateTimeSelect /> 'Starts' tab matches snapshot 1`] = `
</td>
</tr>
<tr>
<td>
<td class=\\"components-hour\\">
<h2>
00
</h2>
@@ -1155,14 +1155,14 @@ exports[`<DateTimeSelect /> 'Starts' tab matches snapshot 1`] = `
:
</h2>
</td>
<td>
<td class=\\"components-minute\\">
<h2>
00
</h2>
</td>
</tr>
<tr>
<td>
<td class=\\"components-hour-down\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
@@ -1182,7 +1182,7 @@ exports[`<DateTimeSelect /> 'Starts' tab matches snapshot 1`] = `
</td>
<td>
</td>
<td>
<td class=\\"components-minute-down\\">
<span>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"

View File

@@ -59,6 +59,11 @@ describe("<DateTimeSelect />", () => {
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("<DateTimeSelect />", () => {
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("<DateTimeSelect />", () => {
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(<TabContentStart silenceFormStore={silenceFormStore} />);
};
@@ -164,20 +202,96 @@ describe("<TabContentStart />", () => {
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("<TabContentEnd />", () => {
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(
<TabContentDuration silenceFormStore={silenceFormStore} />
);
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("<TabContentDuration />", () => {
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) => {