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

Commit

Permalink
feat: create SelectRecipient form input (#2480)
Browse files Browse the repository at this point in the history
  • Loading branch information
goga-m committed Jul 15, 2020
1 parent 0e88c10 commit 96c113e
Show file tree
Hide file tree
Showing 5 changed files with 443 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { contacts } from "domains/contact/data";
import React from "react";

import { SelectRecipient } from "./SelectRecipient";

export default { title: "Domains / Profile / Components / Select Recipient" };

export const Default = () => {
return (
<div className="max-w-lg space-y-8">
<div>
<SelectRecipient contacts={contacts} />
</div>
<div>
<SelectRecipient contacts={contacts} isInvalid />
</div>
<div>
<SelectRecipient contacts={contacts} disabled />
</div>
<div>
<div className="mb-3">Selected address</div>
<SelectRecipient contacts={contacts} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />
</div>
<div>
<div className="mb-3">Selected address (disabled)</div>
<SelectRecipient disabled contacts={contacts} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/require-await */
import { contacts } from "domains/contact/data";
import React from "react";
import { act, fireEvent, render, waitFor } from "testing-library";

import { SelectRecipient } from "./SelectRecipient";

describe("SelectRecipient", () => {
it("should render empty", () => {
const { container } = render(<SelectRecipient contacts={contacts} />);
expect(container).toMatchSnapshot();
});

it("should render disabled", () => {
const { container } = render(<SelectRecipient disabled contacts={contacts} />);
expect(container).toMatchSnapshot();
});

it("should render invalid", () => {
const { container } = render(<SelectRecipient isInvalid contacts={contacts} />);
expect(container).toMatchSnapshot();
});

it("should render with preselected address", () => {
const { container } = render(
<SelectRecipient contacts={contacts} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />,
);
expect(container).toMatchSnapshot();
});

it("should open and close contacts modal", () => {
const { getByTestId } = render(
<SelectRecipient contacts={contacts} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />,
);

expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/);

act(() => {
fireEvent.click(getByTestId("SelectRecipient__select-contact"));
});

expect(getByTestId("modal__inner")).toBeTruthy();

act(() => {
fireEvent.click(getByTestId("modal__close-btn"));
});

waitFor(() => expect(getByTestId("modal__inner")).toBeFalsy());
});

it("should select address from contacts modal", () => {
const { getByTestId, getAllByTestId } = render(
<SelectRecipient contacts={contacts} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />,
);

expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/);

act(() => {
fireEvent.click(getByTestId("SelectRecipient__select-contact"));
});

expect(getByTestId("modal__inner")).toBeTruthy();

const firstAddress = getAllByTestId("ContactListItem__one-option-button-0")[0];

act(() => {
fireEvent.click(firstAddress);
});

waitFor(() => {
expect(getByTestId("modal__inner").toThrow(/Unable to find an element by/));

const selectedAddressValue = contacts[0]?.addresses()[0]?.address;
expect(getByTestId("SelectRecipient__input")).toHaveValue(selectedAddressValue);
});
});

it("should not open contacts modal if disabled", () => {
const { getByTestId } = render(
<SelectRecipient contacts={contacts} disabled address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />,
);

expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/);

act(() => {
fireEvent.click(getByTestId("SelectRecipient__select-contact"));
});

expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/);
});

it("should call onChange prop when entered address in input", async () => {
const fn = jest.fn();
const { getByTestId } = render(<SelectRecipient contacts={contacts} onChange={fn} />);
const address = "bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT";
const recipientInputField = getByTestId("SelectRecipient__input");

await act(async () => {
fireEvent.change(recipientInputField, { target: { value: address } });
});

expect(fn).toBeCalledWith(address);
});

it("should call onChange prop if provided", () => {
const fn = jest.fn();
const { getByTestId, getAllByTestId } = render(
<SelectRecipient contacts={contacts} onChange={fn} address="bP6T9GQ3kqP6T9GQ3kqP6T9GQ3kqTTTP6T9GQ3kqT" />,
);

expect(() => getByTestId("modal__inner")).toThrow(/Unable to find an element by/);

act(() => {
fireEvent.click(getByTestId("SelectRecipient__select-contact"));
});

expect(getByTestId("modal__inner")).toBeTruthy();

const firstAddress = getAllByTestId("ContactListItem__one-option-button-0")[0];

act(() => {
fireEvent.click(firstAddress);
});

waitFor(() => {
expect(getByTestId("modal__inner").toThrow(/Unable to find an element by/));

const selectedAddressValue = contacts[0]?.addresses()[0]?.address;
expect(getByTestId("SelectRecipient__input")).toHaveValue(selectedAddressValue);

expect(fn).toBeCalledWith(selectedAddressValue);
});
});
});
108 changes: 108 additions & 0 deletions src/domains/profile/components/SelectRecipient/SelectRecipient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Avatar } from "app/components/Avatar";
import { Circle } from "app/components/Circle";
import { useFormField } from "app/components/Form/useFormField";
import { Icon } from "app/components/Icon";
import { Input } from "app/components/Input";
import { SearchContact } from "domains/contact/components/SearchContact";
import React, { useEffect, useState } from "react";

type SelectRecipientProps = {
address?: string;
contacts: any[];
disabled?: boolean;
isInvalid?: boolean;
contactSearchTitle?: string;
contactSearchDescription?: string;
selectActionLabel?: string;
onChange?: (address: string) => void;
} & React.InputHTMLAttributes<any>;

const ProfileAvatar = ({ address }: any) => {
if (!address) return <Circle className="mx-3 bg-theme-neutral-200 border-theme-neutral-200" size="sm" noShadow />;
return <Avatar address={address} size="sm" className="mx-3" noShadow />;
};

export const SelectRecipient = React.forwardRef<HTMLInputElement, SelectRecipientProps>(
(
{
contactSearchTitle,
contactSearchDescription,
selectActionLabel,
address,
contacts,
disabled,
isInvalid,
onChange,
}: SelectRecipientProps,
ref,
) => {
const [isContactSearchOpen, setIsContactSearchOpen] = useState(false);
const [selectedAddress, setSelectedAddress] = useState(address);
useEffect(() => setSelectedAddress(address), [address]);

const fieldContext = useFormField();
const isInvalidField = fieldContext?.isInvalid || isInvalid;

const onSelectProfile = (address: any) => {
setSelectedAddress(address);
setIsContactSearchOpen(false);
onChange?.(address);
};

const openContacts = () => {
if (disabled) return;
setIsContactSearchOpen(true);
};

const onInputChange = (value: string) => {
setSelectedAddress(value);
onChange?.(value);
};

return (
<div>
<div data-testid="SelectRecipient__wrapper" className="relative flex items-center w-full text-left">
<div className="absolute left-1">
<ProfileAvatar address={selectedAddress} />
</div>
<Input
className="pl-14 pr-11"
data-testid="SelectRecipient__input"
type="text"
ref={ref}
value={selectedAddress || ""}
onChange={(ev: any) => onInputChange?.(ev.target.value)}
disabled={disabled}
isInvalid={isInvalidField}
/>

<div
data-testid="SelectRecipient__select-contact"
className="absolute flex items-center cursor-pointer right-4 space-x-3"
onClick={openContacts}
>
<Icon name="User" width={20} height={20} />
</div>
</div>

<SearchContact
title={contactSearchTitle}
description={contactSearchDescription}
isOpen={isContactSearchOpen}
contacts={contacts}
options={[{ value: "select", label: selectActionLabel }]}
onAction={(_, { address }: any) => onSelectProfile(address)}
onClose={() => setIsContactSearchOpen(false)}
/>
</div>
);
},
);

SelectRecipient.defaultProps = {
contactSearchTitle: "Recipient search",
contactSearchDescription: "Find and select the recipient from your contacts",
selectActionLabel: "Select",
};

SelectRecipient.displayName = "SelectRecipient";
Loading

0 comments on commit 96c113e

Please sign in to comment.