diff --git a/.gitignore b/.gitignore index be2da33..a01735f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules dist *.local thumbs.db +yarn-error.log .eslintcache diff --git a/.prettierignore b/.prettierignore index bc16779..112f310 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,5 +3,8 @@ node_modules buildResources packages/main/dist packages/preload/dist -packages/rendered/dist +packages/renderer/dist +packages/main/coverage +packages/preload/coverage +packages/renderer/coverage dist/ diff --git a/.storybook/main.js b/.storybook/main.js index 93fe468..597ced5 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -19,10 +19,7 @@ const svgrPlugin = require("vite-plugin-svgr"); const viteConfig = require("../packages/renderer/vite.config"); module.exports = { - stories: [ - "../packages/renderer/**/*.stories.mdx", - "../packages/renderer/**/*.stories.@(js|jsx|ts|tsx)", - ], + stories: ["../packages/renderer/src/**/*.stories.tsx"], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", diff --git a/README.md b/README.md index ecc5477..09e1ac5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,37 @@ -# haveno-ui +# Haveno User Interface -Haveno user interface +## Development + +### Prerequisites + +1. Node 16.x +1. yarn 1.x +1. Haveno daemon and envoy proxy + +### Install dependencies + +```sh +yarn +``` + +### Configure environment variables + +Copy [.env.example](./.env.example) to a file called `.env` and point the environment variables to the envoy proxy. + +### Start the app in watch mode + +```sh +yarn watch +``` + +### Tests + +```sh +yarn tests +``` + +### Storybook + +```sh +yarn storybook +``` diff --git a/package.json b/package.json index ea85e88..d51e2f6 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,12 @@ "@storybook/addon-essentials": "^6.4.22", "@storybook/addon-interactions": "^6.4.22", "@storybook/addon-links": "^6.4.22", - "@storybook/builder-vite": "^0.1.29", + "@storybook/builder-vite": "^0.1.34", "@storybook/react": "^6.4.22", "@storybook/testing-library": "^0.0.10", + "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12", + "@testing-library/user-event": "^14.2.0", "@types/jsonwebtoken": "^8.5.8", "@types/lodash": "^4.14.182", "@types/react": "<18.0.0", @@ -69,6 +71,7 @@ "eslint-plugin-react": "^7.29.4", "happy-dom": "2.41.0", "husky": "^7.0.4", + "jsdom": "^19.0.0", "nano-staged": "^0.7.0", "playwright": "1.19.1", "prettier": "^2.6.2", @@ -97,6 +100,7 @@ "react-intl": "^5.24.8", "react-query": "^3.34.19", "react-router-dom": "6", - "recoil": "^0.7.0" + "recoil": "^0.7.0", + "tabler-icons-react": "^1.48.0" } } diff --git a/packages/renderer/src/components/atoms/Buttons/Button.stories.tsx b/packages/renderer/src/components/atoms/Buttons/Button.stories.tsx index 0ba4daf..86b1b48 100644 --- a/packages/renderer/src/components/atoms/Buttons/Button.stories.tsx +++ b/packages/renderer/src/components/atoms/Buttons/Button.stories.tsx @@ -16,7 +16,7 @@ import { Group, Stack } from "@mantine/core"; import type { ComponentStory, ComponentMeta } from "@storybook/react"; -import { Button } from "."; +import { Button, TextButton } from "."; export default { title: "atoms/Buttons", @@ -30,12 +30,14 @@ const Template: ComponentStory = () => { + Click me + Click me ); diff --git a/packages/renderer/src/components/atoms/Buttons/Button.test.tsx b/packages/renderer/src/components/atoms/Buttons/Button.test.tsx index 166f080..5bdf825 100644 --- a/packages/renderer/src/components/atoms/Buttons/Button.test.tsx +++ b/packages/renderer/src/components/atoms/Buttons/Button.test.tsx @@ -16,7 +16,7 @@ import { describe, expect, it } from "vitest"; import { render } from "@testing-library/react"; -import { Button } from "."; +import { Button, TextButton } from "."; describe("atoms::Buttons", () => { it("renders primary button by default", () => { @@ -38,4 +38,11 @@ describe("atoms::Buttons", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + + it("renders text button", () => { + const { asFragment } = render( + This is a Text Button + ); + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/packages/renderer/src/components/atoms/Buttons/TextButton.tsx b/packages/renderer/src/components/atoms/Buttons/TextButton.tsx index be37bf1..c7a000c 100644 --- a/packages/renderer/src/components/atoms/Buttons/TextButton.tsx +++ b/packages/renderer/src/components/atoms/Buttons/TextButton.tsx @@ -26,7 +26,7 @@ interface TextButtonProps extends UnstyledButtonProps<"button"> { export function TextButton(props: TextButtonProps) { const { children, ...rest } = props; return ( - + {children} diff --git a/packages/renderer/src/components/atoms/Buttons/__snapshots__/Button.test.tsx.snap b/packages/renderer/src/components/atoms/Buttons/__snapshots__/Button.test.tsx.snap index 5ed48fb..afa0dba 100644 --- a/packages/renderer/src/components/atoms/Buttons/__snapshots__/Button.test.tsx.snap +++ b/packages/renderer/src/components/atoms/Buttons/__snapshots__/Button.test.tsx.snap @@ -75,3 +75,18 @@ exports[`atoms::Buttons > renders success button 1`] = ` `; + +exports[`atoms::Buttons > renders text button 1`] = ` + + + +`; diff --git a/packages/renderer/src/components/atoms/Currency/Currency.tsx b/packages/renderer/src/components/atoms/Currency/Currency.tsx index eb7e89f..64b0bc1 100644 --- a/packages/renderer/src/components/atoms/Currency/Currency.tsx +++ b/packages/renderer/src/components/atoms/Currency/Currency.tsx @@ -28,21 +28,19 @@ export function Currency(props: CurrencyProps) { const formattedNumber = useMemo( () => - intl - .formatNumber(value, { - ...(currencyCode - ? { - currency: currencyCode, - currencyDisplay: "code", - style: "currency", - } - : { - style: "decimal", - minimumFractionDigits: 2, - maximumFractionDigits: 12, - }), - }) - .replace(/XXX\s/, ""), + intl.formatNumber(value, { + ...(currencyCode + ? { + currency: currencyCode, + currencyDisplay: "code", + style: "currency", + } + : { + style: "decimal", + minimumFractionDigits: 2, + maximumFractionDigits: 12, + }), + }), [currencyCode, value] ); diff --git a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebarItem.tsx b/packages/renderer/src/components/atoms/Link/Link.stories.tsx similarity index 56% rename from packages/renderer/src/components/molecules/AccountSidebar/AccountSidebarItem.tsx rename to packages/renderer/src/components/atoms/Link/Link.stories.tsx index f10c081..4bf2ab0 100644 --- a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebarItem.tsx +++ b/packages/renderer/src/components/atoms/Link/Link.stories.tsx @@ -14,27 +14,26 @@ // limitations under the License. // ============================================================================= -import { useNavigate } from "react-router-dom"; -import { SecondarySidebarItem } from "@molecules/SecondarySidebar"; -import { useNavLinkActive } from "@src/hooks/misc/useNavLinkActive"; +import { Stack } from "@mantine/core"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import type { LinkProps } from "."; +import { Link } from "."; -interface AccountSidebarItemProps { - label: string; - route: string; -} - -export function AccountSidebarItem({ label, route }: AccountSidebarItemProps) { - const isActive = useNavLinkActive({ to: route }); - const navigate = useNavigate(); +export default { + title: "atoms/Link", + component: Link, +} as ComponentMeta; +const Template: ComponentStory = ({ children, to }: LinkProps) => { return ( - { - return navigate(route); - }} - /> + + {children} + ); -} +}; + +export const Default = Template.bind({}); +Default.args = { + children: "Click me", + to: "/", +}; diff --git a/packages/renderer/src/components/atoms/Link/Link.test.tsx b/packages/renderer/src/components/atoms/Link/Link.test.tsx new file mode 100644 index 0000000..aa05984 --- /dev/null +++ b/packages/renderer/src/components/atoms/Link/Link.test.tsx @@ -0,0 +1,31 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import { AppProviders } from "@atoms/AppProviders"; +import { Link } from "."; + +describe("atoms::Link", () => { + it("renders without exploding", () => { + const { asFragment } = render( + + Click me + + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/renderer/src/components/atoms/Link/Link.tsx b/packages/renderer/src/components/atoms/Link/Link.tsx index f6a9423..baa4328 100644 --- a/packages/renderer/src/components/atoms/Link/Link.tsx +++ b/packages/renderer/src/components/atoms/Link/Link.tsx @@ -19,7 +19,7 @@ import { Link as RouterLink } from "react-router-dom"; import { BodyText } from "@atoms/Typography"; import type { ReactText } from "react"; -interface LinkProps extends RouterLinkProps { +export interface LinkProps extends RouterLinkProps { children: ReactText; } diff --git a/packages/renderer/src/components/atoms/Link/__snapshots__/Link.test.tsx.snap b/packages/renderer/src/components/atoms/Link/__snapshots__/Link.test.tsx.snap new file mode 100644 index 0000000..abacec5 --- /dev/null +++ b/packages/renderer/src/components/atoms/Link/__snapshots__/Link.test.tsx.snap @@ -0,0 +1,15 @@ +// Vitest Snapshot v1 + +exports[`atoms::Link > renders without exploding 1`] = ` + + + + Click me + + + +`; diff --git a/packages/renderer/src/components/atoms/NodeStatus/NodeStatus.tsx b/packages/renderer/src/components/atoms/NodeStatus/NodeStatus.tsx index e2a0b3b..043f089 100644 --- a/packages/renderer/src/components/atoms/NodeStatus/NodeStatus.tsx +++ b/packages/renderer/src/components/atoms/NodeStatus/NodeStatus.tsx @@ -14,12 +14,13 @@ // limitations under the License. // ============================================================================= -import { Box, createStyles, Text } from "@mantine/core"; +import { createStyles, Box, Text } from "@mantine/core"; export enum NodeStatusType { Active = "active", Inactive = "inactive", } + export interface NodeStatusProps { /** Node title */ title: string; @@ -34,9 +35,9 @@ export function NodeStatus({ title, status }: NodeStatusProps) { return ( {title} -
-
-
+ + + ); } @@ -49,7 +50,7 @@ export const useStyles = createStyles( theme.colorScheme === "dark" ? theme.colors.dark[8] : theme.white, border: `1px solid ${theme.colors.gray[2]}`, borderRadius: theme.radius.md, - padding: "0.91rem", + padding: "0.875rem", display: "flex", transition: "background-color 0.1s ease-in-out", }, diff --git a/packages/renderer/src/components/atoms/NodeStatus/__snapshots__/NodeStatus.test.tsx.snap b/packages/renderer/src/components/atoms/NodeStatus/__snapshots__/NodeStatus.test.tsx.snap index cc201fe..8462f81 100644 --- a/packages/renderer/src/components/atoms/NodeStatus/__snapshots__/NodeStatus.test.tsx.snap +++ b/packages/renderer/src/components/atoms/NodeStatus/__snapshots__/NodeStatus.test.tsx.snap @@ -3,7 +3,7 @@ exports[`atoms::NodeStatus > renders without exploding 1`] = `
renders without exploding 1`] = ` node.moneroworldcom:18089:active
renders without exploding 1`] = ` node.moneroworldcom:18089:inactive
diff --git a/packages/renderer/src/components/atoms/NodeStatus/index.tsx b/packages/renderer/src/components/atoms/NodeStatus/index.ts similarity index 100% rename from packages/renderer/src/components/atoms/NodeStatus/index.tsx rename to packages/renderer/src/components/atoms/NodeStatus/index.ts diff --git a/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.stories.tsx b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.stories.tsx new file mode 100644 index 0000000..a24fc63 --- /dev/null +++ b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.stories.tsx @@ -0,0 +1,43 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { Stack } from "@mantine/core"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { EyeCheck, EyeOff } from "tabler-icons-react"; +import { PasswordInput } from "."; + +export default { + title: "atoms/PasswordInput", + component: PasswordInput, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ( + + + reveal ? : + } + /> + + ); +}; + +export const Default = Template.bind({}); diff --git a/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.test.tsx b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.test.tsx new file mode 100644 index 0000000..53f3d3e --- /dev/null +++ b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.test.tsx @@ -0,0 +1,28 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import { PasswordInput } from "."; + +describe("atoms::PasswordInput", () => { + it("renders without exploding", () => { + const { asFragment } = render( + + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.tsx b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.tsx new file mode 100644 index 0000000..221d670 --- /dev/null +++ b/packages/renderer/src/components/atoms/PasswordInput/PasswordInput.tsx @@ -0,0 +1,45 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import type { PasswordInputProps as MPasswordInputProps } from "@mantine/core"; +import { createStyles, PasswordInput as MPasswordInput } from "@mantine/core"; + +interface PasswordInputProps extends MPasswordInputProps { + id: string; +} + +export function PasswordInput(props: PasswordInputProps) { + const { id, ...rest } = props; + const { classes } = useStyles(); + return ; +} + +const useStyles = createStyles((theme) => ({ + label: { + fontSize: "0.875rem", + fontWeight: 600, + marginBottom: theme.spacing.sm, + }, + innerInput: { + fontSize: "0.875rem", + fontWeight: 700, + height: "3rem", + padding: "1rem", + }, + input: { + height: "3rem", + }, +})); diff --git a/packages/renderer/src/components/atoms/PasswordInput/__snapshots__/PasswordInput.test.tsx.snap b/packages/renderer/src/components/atoms/PasswordInput/__snapshots__/PasswordInput.test.tsx.snap new file mode 100644 index 0000000..e36ad08 --- /dev/null +++ b/packages/renderer/src/components/atoms/PasswordInput/__snapshots__/PasswordInput.test.tsx.snap @@ -0,0 +1,56 @@ +// Vitest Snapshot v1 + +exports[`atoms::PasswordInput > renders without exploding 1`] = ` + +
+ +
+
+ +
+
+ +
+
+
+
+`; diff --git a/packages/renderer/src/pages/Wallet/index.ts b/packages/renderer/src/components/atoms/PasswordInput/index.ts similarity index 95% rename from packages/renderer/src/pages/Wallet/index.ts rename to packages/renderer/src/components/atoms/PasswordInput/index.ts index ad9807b..745bd9c 100644 --- a/packages/renderer/src/pages/Wallet/index.ts +++ b/packages/renderer/src/components/atoms/PasswordInput/index.ts @@ -14,4 +14,4 @@ // limitations under the License. // ============================================================================= -export * from "./Wallet"; +export * from "./PasswordInput"; diff --git a/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.test.tsx b/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.test.tsx new file mode 100644 index 0000000..ca2b5f3 --- /dev/null +++ b/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.test.tsx @@ -0,0 +1,105 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +const navigateSpy = vi.fn(); +const deleteSessionSpy = vi.fn(); + +import type { SpyInstanceFn } from "vitest"; +import { beforeAll, describe, expect, it, vi, afterEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { useAuth } from "@hooks/session/useAuth"; +import { ProtectedRoute } from "./ProtectedRoute"; +import { ROUTES } from "@constants/routes"; + +describe("atoms::ProtectedRoute", () => { + beforeAll(() => { + vi.mock("react-router-dom", () => ({ + useNavigate: () => navigateSpy, + })); + + vi.mock("@utils/session", () => ({ + deleteSession: deleteSessionSpy, + })); + + vi.mock("@hooks/session/useAuth", () => ({ + useAuth: vi.fn(() => ({ + isLoading: true, + isSuccess: false, + data: undefined, + })), + })); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("renders children if auth session exists", () => { + (useAuth as SpyInstanceFn).mockReturnValue({ + isLoading: false, + isSuccess: true, + data: true, + }); + const { asFragment, unmount } = render( + +
Protected content
+
+ ); + expect(screen.queryByText("Protected content")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("skips rendering children and redirects to login if no auth session exists", () => { + (useAuth as SpyInstanceFn).mockReturnValueOnce({ + isLoading: false, + isSuccess: true, + data: false, + }); + + const { asFragment, unmount } = render( + +
Protected content
+
+ ); + expect(screen.queryByText("Protected content")).toBeNull(); + expect(deleteSessionSpy).toHaveBeenCalledOnce(); + expect(navigateSpy).toHaveBeenCalledOnce(); + expect(navigateSpy).toHaveBeenCalledWith(ROUTES.Login); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("skips rendering children and redirects to login if auth session can't be retrieved", () => { + (useAuth as SpyInstanceFn).mockReturnValueOnce({ + isLoading: false, + isSuccess: false, + data: true, + }); + + const { asFragment, unmount } = render( + +
Protected content
+
+ ); + expect(screen.queryByText("Protected content")).toBeNull(); + expect(deleteSessionSpy).toHaveBeenCalledOnce(); + expect(navigateSpy).toHaveBeenCalledOnce(); + expect(navigateSpy).toHaveBeenCalledWith(ROUTES.Login); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.tsx b/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.tsx index b56d7bb..c39f88b 100644 --- a/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.tsx +++ b/packages/renderer/src/components/atoms/ProtectedRoute/ProtectedRoute.tsx @@ -18,7 +18,7 @@ import type { ReactNode } from "react"; import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "@hooks/session/useAuth"; -import { deleteSession } from "@src/utils/session"; +import { deleteSession } from "@utils/session"; import { ROUTES } from "@constants/routes"; export function ProtectedRoute({ children }: { children: ReactNode }) { @@ -29,11 +29,11 @@ export function ProtectedRoute({ children }: { children: ReactNode }) { if (isLoading) { return; } - if (!isAuthed) { + if (!isAuthed || !isSuccess) { deleteSession(); navigate(ROUTES.Login); } - }, [isLoading, isAuthed]); + }, [isLoading, isAuthed, isSuccess]); - return isSuccess ? <>{children} : null; + return isSuccess && isAuthed ? <>{children} : null; } diff --git a/packages/renderer/src/components/atoms/ProtectedRoute/__snapshots__/ProtectedRoute.test.tsx.snap b/packages/renderer/src/components/atoms/ProtectedRoute/__snapshots__/ProtectedRoute.test.tsx.snap new file mode 100644 index 0000000..253edd8 --- /dev/null +++ b/packages/renderer/src/components/atoms/ProtectedRoute/__snapshots__/ProtectedRoute.test.tsx.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1 + +exports[`atoms::ProtectedRoute > renders children if auth session exists 1`] = ` + +
+ Protected content +
+
+`; + +exports[`atoms::ProtectedRoute > skips rendering children and redirects to login if auth session can't be retrieved 1`] = ``; + +exports[`atoms::ProtectedRoute > skips rendering children and redirects to login if no auth session exists 1`] = ``; diff --git a/packages/renderer/src/components/atoms/Select/__snapshots__/Select.test.tsx.snap b/packages/renderer/src/components/atoms/Select/__snapshots__/Select.test.tsx.snap index fb182bf..b1cd5f3 100644 --- a/packages/renderer/src/components/atoms/Select/__snapshots__/Select.test.tsx.snap +++ b/packages/renderer/src/components/atoms/Select/__snapshots__/Select.test.tsx.snap @@ -21,8 +21,8 @@ exports[`atoms::Select > renders without exploding 1`] = ` tabindex="-1" >
renders without exploding 1`] = ` autocomplete="nope" class="mantine-Select-defaultVariant mantine-Select-input mantine-93d3e4" data-mantine-stop-propagation="false" - defaultvalue="" id="select" placeholder="Pick one" readonly="" type="text" + value="" />
; -const Template: ComponentStory = (args) => { +const Template: ComponentStory = () => { return ( - + + } /> ); }; export const Default = Template.bind({}); -Default.args = { - id: "email", - label: "Your email", - placeholder: "johndoe@gmail.com", -}; diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.stories.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.stories.tsx similarity index 92% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.stories.tsx rename to packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.stories.tsx index 45e1a6a..017848f 100644 --- a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.stories.tsx +++ b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.stories.tsx @@ -15,12 +15,13 @@ // ============================================================================= import type { ComponentStory, ComponentMeta } from "@storybook/react"; -import { NodeConnectSwitch } from "."; +import { Text } from "@mantine/core"; import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg"; import { ReactComponent as ServerIcon } from "@assets/setting-server.svg"; +import { NodeConnectSwitch } from "."; export default { - title: "atoms/NodeConnectSwitch", + title: "molecules/NodeConnectSwitch", component: NodeConnectSwitch, } as ComponentMeta; @@ -28,13 +29,11 @@ const Template: ComponentStory = () => { return ( } > - Local Node + Local Node = () => { label="Remote Node" icon={} > - Remote Node + Remote Node ); diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.style.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.style.tsx similarity index 100% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.style.tsx rename to packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.style.tsx diff --git a/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.test.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.test.tsx new file mode 100644 index 0000000..428c81f --- /dev/null +++ b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.test.tsx @@ -0,0 +1,112 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { AppProviders } from "@atoms/AppProviders"; +import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg"; +import { ReactComponent as ServerIcon } from "@assets/setting-server.svg"; +import { NodeConnectSwitch } from "."; + +describe("molecules::NodeConnectSwitch", () => { + it("renders without exploding", () => { + const { asFragment, unmount } = render( + + + } + > + Local Node Content + + + } + active={false} + current={false} + > + Remote Node Content + + + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("pre-renders active tab", () => { + const { unmount } = render( + + + } + > + Local Node Content + + + } + > + Remote Node Content + + + + ); + expect(screen.queryByText("Local Node Content")).toBeInTheDocument(); + unmount(); + }); + + it("clicking a tab reveals its contents", () => { + const { unmount } = render( + + + } + > + Local Node Content + + + } + > + Remote Node Content + + + + ); + const tabs = screen.queryAllByRole("tab"); + expect(tabs).toHaveLength(2); + expect(screen.queryByText("Remote Node Content")).not.toBeInTheDocument(); + fireEvent.click(tabs[1]); + expect(screen.queryByText("Remote Node Content")).toBeInTheDocument(); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.tsx similarity index 94% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.tsx rename to packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.tsx index 85cbf9d..3f4f350 100644 --- a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.tsx +++ b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitch.tsx @@ -73,12 +73,12 @@ export function NodeConnectSwitch({ return ( -
{panes}
+ {panes} {content && ( -
+ {content.props.children} -
+
)} ); diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitchMethod.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitchMethod.tsx similarity index 100% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitchMethod.tsx rename to packages/renderer/src/components/molecules/NodeConnectSwitch/NodeConnectSwitchMethod.tsx diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.jsx.snap b/packages/renderer/src/components/molecules/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.tsx.snap similarity index 97% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.jsx.snap rename to packages/renderer/src/components/molecules/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.tsx.snap index a85d6c5..2a03e4f 100644 --- a/packages/renderer/src/components/atoms/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.jsx.snap +++ b/packages/renderer/src/components/molecules/NodeConnectSwitch/__snapshots__/NodeConnectSwitch.test.tsx.snap @@ -1,12 +1,12 @@ // Vitest Snapshot v1 -exports[`atoms::NodeConnectSwitch > renders without exploding 1`] = ` +exports[`molecules::NodeConnectSwitch > renders without exploding 1`] = `
-
diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/index.tsx b/packages/renderer/src/components/molecules/NodeConnectSwitch/index.tsx similarity index 100% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/index.tsx rename to packages/renderer/src/components/molecules/NodeConnectSwitch/index.tsx diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/AddPaymentMethodButton.test.tsx b/packages/renderer/src/components/molecules/PaymentMethodCard/AddPaymentMethodButton.test.tsx new file mode 100644 index 0000000..0893bde --- /dev/null +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/AddPaymentMethodButton.test.tsx @@ -0,0 +1,39 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { AddPaymentMethodButton } from "."; + +describe("molecules::AddPaymentMethodButton", () => { + it("renders without exploding", () => { + const spy = vi.fn(); + const { asFragment, unmount } = render( + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("calls onClick", () => { + const spy = vi.fn(); + const { unmount } = render(); + expect(spy).to.not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole("button")); + expect(spy).to.toHaveBeenCalledTimes(1); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.stories.tsx b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.stories.tsx new file mode 100644 index 0000000..9b95dc6 --- /dev/null +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.stories.tsx @@ -0,0 +1,56 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { Stack } from "@mantine/core"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { + CryptoCurrencyAccountPayload, + PaymentAccount, + PaymentAccountPayload, +} from "haveno-ts"; +import { AddPaymentMethodButton, PaymentMethodCard } from "."; + +export default { + title: "molecules/PaymentMethodCard", + component: PaymentMethodCard, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ( + + + + ); +}; + +const Template2: ComponentStory = () => { + return ( + + console.log("onClick called")} /> + + ); +}; + +export const Default = Template.bind({}); +export const AddButton = Template2.bind({}); + +const paymentAccount1 = new PaymentAccount(); +paymentAccount1.setAccountName("BTC Account 1"); +const cryptoAccPayload1 = new CryptoCurrencyAccountPayload(); +cryptoAccPayload1.setAddress("01234567abcdef"); +const paymentAccPayload1 = new PaymentAccountPayload(); +paymentAccPayload1.setCryptoCurrencyAccountPayload(cryptoAccPayload1); +paymentAccount1.setPaymentAccountPayload(paymentAccPayload1); diff --git a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.test.jsx b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.test.tsx similarity index 53% rename from packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.test.jsx rename to packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.test.tsx index bf05e7b..8f31323 100644 --- a/packages/renderer/src/components/atoms/NodeConnectSwitch/NodeConnectSwitch.test.jsx +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.test.tsx @@ -16,38 +16,30 @@ import { describe, expect, it } from "vitest"; import { render } from "@testing-library/react"; +import { + CryptoCurrencyAccountPayload, + PaymentAccount, + PaymentAccountPayload, +} from "haveno-ts"; import { AppProviders } from "@atoms/AppProviders"; -import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg"; -import { ReactComponent as ServerIcon } from "@assets/setting-server.svg"; -import { NodeConnectSwitch } from "."; +import { PaymentMethodCard } from "."; -describe("atoms::NodeConnectSwitch", () => { +describe("molecules::PaymentMethodCard", () => { it("renders without exploding", () => { - const { asFragment } = render( + const { asFragment, unmount } = render( - - } - > - Local Node - - - } - active={false} - current={false} - > - Remote Node - - + ); expect(asFragment()).toMatchSnapshot(); + unmount(); }); }); + +const paymentAccount1 = new PaymentAccount(); +paymentAccount1.setAccountName("BTC Account 1"); +const cryptoAccPayload1 = new CryptoCurrencyAccountPayload(); +cryptoAccPayload1.setAddress("01234567abcdef"); +const paymentAccPayload1 = new PaymentAccountPayload(); +paymentAccPayload1.setCryptoCurrencyAccountPayload(cryptoAccPayload1); +paymentAccount1.setPaymentAccountPayload(paymentAccPayload1); diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.tsx b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.tsx index 389b2a1..4160bcd 100644 --- a/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.tsx +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/PaymentMethodCard.tsx @@ -31,7 +31,7 @@ import { getPaymentAccountLogo, getPaymentAccountName, getPaymentAccountNumber, -} from "@src/utils/payment-account"; +} from "@utils/payment-account"; interface PaymentMethodCardProps { data: PaymentAccount; diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/AddPaymentMethodButton.test.tsx.snap b/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/AddPaymentMethodButton.test.tsx.snap new file mode 100644 index 0000000..bb3404d --- /dev/null +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/AddPaymentMethodButton.test.tsx.snap @@ -0,0 +1,24 @@ +// Vitest Snapshot v1 + +exports[`molecules::AddPaymentMethodButton > renders without exploding 1`] = ` + + + +`; diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/PaymentMethodCard.test.tsx.snap b/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/PaymentMethodCard.test.tsx.snap new file mode 100644 index 0000000..715e2ef --- /dev/null +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/__snapshots__/PaymentMethodCard.test.tsx.snap @@ -0,0 +1,67 @@ +// Vitest Snapshot v1 + +exports[`molecules::PaymentMethodCard > renders without exploding 1`] = ` + +
+
+
+
+ + + + +
+ BTC +
+
+ +
+
+ 01234567abcdef +
+
+
+
+`; diff --git a/packages/renderer/src/components/molecules/PaymentMethodCard/_types.ts b/packages/renderer/src/components/molecules/PaymentMethodCard/_types.ts index 20ae83d..65ba1aa 100644 --- a/packages/renderer/src/components/molecules/PaymentMethodCard/_types.ts +++ b/packages/renderer/src/components/molecules/PaymentMethodCard/_types.ts @@ -14,4 +14,5 @@ // limitations under the License. // ============================================================================= +// TODO @subir: move this to @constants/currencies export type SupportedCurrencies = "BTC" | "ETH" | "EUR"; diff --git a/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.stories.tsx b/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.stories.tsx new file mode 100644 index 0000000..978bd8b --- /dev/null +++ b/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.stories.tsx @@ -0,0 +1,34 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { Container } from "@mantine/core"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { ReadyToUse } from "."; + +export default { + title: "molecules/ReadyToUse", + component: ReadyToUse, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ( + + console.log("onSubmit called")} /> + + ); +}; + +export const Default = Template.bind({}); diff --git a/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.test.tsx b/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.test.tsx new file mode 100644 index 0000000..e9ea6e1 --- /dev/null +++ b/packages/renderer/src/components/molecules/ReadyToUse/ReadyToUse.test.tsx @@ -0,0 +1,37 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { ReadyToUse } from "."; + +describe("molecules::ReadyToUse", () => { + it("renders without exploding", () => { + const spy = vi.fn(); + const { asFragment, unmount } = render(); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("calls onSubmit", () => { + const spy = vi.fn(); + const { unmount } = render(); + expect(spy).to.not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole("button")); + expect(spy).to.toHaveBeenCalledTimes(1); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/molecules/ReadyToUse/__snapshots__/ReadyToUse.test.tsx.snap b/packages/renderer/src/components/molecules/ReadyToUse/__snapshots__/ReadyToUse.test.tsx.snap new file mode 100644 index 0000000..564c7dc --- /dev/null +++ b/packages/renderer/src/components/molecules/ReadyToUse/__snapshots__/ReadyToUse.test.tsx.snap @@ -0,0 +1,41 @@ +// Vitest Snapshot v1 + +exports[`molecules::ReadyToUse > renders without exploding 1`] = ` + +
+

+ Haveno is ready for use. +

+
+ You’ve succesfully set up Haveno. Please note that to be able to trade, you need to deposit Monero in your Haveno wallet and set up a payment account. +
+
+
+ +
+
+ +`; diff --git a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.test.tsx b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.test.tsx index 011e4a0..7ab96bb 100644 --- a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.test.tsx +++ b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.test.tsx @@ -16,19 +16,19 @@ import { describe, expect, it } from "vitest"; import { render } from "@testing-library/react"; +import { AppProviders } from "@atoms/AppProviders"; import { SecondarySidebar, SecondarySidebarItem } from "./"; -import { ThemeProvider } from "@atoms/AppProviders/ThemeProvider"; describe("molecules::SecondarySidebar", () => { it("renders without exploding", () => { const { asFragment } = render( - + - + ); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.tsx b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.tsx index 57c3b30..8fecdf8 100644 --- a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.tsx +++ b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebar.tsx @@ -25,7 +25,9 @@ interface SecondarySidebarProps { * @param {SecondarySidebarProps} * @returns {JSX.Element} */ -export function SecondarySidebar({ children }: SecondarySidebarProps) { +export function SecondarySidebar({ + children, +}: SecondarySidebarProps): JSX.Element { const { classes } = useStyles(); return ( diff --git a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebarItem.tsx b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebarItem.tsx index 1369c2f..3680868 100644 --- a/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebarItem.tsx +++ b/packages/renderer/src/components/molecules/SecondarySidebar/SecondarySidebarItem.tsx @@ -14,12 +14,14 @@ // limitations under the License. // ============================================================================= +import { useNavLinkActive } from "@hooks/misc/useNavLinkActive"; import { UnstyledButton, Group, Text, createStyles } from "@mantine/core"; interface SecondarySidebarItemProps { isActive?: boolean; label: string; onClick?: (e: React.MouseEvent) => void; + route?: string; } /** @@ -31,8 +33,10 @@ export function SecondarySidebarItem({ isActive = false, label, onClick, -}: SecondarySidebarItemProps) { - const { classes } = useStyles({ isActive }); + route, +}: SecondarySidebarItemProps): JSX.Element { + const isRouteActive = useNavLinkActive({ to: route }); + const { classes } = useStyles({ isActive: isRouteActive || isActive }); return ( diff --git a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.stories.tsx b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.stories.tsx similarity index 96% rename from packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.stories.tsx rename to packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.stories.tsx index 51a73b8..2cc04b3 100644 --- a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.stories.tsx +++ b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.stories.tsx @@ -18,7 +18,7 @@ import type { ComponentStory, ComponentMeta } from "@storybook/react"; import { AccountSidebar } from "./AccountSidebar"; export default { - title: "molecules/AccountSidebar", + title: "organisms/AccountSidebar", component: AccountSidebar, } as ComponentMeta; diff --git a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.test.tsx b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.test.tsx similarity index 89% rename from packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.test.tsx rename to packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.test.tsx index 48e34c2..18b51b1 100644 --- a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.test.tsx +++ b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.test.tsx @@ -16,7 +16,6 @@ import { describe, expect, it } from "vitest"; import { render } from "@testing-library/react"; -import { Routes, Route } from "react-router-dom"; import { AppProviders } from "@atoms/AppProviders"; import { AccountSidebar } from "./AccountSidebar"; @@ -24,9 +23,7 @@ describe("molecules::AccountSidebar", () => { it("renders without exploding", () => { const { asFragment } = render( - - } /> - + ); expect(asFragment()).toMatchSnapshot(); diff --git a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.tsx b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.tsx similarity index 86% rename from packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.tsx rename to packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.tsx index 8ea9f7c..53f86fb 100644 --- a/packages/renderer/src/components/molecules/AccountSidebar/AccountSidebar.tsx +++ b/packages/renderer/src/components/organisms/AccountSidebar/AccountSidebar.tsx @@ -16,15 +16,18 @@ import { createStyles, Box, Title } from "@mantine/core"; import { FormattedMessage } from "react-intl"; -import { SecondarySidebar } from "@molecules/SecondarySidebar"; +import { useNavigate } from "react-router-dom"; +import { + SecondarySidebar, + SecondarySidebarItem, +} from "@molecules/SecondarySidebar"; import { LangKeys } from "@constants/lang"; import { WIDTH } from "./_constants"; import { useGetAccountSidebarMenu } from "./_hooks"; -import { AccountSidebarItem } from "./AccountSidebarItem"; export function AccountSidebar() { const { classes } = useStyles(); - + const navigate = useNavigate(); const menu = useGetAccountSidebarMenu(); return ( @@ -35,10 +38,13 @@ export function AccountSidebar() { {menu.map((item) => ( - { + navigate(item.route); + }} /> ))} diff --git a/packages/renderer/src/components/molecules/AccountSidebar/__snapshots__/AccountSidebar.test.tsx.snap b/packages/renderer/src/components/organisms/AccountSidebar/__snapshots__/AccountSidebar.test.tsx.snap similarity index 100% rename from packages/renderer/src/components/molecules/AccountSidebar/__snapshots__/AccountSidebar.test.tsx.snap rename to packages/renderer/src/components/organisms/AccountSidebar/__snapshots__/AccountSidebar.test.tsx.snap diff --git a/packages/renderer/src/components/molecules/AccountSidebar/_constants.ts b/packages/renderer/src/components/organisms/AccountSidebar/_constants.ts similarity index 100% rename from packages/renderer/src/components/molecules/AccountSidebar/_constants.ts rename to packages/renderer/src/components/organisms/AccountSidebar/_constants.ts diff --git a/packages/renderer/src/components/molecules/AccountSidebar/_hooks.ts b/packages/renderer/src/components/organisms/AccountSidebar/_hooks.ts similarity index 100% rename from packages/renderer/src/components/molecules/AccountSidebar/_hooks.ts rename to packages/renderer/src/components/organisms/AccountSidebar/_hooks.ts diff --git a/packages/renderer/src/components/molecules/AccountSidebar/index.tsx b/packages/renderer/src/components/organisms/AccountSidebar/index.ts similarity index 100% rename from packages/renderer/src/components/molecules/AccountSidebar/index.tsx rename to packages/renderer/src/components/organisms/AccountSidebar/index.ts diff --git a/packages/renderer/src/components/organisms/AddPaymentMethod/AddPaymentMethod.tsx b/packages/renderer/src/components/organisms/AddPaymentMethod/AddPaymentMethod.tsx index 117d255..3a02d27 100644 --- a/packages/renderer/src/components/organisms/AddPaymentMethod/AddPaymentMethod.tsx +++ b/packages/renderer/src/components/organisms/AddPaymentMethod/AddPaymentMethod.tsx @@ -20,15 +20,9 @@ import { useForm, joiResolver } from "@mantine/form"; import Joi from "joi"; import { Button } from "@atoms/Buttons"; import { Select } from "@atoms/Select"; +import { TextInput } from "@atoms/TextInput"; import { SupportedCurrencies } from "@constants/currencies"; import { PaymentMethods as _PaymentMethods } from "@constants/payment-methods"; -import { TextInput } from "@atoms/TextInput"; - -interface FormValues { - currency: string; - paymentMethod: string; - accountNumber: string; -} export function AddPaymentMethod() { const { getInputProps, onSubmit, setFieldValue, values } = @@ -41,7 +35,7 @@ export function AddPaymentMethod() { }, }); - const PaymentMethods = useMemo(() => { + const paymentMethods = useMemo(() => { if (!values.currency) { return []; } @@ -61,6 +55,7 @@ export function AddPaymentMethod() { .sort((a, b) => (a.label > b.label ? 1 : -1)); }, [values?.currency]); + // TODO @subir const handleSubmit = (values: FormValues) => console.log(values); useEffect(() => { @@ -86,7 +81,7 @@ export function AddPaymentMethod() {
renders without exploding 1`] = ` autocomplete="nope" class="mantine-Select-defaultVariant mantine-Select-input mantine-93d3e4" data-mantine-stop-propagation="false" - defaultvalue="" id="currency" placeholder="Pick one" readonly="" type="text" + value="" />
renders without exploding 1`] = ` tabindex="-1" >
renders without exploding 1`] = ` autocomplete="nope" class="mantine-Select-defaultVariant mantine-Select-input mantine-16ko5f2" data-mantine-stop-propagation="false" - defaultvalue="" id="paymentMethod" placeholder="Pick one" type="text" + value="" />
renders without exploding 1`] = `
diff --git a/packages/renderer/src/pages/Wallet/Walet.stories.tsx b/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.stories.tsx similarity index 77% rename from packages/renderer/src/pages/Wallet/Walet.stories.tsx rename to packages/renderer/src/components/organisms/ChangePassword/ChangePassword.stories.tsx index 7d8ddce..cba3a12 100644 --- a/packages/renderer/src/pages/Wallet/Walet.stories.tsx +++ b/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.stories.tsx @@ -14,21 +14,16 @@ // limitations under the License. // ============================================================================= -import { Stack } from "@mantine/core"; import type { ComponentStory, ComponentMeta } from "@storybook/react"; -import { Wallet } from "."; +import { ChangePassword } from "."; export default { - title: "pages/Wallet", - component: Wallet, -} as ComponentMeta; + title: "organisms/Change Password", + component: ChangePassword, +} as ComponentMeta; -const Template: ComponentStory = () => { - return ( - - - - ); +const Template: ComponentStory = () => { + return ; }; export const Default = Template.bind({}); diff --git a/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.tsx b/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.tsx index af84ec8..2d59f05 100644 --- a/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.tsx +++ b/packages/renderer/src/components/organisms/ChangePassword/ChangePassword.tsx @@ -18,7 +18,7 @@ import { FormattedMessage } from "react-intl"; import { Stack, Box, Group } from "@mantine/core"; import { useForm, joiResolver } from "@mantine/form"; import { showNotification } from "@mantine/notifications"; -import { TextInput } from "@atoms/TextInput"; +import { PasswordInput } from "@atoms/PasswordInput"; import { LangKeys } from "@constants/lang"; import { Button } from "@atoms/Buttons"; import { useChangePassword } from "@hooks/storage/useChangePassword"; @@ -68,9 +68,8 @@ export function ChangePassword() {
- - - ; + +const Template: ComponentStory = (args) => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = { + onGoBack: () => console.log("go back"), + onNext: () => console.log("next"), +}; diff --git a/packages/renderer/src/components/organisms/SetPassword/SetPassword.stories.tsx b/packages/renderer/src/components/organisms/SetPassword/SetPassword.stories.tsx new file mode 100644 index 0000000..beb8d80 --- /dev/null +++ b/packages/renderer/src/components/organisms/SetPassword/SetPassword.stories.tsx @@ -0,0 +1,33 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { SetPassword } from "."; + +export default { + title: "organisms/Set Password", + component: SetPassword, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = { + onGoBack: () => console.log("go back"), + onNext: (value: string) => console.log(value), +}; diff --git a/packages/renderer/src/components/organisms/SetPassword/SetPassword.test.tsx b/packages/renderer/src/components/organisms/SetPassword/SetPassword.test.tsx new file mode 100644 index 0000000..5139ed9 --- /dev/null +++ b/packages/renderer/src/components/organisms/SetPassword/SetPassword.test.tsx @@ -0,0 +1,86 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { AppProviders } from "@atoms/AppProviders"; +import { SetPassword } from "."; + +describe("organisms::SetPassword", () => { + it("renders without exploding", () => { + const onBackSpy = vi.fn(); + const onNextSpy = vi.fn(); + const { asFragment, unmount } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("calls onGoBack", async () => { + const onBackSpy = vi.fn(); + const onNextSpy = vi.fn(); + const { unmount } = render( + + + + ); + expect(onBackSpy).to.not.toHaveBeenCalled(); + fireEvent.click(await screen.findByLabelText("Click to go back")); + expect(onBackSpy).to.toHaveBeenCalledTimes(1); + expect(onNextSpy).to.not.toHaveBeenCalled(); + unmount(); + }); + + it("blocks submit if validation fails", async () => { + const onBackSpy = vi.fn(); + const onNextSpy = vi.fn(); + + const { unmount } = render( + + + + ); + expect(onNextSpy).to.not.toHaveBeenCalled(); + fireEvent.click(await screen.findByLabelText("Click to submit")); + expect(onNextSpy).to.not.toHaveBeenCalled(); + unmount(); + }); + + it("calls onSubmit if validation succeeds", async () => { + const onBackSpy = vi.fn(); + const onNextSpy = vi.fn(); + const user = userEvent.setup(); + const { unmount } = render( + + + + ); + await user.type(screen.getByLabelText("Enter password"), "Qwe$9999", { + skipAutoClose: true, + }); + await user.type(screen.getByLabelText("Repeat password"), "Qwe$9999", { + skipAutoClose: true, + }); + expect(onNextSpy).to.not.toHaveBeenCalled(); + fireEvent.submit(screen.getByLabelText("Click to submit")); + expect(onNextSpy).to.toHaveBeenCalledTimes(1); + unmount(); + }); +}); diff --git a/packages/renderer/src/components/organisms/SetPassword/SetPassword.tsx b/packages/renderer/src/components/organisms/SetPassword/SetPassword.tsx index 35725c5..682e809 100644 --- a/packages/renderer/src/components/organisms/SetPassword/SetPassword.tsx +++ b/packages/renderer/src/components/organisms/SetPassword/SetPassword.tsx @@ -23,9 +23,9 @@ import { Button, TextButton } from "@atoms/Buttons"; import { LangKeys } from "@constants/lang"; interface SetPasswordProps { - value: string; onGoBack: () => void; onNext: (password: string) => void; + value?: string; } export function SetPassword(props: SetPasswordProps) { @@ -43,7 +43,7 @@ export function SetPassword(props: SetPasswordProps) { }; return ( - + @@ -55,21 +55,31 @@ export function SetPassword(props: SetPasswordProps) { password. - Go Back - + + Go Back + + diff --git a/packages/renderer/src/components/organisms/SetPassword/__snapshots__/SetPassword.test.tsx.snap b/packages/renderer/src/components/organisms/SetPassword/__snapshots__/SetPassword.test.tsx.snap new file mode 100644 index 0000000..86d4cf8 --- /dev/null +++ b/packages/renderer/src/components/organisms/SetPassword/__snapshots__/SetPassword.test.tsx.snap @@ -0,0 +1,109 @@ +// Vitest Snapshot v1 + +exports[`organisms::SetPassword > renders without exploding 1`] = ` + +
+
+
+

+ Create password +

+
+
+ All your data is stored locally on your machine. Haveno uses solely a password. +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
+ + +`; diff --git a/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.stories.tsx b/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.stories.tsx new file mode 100644 index 0000000..b7c7a68 --- /dev/null +++ b/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.stories.tsx @@ -0,0 +1,33 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { SetPrimaryFiat } from "."; + +export default { + title: "organisms/Set Primary Fiat", + component: SetPrimaryFiat, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = { + onGoBack: () => console.log("go back"), + onNext: (value: string) => console.log(value), +}; diff --git a/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.tsx b/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.tsx index 69a4651..c4f2bcd 100644 --- a/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.tsx +++ b/packages/renderer/src/components/organisms/SetPrimaryFiat/SetPrimaryFiat.tsx @@ -25,7 +25,7 @@ import { SupportedCurrencies } from "@constants/currencies"; interface SetSetPrimaryFiatProps { onGoBack: () => void; onNext: (fiat: string) => void; - value: string; + value?: string; } export function SetPrimaryFiat(props: SetSetPrimaryFiatProps) { diff --git a/packages/renderer/src/components/organisms/Sidebar/Sidebar.test.tsx b/packages/renderer/src/components/organisms/Sidebar/Sidebar.test.tsx index d95b3b2..7beb859 100644 --- a/packages/renderer/src/components/organisms/Sidebar/Sidebar.test.tsx +++ b/packages/renderer/src/components/organisms/Sidebar/Sidebar.test.tsx @@ -14,12 +14,23 @@ // limitations under the License. // ============================================================================= -import { describe, expect, it } from "vitest"; +import { beforeAll, describe, expect, it, vi } from "vitest"; import { render } from "@testing-library/react"; import { AppProviders } from "@atoms/AppProviders"; +import { SyncStatus } from "@constants/sync-status"; import { Sidebar } from "."; describe("molecules::Sidebar", () => { + beforeAll(() => { + vi.mock("@hooks/haveno/useSyncStatus", () => ({ + useSyncStatus: () => ({ + isLoading: false, + isSuccess: true, + data: SyncStatus.NotSynced, + }), + })); + }); + it("renders without exploding", () => { const { asFragment } = render( diff --git a/packages/renderer/src/components/templates/AccountLayout/index.tsx b/packages/renderer/src/components/templates/AccountLayout/index.tsx index ca81681..5e09d6d 100644 --- a/packages/renderer/src/components/templates/AccountLayout/index.tsx +++ b/packages/renderer/src/components/templates/AccountLayout/index.tsx @@ -16,7 +16,7 @@ import { Group, createStyles, Box } from "@mantine/core"; import { NavbarLayout } from "@templates/NavbarLayout"; -import { AccountSidebar } from "@molecules/AccountSidebar"; +import { AccountSidebar } from "@organisms/AccountSidebar"; interface AccountContentProps { children: JSX.Element | JSX.Element[]; diff --git a/packages/renderer/src/constants/currencies.ts b/packages/renderer/src/constants/currencies.ts index 71a6b8c..fc5d506 100644 --- a/packages/renderer/src/constants/currencies.ts +++ b/packages/renderer/src/constants/currencies.ts @@ -19,7 +19,9 @@ import { ReactComponent as EthLogo } from "@assets/eth.svg"; import { ReactComponent as EurLogo } from "@assets/eur.svg"; import { PaymentMethodIds } from "./payment-methods"; -export type SupportedFiat = "USD" | "ETH" | "GBP"; +export type SupportedFiat = "USD" | "EUR" | "GBP"; + +export type SupportedCrypto = "XMR" | "BTC" | "ETH"; export const SupportedCurrencies = [ { diff --git a/packages/renderer/src/hooks/haveno/useIsMoneroNodeRunning.ts b/packages/renderer/src/hooks/haveno/useIsMoneroNodeRunning.ts index 650ea9d..3a320bd 100644 --- a/packages/renderer/src/hooks/haveno/useIsMoneroNodeRunning.ts +++ b/packages/renderer/src/hooks/haveno/useIsMoneroNodeRunning.ts @@ -21,7 +21,12 @@ import { useHavenoClient } from "./useHavenoClient"; export function useIsMoneroNodeRunning() { const client = useHavenoClient(); - return useQuery(QueryKeys.MoneroNodeIsRunning, () => - client.isMoneroNodeRunning() - ); + return useQuery(QueryKeys.MoneroNodeIsRunning, async () => { + try { + const value = await client.isMoneroNodeRunning(); + return value; + } catch { + return false; + } + }); } diff --git a/packages/renderer/src/hooks/misc/useNavLinkActive.ts b/packages/renderer/src/hooks/misc/useNavLinkActive.ts index 50f613e..e8cd123 100644 --- a/packages/renderer/src/hooks/misc/useNavLinkActive.ts +++ b/packages/renderer/src/hooks/misc/useNavLinkActive.ts @@ -14,12 +14,13 @@ // limitations under the License. // ============================================================================= +import { useMemo } from "react"; import { useResolvedPath, useLocation } from "react-router-dom"; interface LinkItemActiveProps { - to: string; caseSensitive?: boolean; end?: boolean; + to?: string; } /** @@ -31,22 +32,29 @@ export const useNavLinkActive = ({ caseSensitive = false, end = false, to, -}: LinkItemActiveProps) => { +}: LinkItemActiveProps): boolean => { const location = useLocation(); - const path = useResolvedPath(to); + const path = useResolvedPath(to ?? ""); - let locationPathname = location.pathname; - let toPathname = path.pathname; + const isActive = useMemo(() => { + if (!to) { + return false; + } + let locationPathName = location.pathname; + let toPathName = path.pathname; - if (!caseSensitive) { - locationPathname = locationPathname.toLowerCase(); - toPathname = toPathname.toLowerCase(); - } + if (!caseSensitive) { + locationPathName = locationPathName.toLowerCase(); + toPathName = toPathName.toLowerCase(); + } - return ( - locationPathname === toPathname || - (!end && - locationPathname.startsWith(toPathname) && - locationPathname.charAt(toPathname.length) === "/") - ); + return ( + locationPathName === toPathName || + (!end && + locationPathName.startsWith(toPathName) && + locationPathName.charAt(toPathName.length) === "/") + ); + }, [location, path, caseSensitive, end, to]); + + return isActive; }; diff --git a/packages/renderer/src/hooks/session/useAuth.ts b/packages/renderer/src/hooks/session/useAuth.ts index 57e4be3..4da5831 100644 --- a/packages/renderer/src/hooks/session/useAuth.ts +++ b/packages/renderer/src/hooks/session/useAuth.ts @@ -15,11 +15,11 @@ // ============================================================================= import { QueryKeys } from "@constants/query-keys"; -import { validateSession } from "@src/utils/session"; +import { validateSession } from "@utils/session"; import { useQuery } from "react-query"; export function useAuth() { - return useQuery( + return useQuery( QueryKeys.AuthSession, async () => { if (await validateSession()) { diff --git a/packages/renderer/src/hooks/storage/useChangePassword.ts b/packages/renderer/src/hooks/storage/useChangePassword.ts index d8f5cb7..0e63c45 100644 --- a/packages/renderer/src/hooks/storage/useChangePassword.ts +++ b/packages/renderer/src/hooks/storage/useChangePassword.ts @@ -14,10 +14,10 @@ // limitations under the License. // ============================================================================= -import { QueryKeys } from "@constants/query-keys"; -import { getIpcError } from "@src/utils/get-ipc-error"; -import { createSession } from "@src/utils/session"; import { useMutation, useQueryClient } from "react-query"; +import { QueryKeys } from "@constants/query-keys"; +import { getIpcError } from "@utils/get-ipc-error"; +import { createSession } from "@utils/session"; interface Variables { currentPassword: string; diff --git a/packages/renderer/src/pages/Account/NodeSettings/NodeSettingsSwitch.tsx b/packages/renderer/src/pages/Account/NodeSettings/NodeSettingsSwitch.tsx index a400927..2ff11d7 100644 --- a/packages/renderer/src/pages/Account/NodeSettings/NodeSettingsSwitch.tsx +++ b/packages/renderer/src/pages/Account/NodeSettings/NodeSettingsSwitch.tsx @@ -17,7 +17,7 @@ import { createStyles } from "@mantine/core"; import { FormattedMessage } from "react-intl"; import { LangKeys } from "@constants/lang"; -import { NodeConnectSwitch } from "@atoms/NodeConnectSwitch"; +import { NodeConnectSwitch } from "@molecules/NodeConnectSwitch"; import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg"; import { ReactComponent as ServerIcon } from "@assets/setting-server.svg"; import { NodeLocalForm } from "./NodeLocalForm"; diff --git a/packages/renderer/src/pages/Home/Home.stories.tsx b/packages/renderer/src/pages/Home/Home.stories.tsx index b299250..60521f7 100644 --- a/packages/renderer/src/pages/Home/Home.stories.tsx +++ b/packages/renderer/src/pages/Home/Home.stories.tsx @@ -15,20 +15,15 @@ // ============================================================================= import type { ComponentStory, ComponentMeta } from "@storybook/react"; -import { AppProviders } from "@atoms/AppProviders"; import { Home } from "."; export default { - title: "pages/Onboarding/Home", + title: "pages/Home", component: Home, } as ComponentMeta; const Template: ComponentStory = () => { - return ( - - - - ); + return ; }; export const Default = Template.bind({}); diff --git a/packages/renderer/src/pages/Wallet/Wallet.tsx b/packages/renderer/src/pages/Login/Login.stories.tsx similarity index 69% rename from packages/renderer/src/pages/Wallet/Wallet.tsx rename to packages/renderer/src/pages/Login/Login.stories.tsx index b50f80d..72bb0ad 100644 --- a/packages/renderer/src/pages/Wallet/Wallet.tsx +++ b/packages/renderer/src/pages/Login/Login.stories.tsx @@ -14,8 +14,17 @@ // limitations under the License. // ============================================================================= -import { NavbarLayout } from "@templates/NavbarLayout"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { Login } from "."; -export function Wallet() { - return ; -} +export default { + title: "pages/Login", + component: Login, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/pages/Login/Login.test.tsx b/packages/renderer/src/pages/Login/Login.test.tsx new file mode 100644 index 0000000..cfb1f87 --- /dev/null +++ b/packages/renderer/src/pages/Login/Login.test.tsx @@ -0,0 +1,113 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +const navSpy = vi.fn(); +const loginSpy = vi.fn(); + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { AppProviders } from "@atoms/AppProviders"; +import { Login } from "."; +import { ROUTES } from "@constants/routes"; + +describe("pages::Login", () => { + beforeEach(() => { + vi.mock("react-router-dom", async () => ({ + ...(await vi.importActual("react-router-dom")), // eslint-disable-line @typescript-eslint/no-explicit-any + useNavigate: () => navSpy, + })); + + vi.mock("@hooks/session/useLogin", () => ({ + useLogin: () => ({ + mutate: loginSpy, + isLoading: false, + }), + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("renders without exploding", () => { + const { asFragment, unmount } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + unmount(); + }); + + it("blocks login if validation fails", async () => { + const user = userEvent.setup(); + const { unmount } = render( + + + + ); + const btnSubmit = screen.getByRole("button", { name: "Login" }); + fireEvent.submit(btnSubmit); + expect(loginSpy).to.not.toHaveBeenCalled(); + // try a short password + await user.type(screen.getByLabelText("Password"), "foo"); + fireEvent.submit(btnSubmit); + expect(loginSpy).to.not.toHaveBeenCalled(); + unmount(); + }); + + it("calls login", async () => { + const PASSWORD = "Haveno!2022"; + const user = userEvent.setup(); + const { unmount } = render( + + + + ); + expect(loginSpy).to.not.toHaveBeenCalled(); + await user.type(screen.getByLabelText("Password"), PASSWORD); + fireEvent.submit(screen.getByRole("button", { name: "Login" })); + expect(loginSpy).to.toHaveBeenCalledTimes(1); + unmount(); + }); + + // TODO: update behavior to redirect to the Market page + it("navigates to Payment Accounts page after successful login", async () => { + const PASSWORD = "Haveno!2022"; + loginSpy.mockImplementation(({ password }, { onSuccess, onError }) => { + if (password === PASSWORD) { + onSuccess(); + } else { + onError({ message: "Invalid password" }); + } + }); + const user = userEvent.setup(); + const { unmount } = render( + + + + ); + expect(navSpy).to.toHaveBeenCalledTimes(0); + await user.type(screen.getByLabelText("Password"), PASSWORD); + fireEvent.submit(screen.getByRole("button", { name: "Login" })); + expect(navSpy).to.toHaveBeenCalledTimes(1); + expect(navSpy).toHaveBeenCalledWith(ROUTES.AccountPaymentAccounts, { + replace: true, + }); + unmount(); + }); +}); diff --git a/packages/renderer/src/pages/Login/Login.tsx b/packages/renderer/src/pages/Login/Login.tsx index bb52e4c..0a6cba2 100644 --- a/packages/renderer/src/pages/Login/Login.tsx +++ b/packages/renderer/src/pages/Login/Login.tsx @@ -19,12 +19,12 @@ import { joiResolver, useForm } from "@mantine/form"; import { useNavigate } from "react-router-dom"; import { Container, Group, Space, Stack } from "@mantine/core"; import { showNotification } from "@mantine/notifications"; +import { useLogin } from "@hooks/session/useLogin"; import { CenteredLayout } from "@templates/CenteredLayout"; import { BodyText, Heading } from "@atoms/Typography"; -import { ROUTES } from "@constants/routes"; -import { useLogin } from "@hooks/session/useLogin"; import { Button } from "@atoms/Buttons"; import { TextInput } from "@atoms/TextInput"; +import { ROUTES } from "@constants/routes"; import { CONTENT_MAX_WIDTH } from "./_constants"; export function Login() { @@ -67,6 +67,7 @@ export function Login() { renders without exploding 1`] = ` + +
+
+ Haveno +
+
+
+
+
+
+

+ Login to Haveno +

+
+
+ All your data is stored locally on your machine. Haveno uses solely a password. +
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+
+
+ +`; diff --git a/packages/renderer/src/pages/Onboarding/ConnectingMonero.stories.tsx b/packages/renderer/src/pages/Onboarding/ConnectingMonero.stories.tsx new file mode 100644 index 0000000..0e0a773 --- /dev/null +++ b/packages/renderer/src/pages/Onboarding/ConnectingMonero.stories.tsx @@ -0,0 +1,30 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { ConnectingMonero } from "."; + +export default { + title: "pages/Onboarding/Connecting Monero", + component: ConnectingMonero, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/pages/Onboarding/CreateAccount.stories.tsx b/packages/renderer/src/pages/Onboarding/CreateAccount.stories.tsx new file mode 100644 index 0000000..133a5f9 --- /dev/null +++ b/packages/renderer/src/pages/Onboarding/CreateAccount.stories.tsx @@ -0,0 +1,30 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { CreateAccount } from "."; + +export default { + title: "pages/Onboarding/Create Account", + component: CreateAccount, +} as ComponentMeta; + +const Template: ComponentStory = () => { + return ; +}; + +export const Default = Template.bind({}); +Default.args = {}; diff --git a/packages/renderer/src/pages/Onboarding/Welcome.stories.tsx b/packages/renderer/src/pages/Onboarding/Welcome.stories.tsx index 5588781..d8681bb 100644 --- a/packages/renderer/src/pages/Onboarding/Welcome.stories.tsx +++ b/packages/renderer/src/pages/Onboarding/Welcome.stories.tsx @@ -15,7 +15,6 @@ // ============================================================================= import type { ComponentStory, ComponentMeta } from "@storybook/react"; -import { AppProviders } from "@atoms/AppProviders"; import { Welcome } from "."; export default { @@ -24,11 +23,7 @@ export default { } as ComponentMeta; const Template: ComponentStory = () => { - return ( - - - - ); + return ; }; export const Default = Template.bind({}); diff --git a/packages/renderer/src/pages/Onboarding/index.ts b/packages/renderer/src/pages/Onboarding/index.ts index 2a08eab..3e2cc8b 100644 --- a/packages/renderer/src/pages/Onboarding/index.ts +++ b/packages/renderer/src/pages/Onboarding/index.ts @@ -14,5 +14,6 @@ // limitations under the License. // ============================================================================= -export * from "./Welcome"; +export * from "./ConnectingMonero"; export * from "./CreateAccount"; +export * from "./Welcome"; diff --git a/packages/renderer/tsconfig.json b/packages/renderer/tsconfig.json index aac9c76..d93551d 100644 --- a/packages/renderer/tsconfig.json +++ b/packages/renderer/tsconfig.json @@ -32,7 +32,8 @@ "src/**/*.tsx", "types/**/*.d.ts", "../../types/**/*.d.ts", - "../preload/contracts.d.ts" + "../preload/contracts.d.ts", + "../../tests/setup-tests.ts" ], "exclude": ["**/*.spec.ts", "**/*.test.ts"] } diff --git a/packages/renderer/vitest.coverage.config.js b/packages/renderer/vitest.config.ts similarity index 84% rename from packages/renderer/vitest.coverage.config.js rename to packages/renderer/vitest.config.ts index d842fc1..2a117aa 100644 --- a/packages/renderer/vitest.coverage.config.js +++ b/packages/renderer/vitest.config.ts @@ -14,19 +14,24 @@ // limitations under the License. // ============================================================================= +import { mergeConfig } from "vite"; +import viteConfig from "./vite.config"; + /** * Config for global end-to-end tests * placed in project root tests folder * @type {import('vite').UserConfig} * @see https://vitest.dev/config/ */ -const config = { +const config = mergeConfig(viteConfig, { test: { + setupFiles: ["../../tests/setup-tests.ts"], + environment: "jsdom", include: ["./src/**/*.{test,spec}.{ts,tsx}"], coverage: { reporter: ["html"], }, }, -}; +}); export default config; diff --git a/tests/setup-tests.ts b/tests/setup-tests.ts new file mode 100644 index 0000000..cb89ec3 --- /dev/null +++ b/tests/setup-tests.ts @@ -0,0 +1,30 @@ +// ============================================================================= +// Copyright 2022 Haveno +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-explicit-any */ +import { expect } from "vitest"; +import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers"; +import matchers from "@testing-library/jest-dom/matchers"; + +declare global { + namespace Vi { + interface JestAssertion + extends jest.Matchers, + TestingLibraryMatchers {} + } +} + +expect.extend(matchers); diff --git a/yarn.lock b/yarn.lock index 988ff73..ec875e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1074,7 +1074,7 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== @@ -2291,10 +2291,10 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-vite@^0.1.29": - version "0.1.29" - resolved "https://registry.yarnpkg.com/@storybook/builder-vite/-/builder-vite-0.1.29.tgz#ca7dff079968605e323b365d72c28903e4a072eb" - integrity sha512-WMPY1Pd5Da3BdXDfgFNhIWq09i7oxMT06nxS909VKOOKHZvckmfQpS8iKJLYp730t4t7S3+MtHf/t2+Kr7Cxew== +"@storybook/builder-vite@^0.1.34": + version "0.1.34" + resolved "https://registry.yarnpkg.com/@storybook/builder-vite/-/builder-vite-0.1.34.tgz#9d65a1b430967cae688ec7cb586056e53f1afef2" + integrity sha512-a40uMOAu66W4zX9+k+wGhhfsyJWSXb7i4Ve5rS9I1fZ/d7xvsjhgQzhHZtLqESU1A/CO1gyvexbcw44efMPuGQ== dependencies: "@joshwooding/vite-plugin-react-docgen-typescript" "0.0.4" "@mdx-js/mdx" "^1.6.22" @@ -3018,6 +3018,21 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/jest-dom@^5.16.4": + version "5.16.4" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" + integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^5.0.0" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react@^12": version "12.1.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -3032,6 +3047,11 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.1.1.tgz#e1ff6118896e4b22af31e5ea2f9da956adde23d8" integrity sha512-XrjH/iEUqNl9lF2HX9YhPNV7Amntkcnpw0Bo1KkRzowNDcgSN9i0nm4Q8Oi5wupgdfPaJNMAWa61A+voD6Kmwg== +"@testing-library/user-event@^14.2.0": + version "14.2.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.2.0.tgz#8293560f8f80a00383d6c755ec3e0b918acb1683" + integrity sha512-+hIlG4nJS6ivZrKnOP7OGsDu9Fxmryj9vCl8x0ZINtTJcCHs2zLsYif5GzuRiBF2ck5GZG2aQr7Msg+EHlnYVQ== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -3173,6 +3193,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.1.tgz#2c8b6dc6ff85c33bcd07d0b62cb3d19ddfdb3ab9" + integrity sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -3360,6 +3388,13 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== +"@types/testing-library__jest-dom@^5.9.1": + version "5.14.3" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz#ee6c7ffe9f8595882ee7bda8af33ae7b8789ef17" + integrity sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw== + dependencies: + "@types/jest" "*" + "@types/uglify-js@*": version "3.13.2" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.2.tgz#1044c1713fb81cb1ceef29ad8a9ee1ce08d690ef" @@ -3741,6 +3776,11 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" +abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -3749,12 +3789,20 @@ accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.2.0: +acorn-walk@^7.1.1, acorn-walk@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== @@ -3769,7 +3817,7 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.4.1: +acorn@^7.1.1, acorn@^7.4.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -4562,6 +4610,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -4952,6 +5005,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -5686,11 +5747,37 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + csstype@^2.5.7: version "2.6.20" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" @@ -5711,6 +5798,15 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== +data-urls@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + dayjs@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805" @@ -5764,6 +5860,11 @@ decamelize@^1.1.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -5898,6 +5999,11 @@ detect-port@^1.3.0: address "^1.0.1" debug "^2.6.0" +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5978,6 +6084,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + dom-accessibility-api@^0.5.9: version "0.5.13" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz#102ee5f25eacce09bdf1cfa5a298f86da473be4b" @@ -6022,6 +6133,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -8035,6 +8153,13 @@ hosted-git-info@^4.0.1, hosted-git-info@^4.0.2: dependencies: lru-cache "^6.0.0" +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + html-entities@^2.1.0: version "2.3.3" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" @@ -8173,7 +8298,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -8655,6 +8780,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -8865,6 +8995,21 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" @@ -8886,6 +9031,16 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-mock@^27.0.6: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -8974,6 +9129,39 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsdom@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" + integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -10104,6 +10292,11 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -10458,7 +10651,7 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@^6.0.0: +parse5@6.0.1, parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -10809,7 +11002,7 @@ pretty-error@^2.1.1: lodash "^4.17.20" renderkid "^2.0.4" -pretty-format@^27.0.2: +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -10942,6 +11135,11 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -10989,7 +11187,7 @@ punycode@^1.2.4: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -11814,6 +12012,13 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" @@ -12157,6 +12362,14 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + source-map-support@^0.5.16, source-map-support@^0.5.19, source-map-support@~0.5.12, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -12514,6 +12727,11 @@ svg-parser@^2.0.2: resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + symbol.prototype.description@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/symbol.prototype.description/-/symbol.prototype.description-1.0.5.tgz#d30e01263b6020fbbd2d2884a6276ce4d49ab568" @@ -12545,6 +12763,11 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== +tabler-icons-react@^1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/tabler-icons-react/-/tabler-icons-react-1.48.0.tgz#2b3251d4b9effa1e78baf4cb05fe7cf79449f116" + integrity sha512-dlcAIGYIB7+fsU1tj8HuK5aN57g3Q5KD8GMAxpBR9E62yFhjn8fbwD2M2X18E66v5TYakUhu6tfUxM+/jvI6Kg== + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -12799,6 +13022,22 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -13137,7 +13376,7 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0: unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" -universalify@^0.1.0: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== @@ -13463,6 +13702,20 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + walker@^1.0.7, walker@~1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -13603,11 +13856,39 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -13744,6 +14025,11 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + xmlbuilder@>=11.0.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -13754,6 +14040,11 @@ xmlbuilder@^9.0.7: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"