@octopusdeploy/design-system-components
Version:
The design systems component library.
228 lines (227 loc) • 12.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const testing_library_1 = require("@octopusdeploy/testing-library");
const react_1 = require("@testing-library/react");
const vitest_1 = require("vitest");
const Timespan_1 = require("../Timespan");
(0, vitest_1.describe)("<Timespan />", () => {
(0, vitest_1.test)("renders the label as a group", () => {
const field = renderTimespan({ label: "Cache duration" });
field.shouldHaveGroupLabel("Cache duration");
});
(0, vitest_1.test)("renders day, hour and minute segments by default", () => {
const field = renderTimespan({ label: "Cache duration" });
field.shouldHaveSegments(["Days", "Hours", "Minutes"]);
field.shouldHaveSuffixes(["days", "hrs", "min"]);
});
(0, vitest_1.test)("renders hour, minute and second segments", () => {
const field = renderTimespan({ label: "Timeout", units: "hour-minute-second" });
field.shouldHaveSegments(["Hours", "Minutes", "Seconds"]);
field.shouldNotHaveSegment("Days");
});
(0, vitest_1.test)("renders minute and second segments", () => {
const field = renderTimespan({ label: "Interval", units: "minute-second" });
field.shouldHaveSegments(["Minutes", "Seconds"]);
field.shouldNotHaveSegment("Days");
field.shouldNotHaveSegment("Hours");
});
(0, vitest_1.test)("reflects the value in each segment", () => {
const field = renderTimespan({ label: "Cache duration", value: { days: 1, hours: 2, minutes: 3 } });
field.shouldHaveSegmentValue("Days", 1);
field.shouldHaveSegmentValue("Hours", 2);
field.shouldHaveSegmentValue("Minutes", 3);
});
(0, vitest_1.test)("calls onChange with the updated segment, preserving the others", async () => {
// Hours starts empty so the controlled value doesn't fight the typed keystroke (the prop value
// is never fed back through the mocked onChange in a test).
const field = renderTimespan({ label: "Cache duration", value: { days: 1, minutes: 3 } });
await field.typeIntoSegment("Hours", 5);
field.shouldHaveCalledOnChangeWith({ days: 1, hours: 5, minutes: 3 });
});
(0, vitest_1.test)("calls onChange with undefined for a cleared segment", async () => {
const field = renderTimespan({ label: "Cache duration", value: { days: 1, hours: 2, minutes: 3 } });
await field.clearSegment("Hours");
field.shouldHaveCalledOnChangeWith({ days: 1, hours: undefined, minutes: 3 });
});
(0, vitest_1.test)("day-hour-minute caps hours and minutes but leaves the leading days segment unbounded", () => {
const field = renderTimespan({ label: "Cache duration" });
field.shouldHaveSegmentMin("Days", 0);
field.shouldNotHaveSegmentMax("Days");
field.shouldHaveSegmentMax("Hours", 23);
field.shouldHaveSegmentMax("Minutes", 59);
});
(0, vitest_1.test)("hour-minute-second caps minutes and seconds but leaves the leading hours segment unbounded", () => {
const field = renderTimespan({ label: "Timeout", units: "hour-minute-second" });
field.shouldHaveSegmentMin("Hours", 0);
field.shouldNotHaveSegmentMax("Hours");
field.shouldHaveSegmentMax("Minutes", 59);
field.shouldHaveSegmentMax("Seconds", 59);
});
(0, vitest_1.test)("minute-second caps seconds but leaves the leading minutes segment unbounded", () => {
const field = renderTimespan({ label: "Interval", units: "minute-second" });
field.shouldHaveSegmentMin("Minutes", 0);
field.shouldNotHaveSegmentMax("Minutes");
field.shouldHaveSegmentMax("Seconds", 59);
});
(0, vitest_1.describe)("only accepts whole, non-negative numbers", () => {
vitest_1.test.each(["-", "+", "e", "E", ".", ","])("blocks the '%s' character in a segment", (key) => {
const field = renderTimespan({ label: "Cache duration" });
field.shouldBlockKeyInSegment("Days", key);
});
vitest_1.test.each(["0", "5", "9", "Backspace", "ArrowUp", "ArrowDown"])("allows the '%s' key in a segment", (key) => {
const field = renderTimespan({ label: "Cache duration" });
field.shouldAllowKeyInSegment("Days", key);
});
(0, vitest_1.test)("clamps a negative value to zero", () => {
const field = renderTimespan({ label: "Cache duration" });
field.setSegmentRawValue("Days", "-5");
field.shouldHaveCalledOnChangeWith({ days: 0 });
});
(0, vitest_1.test)("truncates a fractional value to a whole number", () => {
const field = renderTimespan({ label: "Cache duration" });
field.setSegmentRawValue("Days", "1.9");
field.shouldHaveCalledOnChangeWith({ days: 1 });
});
});
(0, vitest_1.describe)("clamps trailing segments to their max but leaves the leading segment unbounded", () => {
(0, vitest_1.test)("clamps an over-max trailing hours value down to its max", () => {
const field = renderTimespan({ label: "Cache duration" });
field.setSegmentRawValue("Hours", "30");
field.shouldHaveCalledOnChangeWith({ hours: 23 });
});
(0, vitest_1.test)("clamps an over-max trailing minutes value down to its max", () => {
const field = renderTimespan({ label: "Timeout", units: "hour-minute-second" });
field.setSegmentRawValue("Minutes", "90");
field.shouldHaveCalledOnChangeWith({ minutes: 59 });
});
(0, vitest_1.test)("leaves an over-max leading hours value unclamped", () => {
const field = renderTimespan({ label: "Timeout", units: "hour-minute-second" });
field.setSegmentRawValue("Hours", "50");
field.shouldHaveCalledOnChangeWith({ hours: 50 });
});
(0, vitest_1.test)("leaves an over-max leading minutes value unclamped", () => {
const field = renderTimespan({ label: "Interval", units: "minute-second" });
field.setSegmentRawValue("Minutes", "90");
field.shouldHaveCalledOnChangeWith({ minutes: 90 });
});
});
(0, vitest_1.test)("renders the description", () => {
const description = "How long to keep cached entries.";
const field = renderTimespan({ label: "Cache duration", description });
field.shouldHaveDescription(description);
});
(0, vitest_1.test)("renders the validation message without flagging individual segments", () => {
const validationMessage = "Enter a duration greater than zero";
const field = renderTimespan({ label: "Cache duration", validationMessage });
field.shouldHaveError(validationMessage);
field.shouldHaveAriaInvalid("Hours", false);
});
(0, vitest_1.test)("disables all segments when disabled", () => {
const field = renderTimespan({ label: "Cache duration", disabled: true });
field.shouldHaveDisabledSegment("Days");
field.shouldHaveDisabledSegment("Hours");
field.shouldHaveDisabledSegment("Minutes");
});
(0, vitest_1.test)("marks segments read only", () => {
const field = renderTimespan({ label: "Cache duration", readOnly: true });
field.shouldHaveReadOnlySegment("Hours");
});
(0, vitest_1.test)("autofocuses the first segment", () => {
const field = renderTimespan({ label: "Cache duration", autoFocus: true });
field.shouldHaveFocusedSegment("Days");
});
(0, vitest_1.test)("derives segment name attributes from the name prop", () => {
const field = renderTimespan({ label: "Cache duration", name: "cache" });
field.shouldHaveSegmentName("Days", "cache-days");
field.shouldHaveSegmentName("Hours", "cache-hours");
field.shouldHaveSegmentName("Minutes", "cache-minutes");
});
(0, vitest_1.test)("renders the required marker", () => {
const field = renderTimespan({ label: "Cache duration", hasRequiredMarker: true });
field.shouldHaveRequiredMarker();
});
(0, vitest_1.test)("renders the optional marker", () => {
const field = renderTimespan({ label: "Cache duration", hasOptionalMarker: true });
field.shouldHaveOptionalMarker();
});
});
function renderTimespan(options) {
const onChange = vitest_1.vi.fn();
const value = options.value ?? {};
(0, react_1.render)((0, jsx_runtime_1.jsx)(Timespan_1.Timespan, { ...options, value: value, onChange: onChange }));
return getTimespanInteractions(onChange);
}
function getTimespanInteractions(onChange) {
// domQueries has no query for a `role="group"` nor a raw-attribute matcher, so the group legend
// and attribute-level checks (min/max/readonly/aria-invalid) fall back to `screen` as a last resort.
const segmentElement = (label) => react_1.screen.getByLabelText(label);
return {
shouldHaveGroupLabel: (label) => {
(0, vitest_1.expect)(react_1.screen.getByRole("group", { name: new RegExp(label) })).toBeInTheDocument();
},
shouldHaveSegments: (labels) => {
labels.forEach((label) => testing_library_1.domQueries.getNumberInput(label).assertIsInTheDocument());
},
shouldNotHaveSegment: (label) => {
testing_library_1.domQueries.queryNumberInput(label).assertIsNotInTheDocument();
},
shouldHaveSuffixes: (suffixes) => {
suffixes.forEach((suffix) => testing_library_1.domQueries.getText(suffix).assertIsInTheDocument());
},
shouldHaveSegmentValue: (label, value) => {
testing_library_1.domQueries.getNumberInput(label).assertHasValue(value);
},
shouldHaveSegmentMin: (label, min) => {
(0, vitest_1.expect)(segmentElement(label)).toHaveAttribute("min", min.toString());
},
shouldHaveSegmentMax: (label, max) => {
(0, vitest_1.expect)(segmentElement(label)).toHaveAttribute("max", max.toString());
},
shouldNotHaveSegmentMax: (label) => {
(0, vitest_1.expect)(segmentElement(label)).not.toHaveAttribute("max");
},
shouldHaveSegmentName: (label, name) => {
(0, vitest_1.expect)(segmentElement(label)).toHaveAttribute("name", name);
},
shouldHaveDescription: (description) => {
testing_library_1.domQueries.getText(description).assertIsInTheDocument();
},
shouldHaveError: (message) => {
testing_library_1.domQueries.getText(message).assertIsInTheDocument();
},
shouldHaveAriaInvalid: (label, invalid) => {
(0, vitest_1.expect)(segmentElement(label)).toHaveAttribute("aria-invalid", invalid.toString());
},
shouldHaveDisabledSegment: (label) => {
testing_library_1.domQueries.getNumberInput(label).assertIsDisabled();
},
shouldHaveReadOnlySegment: (label) => {
(0, vitest_1.expect)(segmentElement(label)).toHaveAttribute("readonly");
},
shouldHaveFocusedSegment: (label) => {
testing_library_1.domQueries.getNumberInput(label).assertHasFocus();
},
shouldHaveRequiredMarker: () => {
testing_library_1.domQueries.getText("(required)").assertIsInTheDocument();
},
shouldHaveOptionalMarker: () => {
testing_library_1.domQueries.getText("(optional)").assertIsInTheDocument();
},
shouldHaveCalledOnChangeWith: (value) => {
(0, vitest_1.expect)(onChange).toHaveBeenLastCalledWith(value);
},
shouldBlockKeyInSegment: (label, key) => {
// fireEvent returns false when a handler called preventDefault on the event.
(0, vitest_1.expect)(react_1.fireEvent.keyDown(segmentElement(label), { key })).toBe(false);
},
shouldAllowKeyInSegment: (label, key) => {
(0, vitest_1.expect)(react_1.fireEvent.keyDown(segmentElement(label), { key })).toBe(true);
},
setSegmentRawValue: (label, rawValue) => {
react_1.fireEvent.change(segmentElement(label), { target: { value: rawValue } });
},
typeIntoSegment: (label, value) => testing_library_1.domQueries.getNumberInput(label).replaceNumber(value),
clearSegment: (label) => testing_library_1.domQueries.getNumberInput(label).clear(),
};
}