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

refactor: add CreateContact validations and tests #2597

Merged
merged 9 commits into from
Aug 5, 2020
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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 { availableNetworksMock } from "domains/network/data";
import React from "react";
Expand All @@ -16,13 +17,15 @@ export const Default = () => {
const profile = env.profiles().create("Test profile");

return (
<CreateContact
isOpen={true}
profile={profile}
networks={availableNetworksMock}
onClose={() => alert("closed")}
onCancel={() => alert("cancelled")}
onSave={() => alert("saved")}
/>
<EnvironmentProvider env={env}>
<CreateContact
isOpen={true}
profile={profile}
networks={availableNetworksMock}
onClose={() => alert("closed")}
onCancel={() => alert("cancelled")}
onSave={() => alert("saved")}
/>
</EnvironmentProvider>
);
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,77 @@
/* eslint-disable @typescript-eslint/require-await */
import { Contact, Profile } from "@arkecosystem/platform-sdk-profiles";
import { availableNetworksMock } from "domains/network/data";
import React from "react";
import { render } from "testing-library";
import { act, env, fireEvent, getDefaultProfileId, render, waitFor } from "testing-library";

import { translations } from "../../i18n";
import { CreateContact } from "./CreateContact";

const onSave = jest.fn();
let profile: Profile;
let existingContact: Contact;

describe("CreateContact", () => {
beforeAll(() => {
profile = env.profiles().findById(getDefaultProfileId());
existingContact = profile.contacts().values()[0];
});
it("should not render if not open", () => {
const { asFragment, getByTestId } = render(<CreateContact isOpen={false} onSave={onSave} />);
const { asFragment, getByTestId } = render(<CreateContact profile={profile} isOpen={false} onSave={onSave} />);

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

it("should render a modal", () => {
const { asFragment, getByTestId } = render(<CreateContact isOpen={true} onSave={onSave} />);
it("should render create contact modal", () => {
const { asFragment, getByTestId } = render(
<CreateContact profile={profile} isOpen={true} onSave={onSave} networks={availableNetworksMock} />,
);

expect(getByTestId("modal__inner")).toHaveTextContent(translations.MODAL_CREATE_CONTACT.TITLE);
expect(getByTestId("modal__inner")).toHaveTextContent(translations.MODAL_CREATE_CONTACT.DESCRIPTION);
expect(asFragment()).toMatchSnapshot();
});

it("should not create new contact if contact name exists", async () => {
const fn = jest.fn();
const { getByTestId, queryByTestId } = render(
<CreateContact profile={profile} isOpen={true} onSave={onSave} networks={availableNetworksMock} />,
);

expect(getByTestId("modal__inner")).toHaveTextContent(translations.MODAL_CREATE_CONTACT.TITLE);
expect(getByTestId("modal__inner")).toHaveTextContent(translations.MODAL_CREATE_CONTACT.DESCRIPTION);

const assetInput = getByTestId("SelectNetworkInput__input");

await act(async () => {
await fireEvent.change(getByTestId("contact-form__address-input"), {
target: { value: "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib" },
});

fireEvent.change(getByTestId("contact-form__name-input"), {
target: { value: existingContact.name() },
});

fireEvent.change(assetInput, { target: { value: "Ark Devnet" } });

fireEvent.keyDown(assetInput, { key: "Enter", code: 13 });

await waitFor(() => {
expect(queryByTestId("contact-form__add-address-btn")).not.toBeDisabled();
});
});

await act(async () => {
fireEvent.click(getByTestId("contact-form__add-address-btn"));
});

await act(async () => {
fireEvent.click(getByTestId("contact-form__save-btn"));
});

await waitFor(() => {
expect(fn).not.toHaveBeenCalled();
});
});
});
41 changes: 34 additions & 7 deletions src/domains/contact/components/CreateContact/CreateContact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NetworkData, Profile } from "@arkecosystem/platform-sdk-profiles";
import { Modal } from "app/components/Modal";
import { useEnvironmentContext } from "app/contexts";
import { ContactForm } from "domains/contact/components/ContactForm";
import React from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

type CreateContactProps = {
Expand All @@ -16,16 +16,37 @@ type CreateContactProps = {

export const CreateContact = ({ isOpen, networks, profile, onClose, onCancel, onSave }: CreateContactProps) => {
const { t } = useTranslation();
const [errors, setErrors] = useState<any>({});

const { persist } = useEnvironmentContext();

const handleOnSave = async ({ name, addresses }: any) => {
const contact = profile.contacts().create(name);
useEffect(() => setErrors({}), [isOpen]);

const formatError = (errorMessage: string, name: string) => {
switch (true) {
case errorMessage.includes("already exists"):
return {
name: t("CONTACTS.VALIDATION.CONTACT_NAME_EXISTS", {
name,
}),
};
}
};

await profile.contacts().update(contact?.id(), { addresses });
await persist();
const handleOnSave = async ({ name, addresses }: any) => {
try {
const contact = profile.contacts().create(name);
await profile.contacts().update(contact?.id(), { addresses });
await persist();
onSave?.(contact.id());
} catch (e) {
setErrors(formatError(e.toString(), name));
}
};

onSave?.(contact.id());
const handleChange = (fieldName: string) => {
const { [fieldName]: _, ...restErrors } = errors;
setErrors(restErrors);
};

return (
Expand All @@ -36,7 +57,13 @@ export const CreateContact = ({ isOpen, networks, profile, onClose, onCancel, on
onClose={onClose}
>
<div className="mt-8">
<ContactForm networks={networks} onCancel={onCancel} onSave={handleOnSave} />
<ContactForm
onChange={handleChange}
networks={networks}
onCancel={onCancel}
onSave={handleOnSave}
errors={errors}
/>
</div>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`CreateContact should not render if not open 1`] = `<DocumentFragment />`;

exports[`CreateContact should render a modal 1`] = `
exports[`CreateContact should render create contact modal 1`] = `
<DocumentFragment>
<div
class="fixed inset-0 z-50 bg-black opacity-50"
Expand Down Expand Up @@ -129,7 +129,80 @@ exports[`CreateContact should render a modal 1`] = `
aria-labelledby="ContactForm__network-label"
id="ContactForm__network-menu"
role="listbox"
/>
>
<li
aria-selected="false"
class="inline-block pt-6 mr-6 cursor-pointer"
data-testid="SelectNetwork__NetworkIcon--container"
id="ContactForm__network-item-0"
role="option"
>
<div
aria-label="Ark"
class="sc-fznyAO hDlzVD border-theme-danger-light text-theme-danger-400"
data-testid="NetworkIcon-ARK-mainnet"
>
<div
class="sc-AxirZ hyqZof"
data-testid="NetworkIcon__icon"
height="26"
width="26"
>
<svg>
ark.svg
</svg>
</div>
</div>
</li>
<li
aria-selected="false"
class="inline-block pt-6 mr-6 cursor-pointer"
data-testid="SelectNetwork__NetworkIcon--container"
id="ContactForm__network-item-1"
role="option"
>
<div
aria-label="Ark Devnet"
class="sc-fznyAO hDlzVD border-theme-primary-100 text-theme-primary-400"
data-testid="NetworkIcon-ARK-devnet"
>
<div
class="sc-AxirZ hyqZof"
data-testid="NetworkIcon__icon"
height="26"
width="26"
>
<svg>
ark.svg
</svg>
</div>
</div>
</li>
<li
aria-selected="false"
class="inline-block pt-6 mr-6 cursor-pointer"
data-testid="SelectNetwork__NetworkIcon--container"
id="ContactForm__network-item-2"
role="option"
>
<div
aria-label="Bitcoin"
class="sc-fznyAO hDlzVD border-theme-warning-200 text-theme-warning-400"
data-testid="NetworkIcon-BTC-livenet"
>
<div
class="sc-AxirZ hyqZof"
data-testid="NetworkIcon__icon"
height="26"
width="26"
>
<svg>
btc.svg
</svg>
</div>
</div>
</li>
</ul>
</fieldset>
<fieldset
class="sc-AxhUy dJlYTe flex flex-col space-y-2"
Expand Down
80 changes: 79 additions & 1 deletion src/domains/contact/e2e/create-contact.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ test("should open and cancel contact creation modal", async (t) => {
});

test("should succesfully create contact", async (t) => {
const contactName = "Test contact";

await t.click(Selector('[data-testid="contacts__add-contact-btn"]'));
await t
.expect(
Expand All @@ -46,7 +48,7 @@ test("should succesfully create contact", async (t) => {
.ok();

const nameInput = Selector('[data-testid="contact-form__name-input"]');
await t.typeText(nameInput, "Anne Doe");
await t.typeText(nameInput, contactName);

await t.click(Selector("#ContactForm__network-item-1"));

Expand All @@ -67,4 +69,80 @@ test("should succesfully create contact", async (t) => {
Selector('[data-testid="modal__inner"]').withText(translations.CONTACTS.MODAL_CREATE_CONTACT.TITLE).exists,
)
.notOk();

// Show in list
await t
.expect(
Selector('[data-testid="ContactList"] [data-testid="ContactListItem__name"]').withText(contactName).exists,
)
.ok();
});

test("should error if contact name already exists", async (t) => {
const contactName = "Brian";

await t.click(Selector('[data-testid="contacts__add-contact-btn"]'));
await t
.expect(
Selector('[data-testid="modal__inner"]').withText(translations.CONTACTS.MODAL_CREATE_CONTACT.TITLE).exists,
)
.ok();

const nameInput = Selector('[data-testid="contact-form__name-input"]');
await t.typeText(nameInput, contactName);

await t.click(Selector("#ContactForm__network-item-1"));

const addressInput = Selector('[data-testid="contact-form__address-input"]');
await t.typeText(addressInput, "D6Z26L69gdk9qYmTv5uzk3uGepigtHY4ax");
await t.expect(Selector('[data-testid="contact-form__add-address-btn"]').hasAttribute("disabled")).notOk();

// Add address
await t.click(Selector('[data-testid="contact-form__add-address-btn"]'));
await t.expect(Selector('[data-testid="contact-form__address-list-item"]').withText("D6Z26L69").exists).ok();

// Save
await t.expect(Selector('[data-testid="contact-form__save-btn"]').hasAttribute("disabled")).notOk();
await t.click(Selector('[data-testid="contact-form__save-btn"]'));

await t
.expect(Selector("fieldset p").withText(translations.CONTACTS.VALIDATION.CONTACT_NAME_EXISTS_SUFFIX).exists)
.ok();

await t
.expect(
Selector('[data-testid="modal__inner"]').withText(translations.CONTACTS.MODAL_CREATE_CONTACT.TITLE).exists,
)
.ok();
});

test("should disable save button if name consists of empty spaces", async (t) => {
await t.click(Selector('[data-testid="contacts__add-contact-btn"]'));
await t
.expect(
Selector('[data-testid="modal__inner"]').withText(translations.CONTACTS.MODAL_CREATE_CONTACT.TITLE).exists,
)
.ok();

const nameInput = Selector('[data-testid="contact-form__name-input"]');
await t.typeText(nameInput, " ");

await t.click(Selector("#ContactForm__network-item-1"));

const addressInput = Selector('[data-testid="contact-form__address-input"]');
await t.typeText(addressInput, "D6Z26L69gdk9qYmTv5uzk3uGepigtHY4ax");
await t.expect(Selector('[data-testid="contact-form__add-address-btn"]').hasAttribute("disabled")).notOk();

// Add address
await t.click(Selector('[data-testid="contact-form__add-address-btn"]'));
await t.expect(Selector('[data-testid="contact-form__address-list-item"]').withText("D6Z26L69").exists).ok();

// Save
await t.expect(Selector('[data-testid="contact-form__save-btn"]').hasAttribute("disabled")).ok();

await t
.expect(
Selector('[data-testid="modal__inner"]').withText(translations.CONTACTS.MODAL_CREATE_CONTACT.TITLE).exists,
)
.ok();
});
2 changes: 2 additions & 0 deletions src/domains/contact/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@ export const translations: { [key: string]: any } = {
},
VALIDATION: {
MAXLENGTH_ERROR: "The Contact Name should have less than {{maxLength}} characters.",
CONTACT_NAME_EXISTS: "Contact with name {{name}} already exists.",
CONTACT_NAME_EXISTS_SUFFIX: "already exists",
},
};