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

Commit

Permalink
feat: add InputAddress component (#2614)
Browse files Browse the repository at this point in the history
  • Loading branch information
brenopolanski committed Aug 9, 2020
1 parent 83d63a9 commit f912097
Show file tree
Hide file tree
Showing 16 changed files with 525 additions and 1,420 deletions.
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

0 comments on commit f912097

Please sign in to comment.