Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions src/components/MenuBars/MenuBars.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import {animated, useSpring} from "@react-spring/web";
import classNames from "classnames";
import {BoardReactionMenu} from "components/BoardReactionMenu/BoardReactionMenu";
import {AddStickerReaction, ArrowLeft, ArrowRight, Close, GeneralSettings, MarkAsDone, Menu, PresenterMode, RaiseHand, Timer, Voting} from "components/Icon";
import {TooltipButton} from "components/TooltipButton/TooltipButton";
import {hotkeyMap} from "constants/hotkeys";
import {useEffect, useLayoutEffect, useRef, useState} from "react";
import {useHotkeys} from "react-hotkeys-hook";
import {useTranslation} from "react-i18next";
import {useDispatch} from "react-redux";
import {useLocation, useNavigate} from "react-router";
import {Actions} from "store/action";
import {useAppSelector} from "store";
import {Actions} from "store/action";
import _ from "underscore";
import classNames from "classnames";
import {Voting, Timer, RaiseHand, MarkAsDone, AddStickerReaction, GeneralSettings, PresenterMode, Close, ArrowRight, ArrowLeft, Menu} from "components/Icon";
import {hotkeyMap} from "constants/hotkeys";
import {TooltipButton} from "components/TooltipButton/TooltipButton";
import {BoardReactionMenu} from "components/BoardReactionMenu/BoardReactionMenu";
import {useTimer} from "../../utils/hooks/useTimerLeft";
import "./MenuBars.scss";

export interface MenuBarsProps {
Expand Down Expand Up @@ -72,7 +73,6 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
}, [location]);

const {TOGGLE_TIMER_MENU, TOGGLE_VOTING_MENU, TOGGLE_SETTINGS, TOGGLE_RAISED_HAND, TOGGLE_BOARD_REACTION_MENU, TOGGLE_READY_STATE, TOGGLE_MODERATION} = hotkeyMap;

// State & Functions
const state = useAppSelector(
(rootState) => ({
Expand All @@ -81,6 +81,9 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
hotkeysAreActive: rootState.view.hotkeysAreActive,
activeTimer: !!rootState.board.data?.timerEnd,
activeVoting: !!rootState.votings.open,
usedVotes: rootState.votes.filter((v) => v.voting === rootState.votings.open?.id).length,
possibleVotes: rootState.votings.open?.voteLimit,
timerEnd: rootState.board.data?.timerEnd,
}),
_.isEqual
);
Expand Down Expand Up @@ -188,6 +191,35 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
useHotkeys(TOGGLE_TIMER_MENU, toggleTimerMenu, hotkeyOptionsAdmin, []);
useHotkeys(TOGGLE_VOTING_MENU, toggleVotingMenu, hotkeyOptionsAdmin, []);

/**
* Logic for "Mark me as Done" tooltip.
* https://github.com/inovex/scrumlr.io/issues/4269
*/
const timerExpired = useTimer(state.timerEnd);
const [isReadyTooltipClass, setIsReadyTooltipClass] = useState("");
/**
* Logic for when a) a timer initially expired b) available votes are used up
* and the "Mark me as Done" tooltip is not open.
*/
const USED_VOTES = state.usedVotes === state.possibleVotes;
const USER_NOT_READY = !state.currentUser.ready;

useEffect(() => {
let timer: NodeJS.Timeout;
const handleTimeout = () => {
setIsReadyTooltipClass("tooltip-button--content-extended");
setTimeout(() => setIsReadyTooltipClass(""), 28000);
};
if ((timerExpired || USED_VOTES) && USER_NOT_READY && (state.activeTimer || state.activeVoting)) {
timer = setTimeout(handleTimeout, 2000);
}
if (!USED_VOTES || !state.activeTimer || !USER_NOT_READY || !state.activeVoting) {
setIsReadyTooltipClass("");
}

return () => clearTimeout(timer);
}, [timerExpired, USER_NOT_READY, state.timerEnd, USED_VOTES, state.activeTimer, state.activeVoting]);

return (
<>
{/* desktop view */}
Expand All @@ -202,6 +234,7 @@ export const MenuBars = ({showPreviousColumn, showNextColumn, onPreviousColumn,
onClick={toggleReadyState}
label={isReady ? t("MenuBars.unmarkAsDone") : t("MenuBars.markAsDone")}
icon={MarkAsDone}
className={isReadyTooltipClass}
active={isReady}
hotkeyKey={TOGGLE_READY_STATE.toUpperCase()}
/>
Expand Down
58 changes: 56 additions & 2 deletions src/components/MenuBars/__tests__/MenuBars.test.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import {render} from "testUtils";
import {MenuBars} from "components/MenuBars";
import {Provider} from "react-redux";
import getTestStore from "utils/test/getTestStore";
import {MockStoreEnhanced} from "redux-mock-store";
import {render} from "testUtils";
import getTestStore from "utils/test/getTestStore";
import {useTimer} from "../../../utils/hooks/useTimerLeft";
import getTestParticipant from "../../../utils/test/getTestParticipant";

jest.mock("../../../utils/hooks/useTimerLeft");

const createMenuBars = (store: MockStoreEnhanced) => (
<Provider store={store}>
<MenuBars showNextColumn showPreviousColumn onNextColumn={() => {}} onPreviousColumn={() => {}} />
</Provider>
);

describe("MenuBars", () => {
beforeEach(() => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});
});

test("should match snapshot", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});

const store = getTestStore({
participants: {
self: getTestParticipant({role: "MODERATOR"}),
Expand All @@ -24,6 +33,8 @@ describe("MenuBars", () => {
});

test("should render both user- and admin-menu for moderators", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});

const store = getTestStore({
participants: {
self: getTestParticipant({role: "MODERATOR"}),
Expand All @@ -36,6 +47,8 @@ describe("MenuBars", () => {
});

test("should only render user-menu for participants", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});

const store = getTestStore({
participants: {
self: getTestParticipant({role: "PARTICIPANT"}),
Expand All @@ -47,3 +60,44 @@ describe("MenuBars", () => {
expect(container.getElementsByClassName("user-menu").length).toBe(1);
});
});

describe("Mark me as Done Tooltip Logic", () => {
beforeEach(() => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});
});

it("Does not expand the tooltip if the timer is not expired", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});
const store = getTestStore({
participants: {
self: getTestParticipant(),
others: [],
},
});
const {asFragment} = render(createMenuBars(store));
expect(asFragment()).toMatchSnapshot();
});

it("Does not expand the tooltip if the possibleVotes !== usedVotes", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});
const store = getTestStore({
participants: {
self: getTestParticipant(),
others: [],
},
});
const {asFragment} = render(createMenuBars(store));
expect(asFragment()).toMatchSnapshot();
});
it("Expand the tooltip everytime a potential ready state in voting is fulfilled.", () => {
(useTimer as jest.Mock).mockReturnValue({timerExpired: false});
const store = getTestStore({
participants: {
self: getTestParticipant(),
others: [],
},
});
const {asFragment} = render(createMenuBars(store));
expect(asFragment()).toMatchSnapshot();
});
});
Loading
Loading