diff --git a/.gitignore b/.gitignore index f3bd5a4..be2da33 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,10 @@ thumbs.db .idea/httpRequests /.idea/csv-plugin.xml +# Dev environment variables .env + +# Coverage reports +packages/main/coverage/ +packages/preload/coverage/ +packages/renderer/coverage/ diff --git a/package.json b/package.json index f9675fa..ea85e88 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,10 @@ "test:preload": "vitest run -r packages/preload --passWithNoTests", "test:renderer": "vitest run -r packages/renderer --passWithNoTests", "test:renderer:watch": "vitest -r packages/renderer", + "coverage:main": "vitest run -r packages/main --coverage", + "coverage:preload": "vitest run -r packages/preload --coverage", + "coverage:renderer": "vitest run -r packages/renderer --coverage", + "coverage": "npm run coverage:main && npm run coverage:preload && npm run coverage:renderer", "watch": "node scripts/watch.js", "format": "prettier --write .", "lint": "eslint .", @@ -84,7 +88,7 @@ "dayjs": "^1.11.0", "electron-store": "^8.0.1", "electron-updater": "4.6.5", - "haveno-ts": "0.0.4", + "haveno-ts": "0.0.5", "joi": "^17.6.0", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.21", diff --git a/packages/main/vitest.coverage.config.js b/packages/main/vitest.coverage.config.js new file mode 100644 index 0000000..d842fc1 --- /dev/null +++ b/packages/main/vitest.coverage.config.js @@ -0,0 +1,32 @@ +// ============================================================================= +// 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. +// ============================================================================= + +/** + * Config for global end-to-end tests + * placed in project root tests folder + * @type {import('vite').UserConfig} + * @see https://vitest.dev/config/ + */ +const config = { + test: { + include: ["./src/**/*.{test,spec}.{ts,tsx}"], + coverage: { + reporter: ["html"], + }, + }, +}; + +export default config; diff --git a/packages/preload/vitest.coverage.config.js b/packages/preload/vitest.coverage.config.js new file mode 100644 index 0000000..d842fc1 --- /dev/null +++ b/packages/preload/vitest.coverage.config.js @@ -0,0 +1,32 @@ +// ============================================================================= +// 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. +// ============================================================================= + +/** + * Config for global end-to-end tests + * placed in project root tests folder + * @type {import('vite').UserConfig} + * @see https://vitest.dev/config/ + */ +const config = { + test: { + include: ["./src/**/*.{test,spec}.{ts,tsx}"], + coverage: { + reporter: ["html"], + }, + }, +}; + +export default config; diff --git a/packages/renderer/src/components/atoms/Currency/Currency.stories.tsx b/packages/renderer/src/components/atoms/Currency/Currency.stories.tsx new file mode 100644 index 0000000..1ad9ac3 --- /dev/null +++ b/packages/renderer/src/components/atoms/Currency/Currency.stories.tsx @@ -0,0 +1,41 @@ +// ============================================================================= +// 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 { BodyText } from "@atoms/Typography"; +import { Stack } from "@mantine/core"; +import type { ComponentStory, ComponentMeta } from "@storybook/react"; +import { Currency } from "."; + +export default { + title: "atoms/Currency", + component: Currency, +} as ComponentMeta; + +const Template: ComponentStory = (args) => { + return ( + + + + + + ); +}; + +export const Default = Template.bind({}); +Default.args = { + currencyCode: "EUR", + value: 400000.12, +}; diff --git a/packages/renderer/src/components/atoms/Currency/Currency.test.tsx b/packages/renderer/src/components/atoms/Currency/Currency.test.tsx new file mode 100644 index 0000000..12a6c81 --- /dev/null +++ b/packages/renderer/src/components/atoms/Currency/Currency.test.tsx @@ -0,0 +1,49 @@ +// ============================================================================= +// 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 { Currency } from "."; + +describe("atoms::Currency", () => { + it("renders without exploding", () => { + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders currency value", () => { + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders decimal value", () => { + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/packages/renderer/src/components/atoms/Currency/Currency.tsx b/packages/renderer/src/components/atoms/Currency/Currency.tsx new file mode 100644 index 0000000..eb7e89f --- /dev/null +++ b/packages/renderer/src/components/atoms/Currency/Currency.tsx @@ -0,0 +1,50 @@ +// ============================================================================= +// 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 { useMemo } from "react"; +import { useIntl } from "react-intl"; + +interface CurrencyProps { + currencyCode?: string; + value: number; +} + +export function Currency(props: CurrencyProps) { + const { currencyCode, value } = props; + const intl = useIntl(); + + const formattedNumber = useMemo( + () => + intl + .formatNumber(value, { + ...(currencyCode + ? { + currency: currencyCode, + currencyDisplay: "code", + style: "currency", + } + : { + style: "decimal", + minimumFractionDigits: 2, + maximumFractionDigits: 12, + }), + }) + .replace(/XXX\s/, ""), + [currencyCode, value] + ); + + return <>{formattedNumber}; +} diff --git a/packages/renderer/src/components/atoms/Currency/__snapshots__/Currency.test.tsx.snap b/packages/renderer/src/components/atoms/Currency/__snapshots__/Currency.test.tsx.snap new file mode 100644 index 0000000..b60b8a9 --- /dev/null +++ b/packages/renderer/src/components/atoms/Currency/__snapshots__/Currency.test.tsx.snap @@ -0,0 +1,19 @@ +// Vitest Snapshot v1 + +exports[`atoms::Currency > renders currency value 1`] = ` + + USDĀ 10.00 + +`; + +exports[`atoms::Currency > renders decimal value 1`] = ` + + 10.12345678 + +`; + +exports[`atoms::Currency > renders without exploding 1`] = ` + + 10.00 + +`; diff --git a/packages/renderer/src/components/atoms/Currency/index.ts b/packages/renderer/src/components/atoms/Currency/index.ts new file mode 100644 index 0000000..03c9f6a --- /dev/null +++ b/packages/renderer/src/components/atoms/Currency/index.ts @@ -0,0 +1,17 @@ +// ============================================================================= +// 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. +// ============================================================================= + +export * from "./Currency"; diff --git a/packages/renderer/src/components/atoms/Typography/Text.tsx b/packages/renderer/src/components/atoms/Typography/Text.tsx index 966d6e3..bfac6bf 100644 --- a/packages/renderer/src/components/atoms/Typography/Text.tsx +++ b/packages/renderer/src/components/atoms/Typography/Text.tsx @@ -14,14 +14,14 @@ // limitations under the License. // ============================================================================= -import type { ReactText } from "react"; +import type { ReactNode, ReactText } from "react"; import { FormattedMessage } from "react-intl"; import type { TextProps as MTextProps } from "@mantine/core"; import { Text as MText, createStyles } from "@mantine/core"; import type { LangKeys } from "@constants/lang"; type TextProps = MTextProps & { - children: ReactText; + children: ReactText | ReactNode; stringId?: LangKeys; }; @@ -44,7 +44,10 @@ export function BodyText(props: BodyTextProps) { size={size} > {stringId ? ( - + ) : ( children )} @@ -59,7 +62,10 @@ export function InfoText(props: TextProps) { return ( {stringId ? ( - + ) : ( children )} @@ -74,7 +80,10 @@ export function LabelText(props: TextProps<"label">) { return ( {stringId ? ( - + ) : ( children )} diff --git a/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.test.tsx b/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.test.tsx index 84382b5..401fb05 100644 --- a/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.test.tsx +++ b/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.test.tsx @@ -14,13 +14,45 @@ // limitations under the License. // ============================================================================= -import { describe, expect, it } from "vitest"; -import { render } from "@testing-library/react"; +import { beforeAll, describe, expect, it, vi } from "vitest"; +import { render, waitForElementToBeRemoved } from "@testing-library/react"; import { AppProviders } from "@atoms/AppProviders"; import { WalletBalance } from "."; describe("molecules::WalletBalance", () => { - it("renders without exploding", () => { + beforeAll(() => { + vi.mock("@hooks/haveno/useHavenoClient", () => ({ + useHavenoClient: () => ({ + getBalances: async () => { + return { + getLockedBalance: () => 12, + getReservedTradeBalance: () => 14, + getBalance: () => 15, + }; + }, + }), + })); + + vi.mock("@hooks/storage/useAccountInfo", () => ({ + useAccountInfo: () => ({ + isLoading: false, + isSuccess: true, + data: { + primaryFiat: "USD", + }, + }), + })); + + vi.mock("@hooks/haveno/usePrice", () => ({ + usePrice: () => ({ + isLoading: false, + isSuccess: true, + data: 300, + }), + })); + }); + + it("renders loading state", () => { const { asFragment } = render( @@ -28,4 +60,16 @@ describe("molecules::WalletBalance", () => { ); expect(asFragment()).toMatchSnapshot(); }); + + it("renders after loading data", async () => { + const { asFragment, queryByText } = render( + + + + ); + if (queryByText("Loading...")) { + await waitForElementToBeRemoved(() => queryByText("Loading...")); + } + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.tsx b/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.tsx index b60f041..2a7ed13 100644 --- a/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.tsx +++ b/packages/renderer/src/components/molecules/WalletBalance/WalletBalance.tsx @@ -26,19 +26,37 @@ import { import { ReactComponent as XMRLogo } from "@assets/xmr-logo-1.svg"; import { ReactComponent as ArrowDown } from "@assets/arrow-down.svg"; import { useBalances } from "@hooks/haveno/useBalances"; +import { useAccountInfo } from "@hooks/storage/useAccountInfo"; +import { usePrice } from "@hooks/haveno/usePrice"; +import { Currency } from "@atoms/Currency"; +import { BodyText } from "@atoms/Typography"; export function WalletBalance() { const [isOpen, setOpen] = useState(false); const { classes } = useStyles({ isOpen }); - const { data: availableBalances } = useBalances(); - const Total = useMemo(() => { + const { data: availableBalances, isLoading: isLoadingBalance } = + useBalances(); + const { data: accountInfo, isLoading: isLoadingAccountInfo } = + useAccountInfo(); + const { data: price } = usePrice(accountInfo?.primaryFiat); + + const totalBalance = useMemo(() => { return ( Number(availableBalances?.getLockedBalance() || 0) + Number(availableBalances?.getReservedTradeBalance() || 0) - ).toString(); + ); }, [availableBalances]); + + const fiatBalance = useMemo(() => { + if (!totalBalance || !price || !accountInfo?.primaryFiat) { + return 0; + } + return totalBalance * price; + }, [totalBalance, price, accountInfo]); + return ( setOpen(!isOpen)} > @@ -49,35 +67,60 @@ export function WalletBalance() { Available Balance - - - - {availableBalances?.getBalance() ?? 0} - - - - (EUR 2441,02) - - + {isLoadingAccountInfo || isLoadingBalance ? ( - - Total - {Total} - - - Reserved - - {availableBalances?.getReservedTradeBalance() ?? 0} - - - - Locked - - {availableBalances?.getLockedBalance() ?? 0} - - + Loading... - + ) : ( + <> + + + + + + + + + ( + + ) + + + + + + Total + + + + + + Reserved + + + + + + Locked + + + + + + + + )} ); diff --git a/packages/renderer/src/components/molecules/WalletBalance/__snapshots__/WalletBalance.test.tsx.snap b/packages/renderer/src/components/molecules/WalletBalance/__snapshots__/WalletBalance.test.tsx.snap index ff32e86..e362148 100644 --- a/packages/renderer/src/components/molecules/WalletBalance/__snapshots__/WalletBalance.test.tsx.snap +++ b/packages/renderer/src/components/molecules/WalletBalance/__snapshots__/WalletBalance.test.tsx.snap @@ -1,8 +1,9 @@ // Vitest Snapshot v1 -exports[`molecules::WalletBalance > renders without exploding 1`] = ` +exports[`molecules::WalletBalance > renders after loading data 1`] = ` `; + +exports[`molecules::WalletBalance > renders loading state 1`] = ` + + + +`; diff --git a/packages/renderer/src/components/organisms/Sidebar/__snapshots__/Sidebar.test.tsx.snap b/packages/renderer/src/components/organisms/Sidebar/__snapshots__/Sidebar.test.tsx.snap index 041e354..81be9ad 100644 --- a/packages/renderer/src/components/organisms/Sidebar/__snapshots__/Sidebar.test.tsx.snap +++ b/packages/renderer/src/components/organisms/Sidebar/__snapshots__/Sidebar.test.tsx.snap @@ -202,6 +202,7 @@ exports[`molecules::Sidebar > renders without exploding 1`] = ` class="mantine-865t33" >