From 4feee3f530fd0f8a0d33d382157725f98259ab8b Mon Sep 17 00:00:00 2001 From: Josephine Lee Date: Tue, 17 May 2022 11:00:40 -0400 Subject: [PATCH] ui: Add tests for the TimeScaleDropdown component This commit splits out tests in `timescale.spec.tsx` and adds additional tests for the TimeScaleDropdown component, including testing that the custom time picker is initialized to the currently selected time. This also adds TimeScaleDropdown stories. Release note: None --- pkg/ui/workspaces/cluster-ui/BUILD.bazel | 6 + .../cluster-ui/src/dateRange/dateRange.tsx | 5 +- .../src/timeScaleDropdown/rangeSelect.tsx | 10 +- .../timeScaleDropdown/timeFrameControls.tsx | 2 + .../timeScaleDropdown.spec.tsx | 362 ++++++++++++++++++ .../timeScaleDropdown.stories.tsx | 37 ++ .../timeScaleDropdown/timeScaleDropdown.tsx | 28 +- .../src/timeScaleDropdown/timescale.spec.tsx | 305 --------------- .../src/timeScaleDropdown/utils.spec.tsx | 109 ++++++ 9 files changed, 539 insertions(+), 325 deletions(-) create mode 100644 pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.stories.tsx delete mode 100644 pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timescale.spec.tsx create mode 100644 pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx diff --git a/pkg/ui/workspaces/cluster-ui/BUILD.bazel b/pkg/ui/workspaces/cluster-ui/BUILD.bazel index 773a235b14e4..93230f70c465 100644 --- a/pkg/ui/workspaces/cluster-ui/BUILD.bazel +++ b/pkg/ui/workspaces/cluster-ui/BUILD.bazel @@ -200,6 +200,8 @@ nodejs_test( ".eslintrc.json", "src", "@npm//@cockroachlabs/eslint-config", + "@npm//@testing-library/react", + "@npm//@testing-library/user-event", "@npm//@typescript-eslint/eslint-plugin", "@npm//@typescript-eslint/parser", "@npm//eslint", @@ -231,6 +233,8 @@ tsc_test( ]) + [ "tsconfig.json", "tsconfig.linting.json", + "@npm//@testing-library/react", + "@npm//@testing-library/user-event", "//pkg/ui/workspaces/db-console/src/js:crdb-protobuf-client", "//pkg/ui/workspaces/db-console/ccl/src/js:crdb-protobuf-client-ccl", ], @@ -259,6 +263,8 @@ JEST_DEPS = DEPENDENCIES + [ "jest.config.js", "tsconfig.json", "src", + "@npm//@testing-library/react", + "@npm//@testing-library/user-event", "//pkg/ui/workspaces/db-console/src/js:crdb-protobuf-client", "//pkg/ui/workspaces/db-console/ccl/src/js:crdb-protobuf-client-ccl", ] diff --git a/pkg/ui/workspaces/cluster-ui/src/dateRange/dateRange.tsx b/pkg/ui/workspaces/cluster-ui/src/dateRange/dateRange.tsx index 157bbded2c7d..d885e4472ae4 100644 --- a/pkg/ui/workspaces/cluster-ui/src/dateRange/dateRange.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/dateRange/dateRange.tsx @@ -38,6 +38,9 @@ type DateRangeMenuProps = { onCancel: () => void; }; +export const dateFormat = "MMMM D, YYYY"; +export const timeFormat = "H:mm"; + export function DateRangeMenu({ startInit, endInit, @@ -45,8 +48,6 @@ export function DateRangeMenu({ onSubmit, onCancel, }: DateRangeMenuProps): React.ReactElement { - const dateFormat = "MMMM D, YYYY"; - const timeFormat = "H:mm"; /** * Local startMoment and endMoment state are stored here so that users can change the time before clicking "Apply". * They are re-initialized to startInit and endInit by re-mounting this component. It is thus the responsibility of diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx index c90708d2a884..5a0eefd921ec 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/rangeSelect.tsx @@ -31,8 +31,8 @@ export type Selected = { dateEnd?: string; timeStart?: string; timeEnd?: string; - title?: string; - timeLabel?: string; + key: "Custom" | string; + timeLabel: string; timeWindow: TimeWindow; }; @@ -149,7 +149,7 @@ const RangeSelect = ({ {options.map(option => ( @@ -176,8 +176,8 @@ const RangeSelect = ({
{selected.timeLabel} - {selected.title !== "Custom" ? ( - selected.title + {selected.key !== "Custom" ? ( + selected.key ) : ( <> {selected.dateStart}{" "} diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeFrameControls.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeFrameControls.tsx index 1afbe2adf240..19c1c4b858b5 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeFrameControls.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeFrameControls.tsx @@ -53,6 +53,7 @@ export const TimeFrameControls = ({ onClick={handleChangeArrow(ArrowDirection.LEFT)} disabled={left} className={cx("_action", left ? "disabled" : "active")} + aria-label={"previous timeframe"} > @@ -67,6 +68,7 @@ export const TimeFrameControls = ({ onClick={handleChangeArrow(ArrowDirection.RIGHT)} disabled={right} className={cx("_action", right ? "disabled" : "active")} + aria-label={"next timeframe"} > diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx new file mode 100644 index 000000000000..ffb3143e2938 --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.spec.tsx @@ -0,0 +1,362 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { useState } from "react"; +import { mount } from "enzyme"; +import { + formatRangeSelectSelected, + generateDisabledArrows, + timeFormat as dropdownTimeFormat, + dateFormat as dropdownDateFormat, + TimeScaleDropdownProps, + TimeScaleDropdown, +} from "./timeScaleDropdown"; +import * as timescale from "./timeScaleTypes"; +import moment from "moment"; +import { MemoryRouter } from "react-router"; +import TimeFrameControls from "./timeFrameControls"; +import RangeSelect from "./rangeSelect"; +import { timeFormat as customMenuTimeFormat } from "../dateRange"; +import { assert } from "chai"; +import sinon from "sinon"; +import { TimeWindow, ArrowDirection, TimeScale } from "./timeScaleTypes"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +/** + * This wrapper holds the time scale state to allow tests to render a stateful, functional component, + * while providing onSetTimeScale to listen for this. + */ +function TimeScaleDropdownWrapper({ + currentScale, + onSetTimeScale, + ...props +}: Omit & { + onSetTimeScale?: (tw: TimeScale) => void; +}): React.ReactElement { + const [innerTimeScale, setInnerTimeScale] = useState(currentScale); + const setTimeScaleWrapper = (tw: timescale.TimeScale) => { + setInnerTimeScale(tw); + onSetTimeScale(tw); + }; + return ( + + ); +} + +describe(" component", function() { + let clock: sinon.SinonFakeTimers; + + // Returns a new moment every time, so that we don't accidentally mutate it + const getNow = () => { + return moment.utc({ + year: 2020, + month: 5, + day: 1, + hour: 9, + minute: 28, + second: 30, + }); + }; + + const getExpectedCustomText = ( + startMoment: moment.Moment, + endMoment: moment.Moment, + timeLabel?: string, + ) => { + const expectedText = [ + startMoment.format(dropdownTimeFormat), + endMoment.format(dropdownTimeFormat), + "(UTC)", + ]; + if (timeLabel) { + expectedText.push(timeLabel); + } + return expectedText; + }; + + beforeEach(() => { + clock = sinon.useFakeTimers(getNow().toDate()); + }); + + afterEach(() => { + clock.restore(); + }); + + it("updates as different preset options are selected", () => { + const mockSetTimeScale = jest.fn(); + const { getByText, queryByText } = render( + + + , + ); + + // Default state + getByText("Past 10 Minutes"); + getByText("10m"); + expect(queryByText("Past 6 Hours")).toBeNull(); + + // Select a different preset option + userEvent.click(getByText("Past 10 Minutes")); + userEvent.click(getByText("Past 6 Hours")); + + expect(mockSetTimeScale).toHaveBeenCalledTimes(1); + expect(queryByText("Past 10 Minutes")).toBeNull(); + getByText("Past 6 Hours"); + getByText("6h"); + }); + + it("is controlled by next and previous arrow buttons", () => { + const mockSetTimeScale = jest.fn(); + // Default state + const { getByText, getByRole } = render( + + + , + ); + getByText("Past 10 Minutes"); + + // Click left, and it shows a custom time + userEvent.click( + getByRole("button", { + name: "previous timeframe", + }), + ); + expect(mockSetTimeScale).toHaveBeenCalledTimes(1); + for (const expectedText of getExpectedCustomText( + getNow().subtract(moment.duration(10, "m")), + getNow().subtract(moment.duration(10 * 2, "m")), + "10m", + )) { + getByText(expectedText); + } + + // Click right, and it reverts to "Past 10 minutes" + userEvent.click( + getByRole("button", { + name: "next timeframe", + }), + ); + expect(mockSetTimeScale).toHaveBeenCalledTimes(2); + getByText("Past 10 Minutes"); + }); + + it.only("allows selection of a custom time frame", () => { + const mockSetTimeScale = jest.fn(); + // Default state + const { getByText, getByDisplayValue, container } = render( + + + , + ); + // Switch to a bigger time frame + userEvent.click(getByText("Past 10 Minutes")); + userEvent.click(getByText("Past 6 Hours")); + expect(mockSetTimeScale).toHaveBeenCalledTimes(1); + + // Open the custom menu + userEvent.click(getByText("Past 6 Hours")); + userEvent.click(getByText("Custom date range")); + expect(mockSetTimeScale).toHaveBeenCalledTimes(1); + + // Custom menu should be initialized to currently selected time, i.e. now-6h to now + // Start: 3:28 AM (UTC) + // End: 9:28 AM (UTC) + const startText = getNow() + .subtract(6, "h") + .format(customMenuTimeFormat); + const endMoment = getNow(); + getByDisplayValue(startText); // start + getByDisplayValue(endMoment.format(customMenuTimeFormat)); // end + }); +}); + +const initialEntries = [ + "#/metrics/overview/cluster", // Past 10 minutes + `#/metrics/overview/cluster/cluster?start=${moment() + .subtract(1, "hour") + .format("X")}&end=${moment().format("X")}`, // Past 1 hour + `#/metrics/overview/cluster/cluster?start=${moment() + .subtract(6, "hours") + .format("X")}&end=${moment().format("X")}`, // Past 6 hours + "#/metrics/overview/cluster/cluster?start=1584528492&end=1584529092", // 10 minutes + "#/metrics/overview/cluster?start=1583319565&end=1584529165", // 2 weeks + "#/metrics/overview/node/1", // Node 1 - Past 10 minutes + `#/metrics/overview/node/2?start=${moment() + .subtract(10, "minutes") + .format("X")}&end=${moment().format("X")}`, // Node 2 - Past 10 minutes + "#/metrics/overview/node/3?start=1584528726&end=1584529326", // Node 3 - 10 minutes +]; + +describe("TimeScaleDropdown functions", function() { + let state: Omit; + let clock: sinon.SinonFakeTimers; + let currentWindow: TimeWindow; + + const setCurrentWindowFromTimeScale = (timeScale: TimeScale): void => { + const end = timeScale.fixedWindowEnd || moment.utc(); + currentWindow = { + start: moment(end).subtract(timeScale.windowSize), + end, + }; + }; + + const makeTimeScaleDropdown = ( + props: Omit, + ) => { + setCurrentWindowFromTimeScale(props.currentScale); + return mount( + + + , + ); + }; + + beforeEach(() => { + clock = sinon.useFakeTimers(new Date(2020, 5, 1, 9, 28, 30)); + const timeScaleState = new timescale.TimeScaleState(); + setCurrentWindowFromTimeScale(timeScaleState.scale); + state = { + currentScale: timeScaleState.scale, + // setTimeScale: () => {}, + }; + }); + + afterEach(() => { + clock.restore(); + }); + + it("valid path should not redirect to 404", () => { + const wrapper = makeTimeScaleDropdown(state); + assert.equal(wrapper.find(RangeSelect).length, 1); + assert.equal(wrapper.find(TimeFrameControls).length, 1); + }); + + describe("formatRangeSelectSelected", () => { + it("formatRangeSelectSelected must return title Past 10 Minutes", () => { + const _ = makeTimeScaleDropdown(state); + + const title = formatRangeSelectSelected( + currentWindow, + state.currentScale, + ); + assert.deepEqual(title, { + key: "Past 10 Minutes", + timeLabel: "10m", + timeWindow: currentWindow, + }); + }); + + it("returns custom Title with Time part only for current day", () => { + const currentScale = { ...state.currentScale, key: "Custom" }; + const title = formatRangeSelectSelected(currentWindow, currentScale); + const timeStart = moment + .utc(currentWindow.start) + .format(dropdownTimeFormat); + const timeEnd = moment.utc(currentWindow.end).format(dropdownTimeFormat); + const wrapper = makeTimeScaleDropdown({ ...state, currentScale }); + assert.equal( + wrapper + .find(".trigger .Select-value-label") + .first() + .text(), + ` ${timeStart} - ${timeEnd} (UTC)`, + ); + assert.deepEqual(title, { + dateStart: "", + dateEnd: "", + timeStart, + timeEnd, + key: "Custom", + timeLabel: "10m", + timeWindow: currentWindow, + }); + }); + + it("returns custom Title with Date and Time part for the range with past days", () => { + const window: TimeWindow = { + start: moment(currentWindow.start).subtract(2, "day"), + end: moment(currentWindow.end).subtract(1, "day"), + }; + const currentScale = { + ...state.currentScale, + fixedWindowEnd: window.end, + windowSize: moment.duration( + window.end.diff(window.start, "seconds"), + "seconds", + ), + key: "Custom", + }; + const title = formatRangeSelectSelected(window, currentScale); + const timeStart = moment.utc(window.start).format(dropdownTimeFormat); + const timeEnd = moment.utc(window.end).format(dropdownTimeFormat); + const dateStart = moment.utc(window.start).format(dropdownDateFormat); + const dateEnd = moment.utc(window.end).format(dropdownDateFormat); + const wrapper = makeTimeScaleDropdown({ + ...state, + currentScale, + }); + assert.equal( + wrapper + .find(".trigger .Select-value-label") + .first() + .text(), + `${dateStart} ${timeStart} - ${dateEnd} ${timeEnd} (UTC)`, + ); + assert.deepEqual(title, { + dateStart, + dateEnd, + timeStart, + timeEnd, + key: "Custom", + timeLabel: "1d", + timeWindow: currentWindow, + }); + }); + }); + + it("generateDisabledArrows must return array with disabled buttons", () => { + const arrows = generateDisabledArrows(currentWindow); + const wrapper = makeTimeScaleDropdown(state); + assert.equal(wrapper.find(".controls-content ._action.disabled").length, 2); + assert.deepEqual(arrows, [ArrowDirection.CENTER, ArrowDirection.RIGHT]); + }); + + it("generateDisabledArrows must render 3 active buttons and return empty array", () => { + const window: TimeWindow = { + start: moment(currentWindow.start).subtract(1, "day"), + end: moment(currentWindow.end).subtract(1, "day"), + }; + const currentTimeScale = { + ...state.currentScale, + fixedWindowEnd: window.end, + }; + const arrows = generateDisabledArrows(window); + const wrapper = makeTimeScaleDropdown({ + ...state, + currentScale: currentTimeScale, + }); + assert.equal(wrapper.find(".controls-content ._action.disabled").length, 0); + assert.deepEqual(arrows, []); + }); +}); diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.stories.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.stories.tsx new file mode 100644 index 000000000000..2deb7f19844d --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.stories.tsx @@ -0,0 +1,37 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import React, { useState } from "react"; +import { storiesOf } from "@storybook/react"; +import { TimeScaleDropdown } from "./timeScaleDropdown"; +import { defaultTimeScaleOptions, defaultTimeScaleSelected } from "./utils"; +import moment from "moment"; + +export function TimeScaleDropdownWrapper({ + initialTimeScale = defaultTimeScaleSelected, +}): React.ReactElement { + const [timeScale, setTimeScale] = useState(initialTimeScale); + return ( + + ); +} + +storiesOf("TimeScaleDropdown", module) + .add("default", () => ) + .add("custom", () => ( + + )); diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx index 3b39f7cde9b3..63583d7bcd95 100644 --- a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/timeScaleDropdown.tsx @@ -12,14 +12,16 @@ import React, { useMemo } from "react"; import moment from "moment"; import classNames from "classnames/bind"; import { - TimeRangeTitle, TimeScale, TimeWindow, ArrowDirection, TimeScaleOptions, } from "./timeScaleTypes"; import TimeFrameControls from "./timeFrameControls"; -import RangeSelect, { RangeOption } from "./rangeSelect"; +import RangeSelect, { + RangeOption, + Selected as RangeSelectSelected, +} from "./rangeSelect"; import { defaultTimeScaleOptions, findClosestTimeScale } from "./utils"; import styles from "./timeScale.module.scss"; @@ -67,10 +69,16 @@ export const getTimeLabel = ( } }; -export const getTimeRangeTitle = ( +export const formatRangeSelectSelected = ( currentWindow: TimeWindow, currentScale: TimeScale, -): TimeRangeTitle => { +): RangeSelectSelected => { + const selected = { + timeLabel: getTimeLabel(currentWindow), + timeWindow: currentWindow, + key: currentScale.key, + }; + if (currentScale.key === "Custom") { const start = currentWindow.start.utc(); const end = currentWindow.end.utc(); @@ -79,20 +87,14 @@ export const getTimeRangeTitle = ( const omitDayFormat = endDayIsToday && startEndOnSameDay; return { + ...selected, dateStart: omitDayFormat ? "" : start.format(dateFormat), dateEnd: omitDayFormat || startEndOnSameDay ? "" : end.format(dateFormat), timeStart: moment.utc(start).format(timeFormat), timeEnd: moment.utc(end).format(timeFormat), - title: "Custom", - timeLabel: getTimeLabel(currentWindow), - timeWindow: currentWindow, }; } else { - return { - title: currentScale.key, - timeLabel: getTimeLabel(currentWindow), - timeWindow: currentWindow, - }; + return selected; } }; @@ -245,9 +247,9 @@ export const TimeScaleDropdown: React.FC = ({ return (
", function() { - let state: TimeScaleDropdownProps; - let clock: sinon.SinonFakeTimers; - let currentWindow: TimeWindow; - - const setCurrentWindowFromTimeScale = (timeScale: TimeScale): void => { - const end = timeScale.fixedWindowEnd || moment.utc(); - currentWindow = { - start: moment(end).subtract(timeScale.windowSize), - end, - }; - }; - - const makeTimeScaleDropdown = (props: TimeScaleDropdownProps) => { - setCurrentWindowFromTimeScale(props.currentScale); - return mount( - - - , - ); - }; - - beforeEach(() => { - clock = sinon.useFakeTimers(new Date(2020, 5, 1, 9, 28, 30)); - const timeScaleState = new timescale.TimeScaleState(); - setCurrentWindowFromTimeScale(timeScaleState.scale); - state = { - currentScale: timeScaleState.scale, - setTimeScale: () => {}, - }; - }); - - afterEach(() => { - clock.restore(); - }); - - it("valid path should not redirect to 404", () => { - const wrapper = makeTimeScaleDropdown(state); - assert.equal(wrapper.find(RangeSelect).length, 1); - assert.equal(wrapper.find(TimeFrameControls).length, 1); - }); - - it("Past 10 minutes must be render", () => { - const wrapper = makeTimeScaleDropdown(state); - wrapper.setProps({ currentScale: state.currentScale }); - const expected: TimeScale = { - key: "Past 10 Minutes", - ...defaultTimeScaleOptions["Past 10 Minutes"], - fixedWindowEnd: false, - }; - assert.deepEqual(wrapper.props().currentScale, expected); - }); - - it("getTimeRangeTitle must return title Past 10 Minutes", () => { - const wrapper = makeTimeScaleDropdown(state); - assert.equal( - wrapper - .find(".trigger .Select-value-label") - .first() - .text(), - "Past 10 Minutes", - ); - - const title = getTimeRangeTitle(currentWindow, state.currentScale); - assert.deepEqual(title, { - title: "Past 10 Minutes", - timeLabel: "10m", - timeWindow: currentWindow, - }); - }); - - describe("getTimeRangeTitle", () => { - it("returns custom Title with Time part only for current day", () => { - const currentScale = { ...state.currentScale, key: "Custom" }; - const title = getTimeRangeTitle(currentWindow, currentScale); - const timeStart = moment.utc(currentWindow.start).format(timeFormat); - const timeEnd = moment.utc(currentWindow.end).format(timeFormat); - const wrapper = makeTimeScaleDropdown({ ...state, currentScale }); - assert.equal( - wrapper - .find(".trigger .Select-value-label") - .first() - .text(), - ` ${timeStart} - ${timeEnd} (UTC)`, - ); - assert.deepEqual(title, { - dateStart: "", - dateEnd: "", - timeStart, - timeEnd, - title: "Custom", - timeLabel: "10m", - timeWindow: currentWindow, - }); - }); - - it("returns custom Title with Date and Time part for the range with past days", () => { - const window: TimeWindow = { - start: moment(currentWindow.start).subtract(2, "day"), - end: moment(currentWindow.end).subtract(1, "day"), - }; - const currentScale = { - ...state.currentScale, - fixedWindowEnd: window.end, - windowSize: moment.duration( - window.end.diff(window.start, "seconds"), - "seconds", - ), - key: "Custom", - }; - const title = getTimeRangeTitle(window, currentScale); - const timeStart = moment.utc(window.start).format(timeFormat); - const timeEnd = moment.utc(window.end).format(timeFormat); - const dateStart = moment.utc(window.start).format(dateFormat); - const dateEnd = moment.utc(window.end).format(dateFormat); - const wrapper = makeTimeScaleDropdown({ - ...state, - currentScale, - }); - assert.equal( - wrapper - .find(".trigger .Select-value-label") - .first() - .text(), - `${dateStart} ${timeStart} - ${dateEnd} ${timeEnd} (UTC)`, - ); - assert.deepEqual(title, { - dateStart, - dateEnd, - timeStart, - timeEnd, - title: "Custom", - timeLabel: "1d", - timeWindow: currentWindow, - }); - }); - }); - - it("generateDisabledArrows must return array with disabled buttons", () => { - const arrows = generateDisabledArrows(currentWindow); - const wrapper = makeTimeScaleDropdown(state); - assert.equal(wrapper.find(".controls-content ._action.disabled").length, 2); - assert.deepEqual(arrows, [ArrowDirection.CENTER, ArrowDirection.RIGHT]); - }); - - it("generateDisabledArrows must render 3 active buttons and return empty array", () => { - const window: TimeWindow = { - start: moment(currentWindow.start).subtract(1, "day"), - end: moment(currentWindow.end).subtract(1, "day"), - }; - const currentTimeScale = { - ...state.currentScale, - fixedWindowEnd: window.end, - }; - const arrows = generateDisabledArrows(window); - const wrapper = makeTimeScaleDropdown({ - ...state, - currentScale: currentTimeScale, - }); - assert.equal(wrapper.find(".controls-content ._action.disabled").length, 0); - assert.deepEqual(arrows, []); - }); -}); - -describe("timescale utils", (): void => { - describe("toRoundedDateRange", () => { - it("round values", () => { - const ts: TimeScale = { - windowSize: moment.duration(5, "day"), - sampleSize: moment.duration(5, "minutes"), - fixedWindowEnd: moment.utc("2022.01.10 13:42"), - key: "Custom", - }; - const [start, end] = toRoundedDateRange(ts); - assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); - assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); - }); - - it("already rounded values", () => { - const ts: TimeScale = { - windowSize: moment.duration(5, "day"), - sampleSize: moment.duration(5, "minutes"), - fixedWindowEnd: moment.utc("2022.01.10 13:00"), - key: "Custom", - }; - const [start, end] = toRoundedDateRange(ts); - assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); - assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); - }); - }); - - describe("findClosestTimeScale", () => { - it("should find the correct time scale", () => { - // `seconds` != window size of any of the default options, `startSeconds` not specified. - assert.deepEqual(findClosestTimeScale(defaultTimeScaleOptions, 15), { - ...defaultTimeScaleOptions["Past 10 Minutes"], - key: "Custom", - }); - // `seconds` != window size of any of the default options, `startSeconds` not specified. - assert.deepEqual( - findClosestTimeScale( - defaultTimeScaleOptions, - moment.duration(moment().daysInMonth() * 5, "days").asSeconds(), - ), - { ...defaultTimeScaleOptions["Past 2 Months"], key: "Custom" }, - ); - // `seconds` == window size of one of the default options, `startSeconds` not specified. - assert.deepEqual( - findClosestTimeScale( - defaultTimeScaleOptions, - moment.duration(10, "minutes").asSeconds(), - ), - { - ...defaultTimeScaleOptions["Past 10 Minutes"], - key: "Past 10 Minutes", - }, - ); - // `seconds` == window size of one of the default options, `startSeconds` not specified. - assert.deepEqual( - findClosestTimeScale( - defaultTimeScaleOptions, - moment.duration(14, "days").asSeconds(), - ), - { - ...defaultTimeScaleOptions["Past 2 Weeks"], - key: "Past 2 Weeks", - }, - ); - // `seconds` == window size of one of the default options, `startSeconds` is now. - assert.deepEqual( - findClosestTimeScale( - defaultTimeScaleOptions, - defaultTimeScaleOptions["Past 10 Minutes"].windowSize.asSeconds(), - moment().unix(), - ), - { - ...defaultTimeScaleOptions["Past 10 Minutes"], - key: "Past 10 Minutes", - }, - ); - // `seconds` == window size of one of the default options, `startSeconds` is in the past. - assert.deepEqual( - findClosestTimeScale( - defaultTimeScaleOptions, - defaultTimeScaleOptions["Past 10 Minutes"].windowSize.asSeconds(), - moment() - .subtract(1, "day") - .unix(), - ), - { ...defaultTimeScaleOptions["Past 10 Minutes"], key: "Custom" }, - ); - }); - }); -}); diff --git a/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx new file mode 100644 index 000000000000..160570c49def --- /dev/null +++ b/pkg/ui/workspaces/cluster-ui/src/timeScaleDropdown/utils.spec.tsx @@ -0,0 +1,109 @@ +// Copyright 2022 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +import { TimeScale } from "./timeScaleTypes"; +import moment from "moment"; +import { + defaultTimeScaleOptions, + findClosestTimeScale, + toRoundedDateRange, +} from "./utils"; +import { assert } from "chai"; + +describe("timescale utils", (): void => { + describe("toRoundedDateRange", () => { + it("round values", () => { + const ts: TimeScale = { + windowSize: moment.duration(5, "day"), + sampleSize: moment.duration(5, "minutes"), + fixedWindowEnd: moment.utc("2022.01.10 13:42"), + key: "Custom", + }; + const [start, end] = toRoundedDateRange(ts); + assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); + assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); + }); + + it("already rounded values", () => { + const ts: TimeScale = { + windowSize: moment.duration(5, "day"), + sampleSize: moment.duration(5, "minutes"), + fixedWindowEnd: moment.utc("2022.01.10 13:00"), + key: "Custom", + }; + const [start, end] = toRoundedDateRange(ts); + assert.equal(start.format("YYYY.MM.DD HH:mm:ss"), "2022.01.05 13:00:00"); + assert.equal(end.format("YYYY.MM.DD HH:mm:ss"), "2022.01.10 14:00:00"); + }); + }); + + describe("findClosestTimeScale", () => { + it("should find the correct time scale", () => { + // `seconds` != window size of any of the default options, `startSeconds` not specified. + assert.deepEqual(findClosestTimeScale(defaultTimeScaleOptions, 15), { + ...defaultTimeScaleOptions["Past 10 Minutes"], + key: "Custom", + }); + // `seconds` != window size of any of the default options, `startSeconds` not specified. + assert.deepEqual( + findClosestTimeScale( + defaultTimeScaleOptions, + moment.duration(moment().daysInMonth() * 5, "days").asSeconds(), + ), + { ...defaultTimeScaleOptions["Past 2 Months"], key: "Custom" }, + ); + // `seconds` == window size of one of the default options, `startSeconds` not specified. + assert.deepEqual( + findClosestTimeScale( + defaultTimeScaleOptions, + moment.duration(10, "minutes").asSeconds(), + ), + { + ...defaultTimeScaleOptions["Past 10 Minutes"], + key: "Past 10 Minutes", + }, + ); + // `seconds` == window size of one of the default options, `startSeconds` not specified. + assert.deepEqual( + findClosestTimeScale( + defaultTimeScaleOptions, + moment.duration(14, "days").asSeconds(), + ), + { + ...defaultTimeScaleOptions["Past 2 Weeks"], + key: "Past 2 Weeks", + }, + ); + // `seconds` == window size of one of the default options, `startSeconds` is now. + assert.deepEqual( + findClosestTimeScale( + defaultTimeScaleOptions, + defaultTimeScaleOptions["Past 10 Minutes"].windowSize.asSeconds(), + moment().unix(), + ), + { + ...defaultTimeScaleOptions["Past 10 Minutes"], + key: "Past 10 Minutes", + }, + ); + // `seconds` == window size of one of the default options, `startSeconds` is in the past. + assert.deepEqual( + findClosestTimeScale( + defaultTimeScaleOptions, + defaultTimeScaleOptions["Past 10 Minutes"].windowSize.asSeconds(), + moment() + .subtract(1, "day") + .unix(), + ), + { ...defaultTimeScaleOptions["Past 10 Minutes"], key: "Custom" }, + ); + }); + }); +});