Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
Merge branch 'main' into EVG-19395-feature-branch
Browse files Browse the repository at this point in the history
  • Loading branch information
khelif96 committed Nov 7, 2023
2 parents 7a8da14 + c8cb545 commit 562fc71
Show file tree
Hide file tree
Showing 24 changed files with 511 additions and 690 deletions.
4 changes: 2 additions & 2 deletions .evergreen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,9 @@ buildvariants:
- ubuntu2204-large
expansions:
goroot: /opt/golang/go1.20
mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.6.tgz
mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-1.9.0-linux-x64.tgz
mongodb_tools_url: https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.8.0.tgz
mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.2.tgz
mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-2.0.2-linux-x64.tgz
node_version: 16.17.0
modules:
- evergreen
Expand Down
6 changes: 5 additions & 1 deletion cypress/integration/version/task_table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe("Task table", () => {
it("Updates the url when column headers are clicked", () => {
cy.visit(pathTasks);
waitForTaskTable();
// TODO: Remove wait in DEVPROD-597.
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(200);
cy.location("search").should(
"contain",
"sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC"
Expand Down Expand Up @@ -83,7 +86,8 @@ describe("Task table", () => {
});

["NAME", "STATUS", "BASE_STATUS", "VARIANT"].forEach((sortBy) => {
// TODO: This test doesn't work bc of issues with assertCorrectRequestVariables
// TODO: This test doesn't work bc of issues with assertCorrectRequestVariables.
// Remove skip in DEVPROD-597.
it.skip(`Fetches tasks sorted by ${sortBy} when ${sortBy} header is clicked`, () => {
// clickSorterAndAssertTasksAreFetched(sortBy);
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spruce",
"version": "3.0.166",
"version": "3.0.169",
"private": true,
"scripts": {
"bootstrap-logkeeper": "./scripts/bootstrap-logkeeper.sh",
Expand Down
13 changes: 1 addition & 12 deletions src/components/Header/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Cookies from "js-cookie";
import { Link, useParams } from "react-router-dom";
import { useNavbarAnalytics } from "analytics";
import Icon from "components/Icon";
import HalloweenTree from "components/Icon/icons/Halloween.svg";
import { CURRENT_PROJECT } from "constants/cookies";
import { wikiUrl } from "constants/externalResources";
import { getCommitsRoute, getUserPatchesRoute, routes } from "constants/routes";
Expand Down Expand Up @@ -61,10 +60,7 @@ export const Navbar: React.FC = () => {
to={routes.myPatches}
onClick={() => sendEvent({ name: "Click Logo Link" })}
>
<HalloweenTreeIcon
src={HalloweenTree}
alt="Evergreen Logo with Halloween Decorations"
/>
<Icon glyph="EvergreenLogo" size={32} />
</LogoLink>
<PrimaryLink
data-cy="project-health-link"
Expand Down Expand Up @@ -176,10 +172,3 @@ const secondaryStyle = css`
const SecondaryLink = styled.a`
${secondaryStyle}
`;

const HalloweenTreeIcon = styled.img`
height: 46px;
width: 46px;
position: relative;
bottom: 4px;
`;
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { useState } from "react";
import styled from "@emotion/styled";
import IconButton from "@leafygreen-ui/icon-button";
import { palette } from "@leafygreen-ui/palette";
import Icon from "components/Icon";
import IconTooltip from "components/IconTooltip";
import TextInput from "components/TextInputWithGlyph";
import TextInput from "components/TextInputWithValidation";
import { useUpsertQueryParams } from "hooks";
import { TestStatus } from "types/history";
import { validators } from "utils";

const { validateRegexp } = validators;
const { yellow } = palette;

interface HistoryTableTestSearchProps {
onSubmit?: () => void;
Expand All @@ -19,19 +13,11 @@ interface HistoryTableTestSearchProps {
export const HistoryTableTestSearch: React.FC<HistoryTableTestSearchProps> = ({
onSubmit = () => {},
}) => {
const [input, setInput] = useState("");
const isValid = validateRegexp(input);
const handleSubmit = useUpsertQueryParams();

const handleOnChange = (value: string) => {
setInput(value);
};
const handleOnSubmit = () => {
if (isValid) {
onSubmit();
handleSubmit({ category: TestStatus.Failed, value: input });
setInput("");
}
const handleOnSubmit = (input: string) => {
onSubmit();
handleSubmit({ category: TestStatus.Failed, value: input });
};

return (
Expand All @@ -40,30 +26,11 @@ export const HistoryTableTestSearch: React.FC<HistoryTableTestSearchProps> = ({
type="search"
label="Filter by Failed Tests"
aria-label="history-table-test-search-input"
value={input}
placeholder="Search test name regex"
onChange={(e) => handleOnChange(e.target.value)}
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) =>
e.key === "Enter" && handleOnSubmit()
}
icon={
isValid ? (
<IconButton
onClick={handleOnSubmit}
aria-label="Select plus button"
>
<Icon glyph="Plus" data-cy="tuple-select-button" />
</IconButton>
) : (
<IconTooltip
glyph="Warning"
data-cy="tuple-select-warning"
fill={yellow.base}
>
Invalid Regular Expression
</IconTooltip>
)
}
validatorErrorMessage="Invalid regular expression"
onSubmit={handleOnSubmit}
validator={validateRegexp}
clearOnSubmit
/>
</ContentWrapper>
);
Expand Down
26 changes: 0 additions & 26 deletions src/components/Icon/icons/Halloween.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CustomStoryObj, CustomMeta } from "test_utils/types";
import TextInputWithGlyph from ".";

export default {
title: "Components/TextInput/TextInputWithGlyph",
component: TextInputWithGlyph,
} satisfies CustomMeta<typeof TextInputWithGlyph>;

Expand Down
5 changes: 3 additions & 2 deletions src/components/TextInputWithGlyph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ type TextInputWithGlyphProps = {

const TextInputWithGlyph: React.FC<TextInputWithGlyphProps> = forwardRef(
(props, ref) => {
const { icon, ...rest } = props;
const { className, icon, ...rest } = props;

return (
<TextInputWrapper>
<TextInputWrapper className={className}>
<TextInput ref={ref} {...rest} />
<IconWrapper>{icon}</IconWrapper>
</TextInputWrapper>
Expand All @@ -38,4 +38,5 @@ const IconWrapper = styled.div`
justify-content: center;
`;

export type { TextInputWithGlyphProps };
export default TextInputWithGlyph;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CustomMeta, CustomStoryObj } from "test_utils/types";
import TextInputWithValidation from ".";

export default {
title: "Components/TextInput/TextInputWithValidation",
component: TextInputWithValidation,
} satisfies CustomMeta<typeof TextInputWithValidation>;

export const Default: CustomStoryObj<typeof TextInputWithValidation> = {
render: (args) => <TextInputWithValidation {...args} />,
argTypes: {},
args: {
validator: (v) => v !== "bad",
label: "Some search field",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { render, screen, userEvent } from "test_utils";
import TextInputWithValidation from ".";

describe("textInputWithValidation", () => {
it("should not be able to submit with an invalid input", async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(
<TextInputWithValidation
onSubmit={onSubmit}
label="textinput"
aria-label="textinput"
validator={(v) => v.length > 5}
/>
);
const input = screen.getByRole("textbox", { name: "textinput" });
await user.type(input, "test");
expect(input).toHaveValue("test");
await user.type(input, "{enter}");
expect(onSubmit).not.toHaveBeenCalledWith("test");
});
it("should not validate without a validation function", async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(
<TextInputWithValidation
onSubmit={onSubmit}
label="textinput"
aria-label="textinput"
/>
);
const input = screen.getByRole("textbox", { name: "textinput" });
await user.type(input, "test");
await user.type(input, "{enter}");
expect(onSubmit).toHaveBeenCalledWith("test");
});
it("should call onChange only for valid inputs", async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(
<TextInputWithValidation
onChange={onChange}
label="textinput"
aria-label="textinput"
validator={(v) => v.length >= 5}
/>
);
const input = screen.getByRole("textbox", { name: "textinput" });
await user.type(input, "test");
expect(onChange).not.toHaveBeenCalledWith("test");
await user.type(input, "5");
expect(onChange).toHaveBeenCalledWith("test5");
});
it("clearOnSubmit should clear the input after a valid input is submitted", async () => {
const user = userEvent.setup();
const onChange = jest.fn();
const onSubmit = jest.fn();
render(
<TextInputWithValidation
onChange={onChange}
label="textinput"
aria-label="textinput"
validator={(v) => v.length >= 5}
onSubmit={onSubmit}
clearOnSubmit
/>
);
const input = screen.getByRole("textbox", { name: "textinput" });
await user.type(input, "test");
expect(onChange).not.toHaveBeenCalledWith("test");
await user.type(input, "5");
expect(onChange).toHaveBeenCalledWith("test5");
await user.type(input, "{enter}");
expect(input).toHaveValue("");
expect(onSubmit).toHaveBeenCalledWith("test5");
});
});
99 changes: 99 additions & 0 deletions src/components/TextInputWithValidation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState, forwardRef } from "react";
import IconButton from "@leafygreen-ui/icon-button";
import { palette } from "@leafygreen-ui/palette";
import Icon from "components/Icon";
import IconTooltip from "components/IconTooltip";
import TextInputWithGlyph from "components/TextInputWithGlyph";
import type { TextInputWithGlyphProps } from "components/TextInputWithGlyph";

const { yellow } = palette;
type TextInputWithValidationProps = {
/**
* `onSubmit` will be called when the user submits a new input with the enter key or the plus button
* if the input is valid
* @param value - the value of the input
* @returns void
*/
onSubmit?: (value: string) => void;
validator?: (value: string) => boolean;
/**
* `onChange` will be called when the user types into the input and the input is valid
* @param value - the value of the input
* @returns void
*/
onChange?: (value: string) => void;
validatorErrorMessage?: string;
placeholder?: string;
/**
* If true, the input will be cleared when the user submits a new input
*/
clearOnSubmit?: boolean;
} & Omit<TextInputWithGlyphProps, "icon" | "onSubmit" | "onChange">;

const TextInputWithValidation: React.FC<TextInputWithValidationProps> =
forwardRef((props, ref) => {
const {
"aria-label": ariaLabel,
clearOnSubmit = false,
label,
onChange = () => {},
onSubmit = () => {},
validator = () => true,
validatorErrorMessage = "Invalid input",
...rest
} = props;

const [input, setInput] = useState("");
const isValid = validator(input);

const handleOnSubmit = () => {
if (isValid) {
onSubmit(input);
if (clearOnSubmit) {
setInput("");
}
}
};

const handleOnChange = (value: string) => {
if (validator(value)) {
onChange(value);
}
setInput(value);
};

return (
<TextInputWithGlyph
value={input}
onChange={(e) => handleOnChange(e.target.value)}
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) =>
e.key === "Enter" && handleOnSubmit()
}
label={label}
aria-label={ariaLabel}
ref={ref}
icon={
isValid ? (
<IconButton
onClick={handleOnSubmit}
aria-label="Select plus button"
>
<Icon glyph="Plus" />
</IconButton>
) : (
<IconTooltip
glyph="Warning"
fill={yellow.base}
aria-label="validation error"
>
{validatorErrorMessage}
</IconTooltip>
)
}
{...rest}
/>
);
});

TextInputWithValidation.displayName = "TextInputWithValidation";
export default TextInputWithValidation;
4 changes: 2 additions & 2 deletions src/components/TupleSelect/TupleSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ describe("tupleSelect", () => {
expect(input).toHaveValue("bad");
expect(onSubmit).not.toHaveBeenCalled();
expect(validator).toHaveBeenLastCalledWith("bad");
expect(screen.getByDataCy("tuple-select-warning")).toBeInTheDocument();
await user.hover(screen.queryByDataCy("tuple-select-warning"));
expect(screen.getByLabelText("validation error")).toBeInTheDocument();
await user.hover(screen.getByLabelText("validation error"));
await screen.findByText(validatorErrorMessage);
});
});
Loading

0 comments on commit 562fc71

Please sign in to comment.