Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions src/components/AddCustomer/AddCustomerForm.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,89 @@
import * as React from "react";
import { render } from "../../utils/testUtils";
import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "../../utils/testUtils";
import { AddCustomerForm } from "./AddCustomerForm";

describe("<AddCustomerForm />", () => {
const saveCustomer = jest.fn();
afterEach(() => {
jest.clearAllMocks();
});
it("should render a <AddCustomerForm />", () => {
const wrapper = render(<AddCustomerForm saveCustomer={() => {}} />);
const wrapper = render(<AddCustomerForm saveCustomer={saveCustomer} />);
expect(wrapper.container).toMatchSnapshot();
});

//@TODO Add tests for entering data and clicking submit
it("should render 3 input fields", () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
expect(screen.getAllByRole("textbox").length).toEqual(3);
});

it("should submit a form with input value when submit is clicked", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);

userEvent.type(screen.getByLabelText(/First Name/), "Tom");
userEvent.type(screen.getByLabelText(/Last Name/), "Smith");
userEvent.type(screen.getByLabelText(/Phone Number/), "0400 111 222");
userEvent.click(screen.getByRole("button"));
await waitFor(() => {
expect(saveCustomer).toHaveBeenCalledWith({
firstName: "Tom",
lastName: "Smith",
phoneNumber: "0400 111 222",
});
});
});

describe("validation", () => {
it("should display 3 error messages when submitting a blank form", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
userEvent.click(screen.getByRole("button"));
await waitFor(() => {
expect(screen.getAllByRole("alert").length).toEqual(3);
});
});

it("should display an error message when first name field is touched and left empty", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
userEvent.click(screen.getByLabelText(/First Name/));
userEvent.click(screen.getByLabelText(/Last Name/));
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent(
"First name is required"
);
});
});

it("should display an error message when last name field is touched and left empty", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
userEvent.click(screen.getByLabelText(/Last Name/));
userEvent.click(screen.getByLabelText(/Phone Number/));
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent(
"Last name is required"
);
});
});

it("should display an error message when phone number field is touched and left empty", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
userEvent.click(screen.getByLabelText(/Phone Number/));
userEvent.click(screen.getByLabelText(/First Name/));
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent(
"Phone number is required"
);
});
});

it("should display an error message when inputted an invalid phone number", async () => {
render(<AddCustomerForm saveCustomer={saveCustomer} />);
userEvent.type(screen.getByLabelText(/Phone Number/), "000");
userEvent.click(screen.getByLabelText(/First Name/));
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent(
"Phone number is not valid"
);
});
});
});
});
86 changes: 61 additions & 25 deletions src/components/AddCustomer/AddCustomerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import {
StyledInput,
StyledLabel,
StyledAddButton,
StyledErrorMessage,
} from "./StyledAddCustomerForm";
import { validateEmptyField, validatePattern } from "../../utils/validator";

type Props = {
saveCustomer: (customer: ICustomer | any) => void;
};

export const AddCustomerForm: React.FC<Props> = ({ saveCustomer }) => {
const PHONE_NUMBER_REGEX =
/^(?:\+?(61))? ?(?:\((?=.*\)))?(0?[2-57-8])\)? ?(\d\d(?:[- ](?=\d{3})|(?!\d\d[- ]?\d[- ]))\d\d[- ]?\d[- ]?\d{3})$/;

return (
<Formik
initialValues={{
Expand All @@ -28,34 +33,65 @@ export const AddCustomerForm: React.FC<Props> = ({ saveCustomer }) => {
setSubmitting(false);
}}
>
<StyledForm>
<StyledLabel htmlFor="firstName">First Name</StyledLabel>
<Field
as={StyledInput}
id="firstName"
name="firstName"
placeholder="John"
/>
{({ errors, touched }) => (
<StyledForm>
<StyledLabel htmlFor="firstName">First Name</StyledLabel>
<Field
as={StyledInput}
id="firstName"
name="firstName"
placeholder="John"
validate={(value: string) =>
validateEmptyField(value, "First name is required")
}
/>
{errors.firstName && touched.firstName && (
<StyledErrorMessage role="alert">
{errors.firstName}
</StyledErrorMessage>
)}

<StyledLabel htmlFor="lastName">Last Name</StyledLabel>
<Field
as={StyledInput}
id="lastName"
name="lastName"
placeholder="Doe"
/>
<StyledLabel htmlFor="lastName">Last Name</StyledLabel>
<Field
as={StyledInput}
id="lastName"
name="lastName"
placeholder="Doe"
validate={(value: string) =>
validateEmptyField(value, "Last name is required")
}
/>
{errors.lastName && touched.lastName && (
<StyledErrorMessage role="alert">
{errors.lastName}
</StyledErrorMessage>
)}

<StyledLabel htmlFor="phoneNumber">Phone Number</StyledLabel>
<Field
as={StyledInput}
id="phoneNumber"
name="phoneNumber"
placeholder="john@acme.com"
type="tel"
/>
<StyledLabel htmlFor="phoneNumber">Phone Number</StyledLabel>
<Field
as={StyledInput}
id="phoneNumber"
name="phoneNumber"
placeholder="0411 222 333"
type="tel"
validate={(value: string) =>
validateEmptyField(value, "Phone number is required") ||
validatePattern(
value,
PHONE_NUMBER_REGEX,
"Phone number is not valid"
)
}
/>
{errors.phoneNumber && touched.phoneNumber && (
<StyledErrorMessage role="alert">
{errors.phoneNumber}
</StyledErrorMessage>
)}

<StyledAddButton type="submit">Add Customer</StyledAddButton>
</StyledForm>
<StyledAddButton type="submit">Add Customer</StyledAddButton>
</StyledForm>
)}
</Formik>
);
};
5 changes: 5 additions & 0 deletions src/components/AddCustomer/StyledAddCustomerForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ export const StyledAddButton = styled.button`
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
`;

export const StyledErrorMessage = styled.span`
color: red;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ exports[`<AddCustomerForm /> should render a <AddCustomerForm /> 1`] = `
class="sc-dlnjwi fstXny"
id="phoneNumber"
name="phoneNumber"
placeholder="john@acme.com"
placeholder="0411 222 333"
type="tel"
value=""
/>
<button
class="sc-hKFxyN jrRSci"
class="sc-hKFxyN FGdaA"
type="submit"
>
Add Customer
Expand Down
40 changes: 36 additions & 4 deletions src/components/Customer/Customer.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
import * as React from "react";
import { render } from "../../utils/testUtils";
import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "../../utils/testUtils";
import { Customer } from "./Customer";
import { v4 as uuidv4 } from "uuid";

const customer = {
id: 1,
id: uuidv4(),
firstName: "Test",
lastName: "Tester",
phoneNumber: "00000000",
};

describe("<Customer />", () => {
const deleteCustomer = jest.fn();

afterEach(() => {
jest.clearAllMocks();
});

it("should render a <Customer />", () => {
const wrapper = render(
<Customer customer={customer} removeCustomer={() => {}} />
<Customer customer={customer} deleteCustomer={deleteCustomer} />
);
expect(wrapper.container).toMatchSnapshot();
});

it("should render the customer details that matches mock customer's details", () => {
render(<Customer customer={customer} deleteCustomer={deleteCustomer} />);
expect(screen.getByRole("heading")).toHaveTextContent("Test Tester");
expect(screen.getByText(/Phone number: /)).toHaveTextContent(
"Phone number: 00000000"
);
});

it("should render a delete button", () => {
render(<Customer customer={customer} deleteCustomer={deleteCustomer} />);
expect(
screen.getByRole("button", {
name: "Delete",
})
).toBeVisible();
});

it("should call deleteCustomer when delete is clicked", async () => {
render(<Customer customer={customer} deleteCustomer={deleteCustomer} />);
userEvent.click(screen.getByRole("button"));
await waitFor(() => {
expect(deleteCustomer).toHaveBeenCalledWith(customer);
});
});
});
13 changes: 2 additions & 11 deletions src/components/Customer/Customer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as React from "react";
import { Dispatch } from "redux";
import { useDispatch } from "react-redux";
import { ICustomer } from "../../types/types";
import {
StyledCustomer,
Expand All @@ -11,17 +9,10 @@ import {

type Props = {
customer: ICustomer;
removeCustomer: (customer: ICustomer) => void;
deleteCustomer: (customer: ICustomer) => void;
};

export const Customer: React.FC<Props> = ({ customer, removeCustomer }) => {
const dispatch: Dispatch<any> = useDispatch();

const deleteCustomer = React.useCallback(
(customer: ICustomer) => dispatch(removeCustomer(customer)),
[dispatch, removeCustomer]
);

export const Customer: React.FC<Props> = ({ customer, deleteCustomer }) => {
return (
<StyledCustomer>
<StyledCustomerName>
Expand Down
1 change: 1 addition & 0 deletions src/components/Customer/StyledCustomer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export const StyledCustomerDelete = styled.button`
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ exports[`<Customer /> should render a <Customer /> 1`] = `
00000000
</p>
<button
class="sc-hKFxyN dwHgfV"
class="sc-hKFxyN fMdrYX"
>
Delete
</button>
Expand Down
16 changes: 8 additions & 8 deletions src/redux/reducers/customerReducers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ const initialState = {
customers: [
{
firstName: "Charles",
id: 1,
id: "1",
lastName: "Babbage",
phoneNumber: "0412 123 123",
},
{
firstName: "Alan",
id: 2,
id: "2",
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
},
{
firstName: "Ada",
id: 3,
id: "3",
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
},
Expand All @@ -37,7 +37,7 @@ describe("customer reducer", () => {
type: ADD_CUSTOMER,
customer: {
firstName: "Test",
id: 1,
id: "1",
lastName: "Dummy",
phoneNumber: "000 000 000",
},
Expand All @@ -46,7 +46,7 @@ describe("customer reducer", () => {
customers: [
{
firstName: "Test",
id: 1,
id: "1",
lastName: "Dummy",
phoneNumber: "000 000 000",
},
Expand All @@ -60,7 +60,7 @@ describe("customer reducer", () => {
type: REMOVE_CUSTOMER,
customer: {
firstName: "Charles",
id: 1,
id: "1",
lastName: "Babbage",
phoneNumber: "0412 123 123",
},
Expand All @@ -69,13 +69,13 @@ describe("customer reducer", () => {
customers: [
{
firstName: "Alan",
id: 2,
id: "2",
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
},
{
firstName: "Ada",
id: 3,
id: "3",
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
},
Expand Down
Loading