Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

feat: add InputAddress component #2614

Merged
merged 41 commits into from Aug 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f0a2ddc
feat: add input address component
brenopolanski Aug 5, 2020
f604b39
chore: add viewBox for qrcode.svg
brenopolanski Aug 5, 2020
45e5bee
wip
brenopolanski Aug 6, 2020
87c4e3f
chore: optimize 1 SVG(s)
boldninja Aug 6, 2020
4b7cc1c
feat: add debounce hook
brenopolanski Aug 6, 2020
082f0ee
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 6, 2020
420f280
Merge branch '3.0-react' into feat/input-address-component
brenopolanski Aug 6, 2020
05fff3d
style: resolve style guide violations
brenopolanski Aug 6, 2020
cd8e648
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 6, 2020
c567749
style: resolve style guide violations
faustbrian Aug 6, 2020
a983f0a
feat: add function to validate address
brenopolanski Aug 6, 2020
d7505e2
Merge branch 'feat/input-address-component' of github.com:ArkEcosyste…
brenopolanski Aug 6, 2020
fd557cb
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 6, 2020
0907c79
feat: set error message
brenopolanski Aug 6, 2020
0152a54
Merge branch 'feat/input-address-component' of github.com:ArkEcosyste…
brenopolanski Aug 6, 2020
749983f
feat: clear the error if it is a valid address
brenopolanski Aug 6, 2020
b8967dc
chore: add i18n translations
brenopolanski Aug 6, 2020
ff53181
wip
brenopolanski Aug 6, 2020
46bf7fa
feat: add function to get valid address
brenopolanski Aug 6, 2020
3f00210
refactor: use custom validation rule
brenopolanski Aug 6, 2020
d1691c3
wip
brenopolanski Aug 6, 2020
a987422
chore: update story and some tweaks
brenopolanski Aug 7, 2020
c5de976
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 7, 2020
6f3872b
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 7, 2020
2025877
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 7, 2020
6e63dad
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 7, 2020
8079b7e
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 7, 2020
c8970c5
Merge branch '3.0-react' into feat/input-address-component
brenopolanski Aug 7, 2020
f5b2e13
test: fix import wallet tests
brenopolanski Aug 7, 2020
29fbe47
test: add tests to the InputAddress component
brenopolanski Aug 7, 2020
298ad46
Merge branch '3.0-react' into feat/input-address-component
brenopolanski Aug 7, 2020
25c685e
chore: update InputAddress tests
brenopolanski Aug 7, 2020
49324a0
refactor: use register prop instead of formcontext
luciorubeens Aug 8, 2020
212de29
Merge remote branch '3.0-react' into feat/input-address-component
luciorubeens Aug 8, 2020
2df47ac
Merge branch '3.0-react' into feat/input-address-component
faustbrian Aug 8, 2020
7688901
fix: validation field name
luciorubeens Aug 8, 2020
0ba3480
chore: update tests and some tweaks
brenopolanski Aug 8, 2020
beebd69
style: resolve style guide violations
brenopolanski Aug 8, 2020
3f4931a
misc: force status updates
brenopolanski Aug 8, 2020
0890dc7
Merge branch 'feat/input-address-component' of github.com:ArkEcosyste…
brenopolanski Aug 8, 2020
07dc144
wip
brenopolanski Aug 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/assets/svg/qrcode.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 20 additions & 5 deletions src/app/components/Input/Input.stories.tsx
@@ -1,6 +1,13 @@
import { ARK } from "@arkecosystem/platform-sdk-ark";
import { Environment } from "@arkecosystem/platform-sdk-profiles";
import { EnvironmentProvider } from "app/contexts";
import { httpClient } from "app/services";
import React from "react";
import { StubStorage } from "tests/mocks";

import { Input, InputCounter, InputPassword } from "./index";
import { Input, InputAddress, InputCounter, InputPassword } from "./index";

const env = new Environment({ coins: { ARK }, httpClient, storage: new StubStorage() });

export default {
title: "App / Components / Input",
Expand All @@ -14,14 +21,22 @@ export const Default = () => (
</div>
);

export const Password = () => (
<div className="max-w-xs">
<InputPassword defaultValue="secret" />
</div>
export const Address = () => (
<EnvironmentProvider env={env}>
<div className="max-w-xs">
<InputAddress coin="ARK" network="devnet" defaultValue="DT11QcbKqTXJ59jrUTpcMyggTcwmyFYRTM" />
</div>
</EnvironmentProvider>
);

export const Counter = () => (
<div className="max-w-xs">
<InputCounter defaultValue="Hello" maxLength={255} />
</div>
);

export const Password = () => (
<div className="max-w-xs">
<InputPassword defaultValue="secret" />
</div>
);
82 changes: 82 additions & 0 deletions src/app/components/Input/InputAddress.test.tsx
@@ -0,0 +1,82 @@
import { act, renderHook } from "@testing-library/react-hooks";
import { EnvironmentProvider } from "app/contexts";
import React from "react";
import { useForm } from "react-hook-form";
import { env, fireEvent, render } from "utils/testing-library";

import { InputAddress, InputAddressProps } from "./InputAddress";

describe("InputAddress", () => {
const TestInputAddress = (props: InputAddressProps) => (
<EnvironmentProvider env={env}>
<InputAddress name="address" {...props} />
</EnvironmentProvider>
);

it("should render", () => {
const { getByTestId, asFragment } = render(<TestInputAddress coin="ARK" network="devnet" />);
expect(getByTestId("InputAddress__input")).toBeInTheDocument();
expect(getByTestId("InputAddress__qr-button")).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
});

it("should emit event whe clicking on qr button", () => {
const onQRButtonClick = jest.fn();
const { getByTestId } = render(
<TestInputAddress coin="ARK" network="devnet" onQRCodeClick={onQRButtonClick} />,
);
fireEvent.click(getByTestId("InputAddress__qr-button"));
expect(onQRButtonClick).toHaveBeenCalled();
});

it("should validate a wrong address", async () => {
const { result, waitForNextUpdate } = renderHook(() => useForm({ mode: "onChange" }));
const { register, errors } = result.current;

const { getByTestId } = render(<TestInputAddress coin="ARK" network="devnet" registerRef={register} />);

act(() => {
fireEvent.input(getByTestId("InputAddress__input"), { target: { value: "Abc" } });
});

await waitForNextUpdate();
expect(errors.address?.message).toBe("The address is not valid");
});

it("should validate a valid address and emit event", async () => {
const onValidAddress = jest.fn();
const { result, waitForNextUpdate } = renderHook(() => useForm({ mode: "onChange" }));
const { register, errors } = result.current;
const validAddress = "DT11QcbKqTXJ59jrUTpcMyggTcwmyFYRTM";

const { getByTestId } = render(
<TestInputAddress coin="ARK" network="devnet" registerRef={register} onValidAddress={onValidAddress} />,
);

act(() => {
fireEvent.input(getByTestId("InputAddress__input"), {
target: { value: validAddress },
});
});

await waitForNextUpdate();
expect(errors.address?.message).toBe(undefined);
expect(onValidAddress).toHaveBeenCalledWith(validAddress);
});

it("should validate with additional rules", async () => {
const { result, waitForNextUpdate } = renderHook(() => useForm({ mode: "onChange" }));
const { register, errors } = result.current;

const { getByTestId } = render(
<TestInputAddress coin="ARK" network="devnet" registerRef={register} additionalRules={{ minLength: 10 }} />,
);

act(() => {
fireEvent.input(getByTestId("InputAddress__input"), { target: { value: "Abc" } });
});

await waitForNextUpdate();
expect(errors.address?.type).toBe("minLength");
});
});
70 changes: 70 additions & 0 deletions src/app/components/Input/InputAddress.tsx
@@ -0,0 +1,70 @@
import { Icon } from "app/components/Icon";
import { useEnvironmentContext } from "app/contexts";
import React from "react";
import { ValidationOptions } from "react-hook-form";
import { useTranslation } from "react-i18next";

import { Input } from "./Input";
import { InputAddonEnd, InputGroup } from "./InputGroup";

export type InputAddressProps = {
coin: string;
network: string;
registerRef?: (options: ValidationOptions) => (ref: HTMLInputElement | null) => void;
additionalRules?: ValidationOptions;
onValidAddress?: (address: string) => void;
onQRCodeClick?: () => void;
} & React.InputHTMLAttributes<any>;

export const InputAddress = ({
coin,
network,
registerRef,
additionalRules,
onValidAddress,
onQRCodeClick,
...props
}: InputAddressProps) => {
const { t } = useTranslation();
const { env } = useEnvironmentContext();

const validateAddress = async (address: string) => {
const isValidAddress = await env.dataValidator().address(coin, network, address);

if (isValidAddress) {
onValidAddress?.(address);
return true;
}

return t("COMMON.INPUT_ADDRESS.VALIDATION.NOT_VALID");
};

return (
<InputGroup className="max-w-20">
<Input
ref={registerRef?.({
...additionalRules,
validate: validateAddress,
})}
type="text"
className="pr-12"
data-testid="InputAddress__input"
{...props}
/>
<InputAddonEnd className="my-px mr-4">
<button
data-testid="InputAddress__qr-button"
type="button"
className="flex items-center justify-center w-full h-full text-2xl focus:outline-none bg-theme-background text-theme-primary-400"
onClick={onQRCodeClick}
>
<Icon name="Qrcode" width={20} height={20} />
</button>
</InputAddonEnd>
</InputGroup>
);
};

InputAddress.defaultProps = {
additionalRules: {},
};
35 changes: 35 additions & 0 deletions src/app/components/Input/__snapshots__/InputAddress.test.tsx.snap
@@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`InputAddress should render 1`] = `
<DocumentFragment>
<div
class="sc-AxgMl cZbWSE max-w-20"
>
<input
class="sc-AxirZ cNbglv overflow-hidden w-full bg-theme-background appearance-none rounded border border-theme-neutral-300 text-theme-neutral-900 transition-colors duration-200 px-4 py-3 pr-12"
data-testid="InputAddress__input"
name="address"
type="text"
/>
<div
class="sc-AxiKw sc-AxhUy QmuzF my-px mr-4"
>
<button
class="flex items-center justify-center w-full h-full text-2xl focus:outline-none bg-theme-background text-theme-primary-400"
data-testid="InputAddress__qr-button"
type="button"
>
<div
class="sc-AxjAm buCJuk"
height="20"
width="20"
>
<svg>
qrcode.svg
</svg>
</div>
</button>
</div>
</div>
</DocumentFragment>
`;
14 changes: 7 additions & 7 deletions src/app/components/Input/__snapshots__/InputGroup.test.tsx.snap
Expand Up @@ -3,10 +3,10 @@
exports[`InputGroup should render with both elements 1`] = `
<DocumentFragment>
<div
class="sc-AxhUy fNzmXy max-w-xs"
class="sc-AxgMl cZbWSE max-w-xs"
>
<div
class="sc-AxirZ sc-AxiKw cUKUhn"
class="sc-AxiKw sc-AxhCb kpqYkD"
>
<span
data-testid="start"
Expand All @@ -15,7 +15,7 @@ exports[`InputGroup should render with both elements 1`] = `
</span>
</div>
<div
class="sc-AxirZ sc-AxhCb dJFQuU"
class="sc-AxiKw sc-AxhUy QmuzF"
>
<span
data-testid="end"
Expand All @@ -36,10 +36,10 @@ exports[`InputGroup should render with both elements 1`] = `
exports[`InputGroup should render with element on the left 1`] = `
<DocumentFragment>
<div
class="sc-AxhUy fNzmXy max-w-xs"
class="sc-AxgMl cZbWSE max-w-xs"
>
<div
class="sc-AxirZ sc-AxiKw cUKUhn"
class="sc-AxiKw sc-AxhCb kpqYkD"
>
<span
data-testid="start"
Expand All @@ -60,10 +60,10 @@ exports[`InputGroup should render with element on the left 1`] = `
exports[`InputGroup should render with element on the right 1`] = `
<DocumentFragment>
<div
class="sc-AxhUy fNzmXy max-w-xs"
class="sc-AxgMl cZbWSE max-w-xs"
>
<div
class="sc-AxirZ sc-AxhCb dJFQuU"
class="sc-AxiKw sc-AxhUy QmuzF"
>
<span
data-testid="end"
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/Input/index.tsx
@@ -1,6 +1,7 @@
export * from "./Input";
export * from "./InputAddress";
export * from "./InputCounter";
export * from "./InputCurrency";
export * from "./InputGroup";
export * from "./InputPassword";
export * from "./InputCounter";
export * from "./InputRange";
export * from "./InputCurrency";
6 changes: 6 additions & 0 deletions src/app/i18n/common/i18n.ts
Expand Up @@ -193,4 +193,10 @@ export const translations: { [key: string]: any } = {
PERIODS: {
DAILY: "Daily",
},

INPUT_ADDRESS: {
VALIDATION: {
NOT_VALID: "The address is not valid",
},
},
};
2 changes: 1 addition & 1 deletion src/domains/dashboard/pages/Dashboard/Dashboard.test.tsx
Expand Up @@ -27,7 +27,7 @@ beforeEach(() => {
.persist();
});

afterEach(() => nock.cleanAll());
// afterEach(() => nock.cleanAll());

describe("Dashboard", () => {
it("should render", async () => {
Expand Down
Expand Up @@ -25,7 +25,7 @@ exports[`CustomPeers should render a modal 1`] = `
class="p-1"
>
<div
class="sc-AxmLO fZFkuY"
class="sc-AxhUy iFvBPK"
height="12"
width="12"
>
Expand Down Expand Up @@ -73,11 +73,11 @@ exports[`CustomPeers should render a modal 1`] = `
id="CustomPeers__network-label"
/>
<div
class="sc-Axmtr jzhOwR"
class="sc-AxmLO kuiknc"
data-testid="SelectNetworkInput"
>
<div
class="sc-AxhUy sc-AxgMl kAPRWA px-4"
class="sc-AxgMl sc-AxheI hUEXQW px-4"
>
<div
class="sc-fznyAO crOapn border-theme-neutral-light text-theme-neutral"
Expand Down