From 576310288a8cb7e57e082f17596355e70edcac8b Mon Sep 17 00:00:00 2001
From: Vincent He
Date: Sun, 5 Dec 2021 14:56:39 +1100
Subject: [PATCH 1/5] improve tests
---
.../AddCustomer/AddCustomerForm.spec.tsx | 31 +++++++++++++++--
src/components/Customer/Customer.spec.tsx | 33 +++++++++++++++++--
src/components/Customer/Customer.tsx | 11 ++-----
src/redux/reducers/customerReducers.tsx | 2 +-
src/views/Home.spec.tsx | 9 +++++
src/views/Home.tsx | 7 +++-
6 files changed, 76 insertions(+), 17 deletions(-)
create mode 100644 src/views/Home.spec.tsx
diff --git a/src/components/AddCustomer/AddCustomerForm.spec.tsx b/src/components/AddCustomer/AddCustomerForm.spec.tsx
index cc1a2e1..f401363 100644
--- a/src/components/AddCustomer/AddCustomerForm.spec.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.spec.tsx
@@ -1,12 +1,37 @@
-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("", () => {
+ const saveCustomer = jest.fn();
+ afterEach(() => {
+ jest.clearAllMocks()
+ });
it("should render a ", () => {
- const wrapper = render( {}} />);
+ const wrapper = render();
expect(wrapper.container).toMatchSnapshot();
});
//@TODO Add tests for entering data and clicking submit
+ it("should render 3 input fields", () => {
+ render();
+ expect(screen.getAllByRole("textbox").length).toEqual(3);
+ });
+
+ it("should submit a form with input value when submit is clicked", async () => {
+ render();
+
+ 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',
+ });
+ });
+ });
+
});
diff --git a/src/components/Customer/Customer.spec.tsx b/src/components/Customer/Customer.spec.tsx
index b13962c..c5836c9 100644
--- a/src/components/Customer/Customer.spec.tsx
+++ b/src/components/Customer/Customer.spec.tsx
@@ -1,5 +1,5 @@
-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";
const customer = {
@@ -10,10 +10,37 @@ const customer = {
};
describe("", () => {
+ const deleteCustomer = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
it("should render a ", () => {
const wrapper = render(
- {}} />
+
);
expect(wrapper.container).toMatchSnapshot();
});
+
+ it("should render the customer details that matches mock customer's details", () => {
+ render();
+ expect(screen.getByRole('heading')).toHaveTextContent('Test Tester');
+ expect(screen.getByText(/Phone number: /)).toHaveTextContent("Phone number: 00000000");
+ });
+
+ it("should render a delete button", () => {
+ render();
+ expect(screen.getByRole("button", {
+ name:"Delete"
+ })).toBeVisible();
+ });
+
+ it("should call deleteCustomer when delete is clicked", async () => {
+ render();
+ userEvent.click(screen.getByRole("button"));
+ await waitFor(() => {
+ expect(deleteCustomer).toHaveBeenCalledWith(customer);
+ });
+ })
});
diff --git a/src/components/Customer/Customer.tsx b/src/components/Customer/Customer.tsx
index 8fd093a..d658b93 100644
--- a/src/components/Customer/Customer.tsx
+++ b/src/components/Customer/Customer.tsx
@@ -11,17 +11,10 @@ import {
type Props = {
customer: ICustomer;
- removeCustomer: (customer: ICustomer) => void;
+ deleteCustomer: (customer: ICustomer) => void;
};
-export const Customer: React.FC = ({ customer, removeCustomer }) => {
- const dispatch: Dispatch = useDispatch();
-
- const deleteCustomer = React.useCallback(
- (customer: ICustomer) => dispatch(removeCustomer(customer)),
- [dispatch, removeCustomer]
- );
-
+export const Customer: React.FC = ({ customer, deleteCustomer }) => {
return (
diff --git a/src/redux/reducers/customerReducers.tsx b/src/redux/reducers/customerReducers.tsx
index d6d837e..d9a825d 100644
--- a/src/redux/reducers/customerReducers.tsx
+++ b/src/redux/reducers/customerReducers.tsx
@@ -38,7 +38,7 @@ export const customerReducer = (
};
return {
...state,
- customers: state.customers.concat(newCustomer),
+ customers: [...state.customers, newCustomer],
};
case REMOVE_CUSTOMER:
const updatedCustomers: ICustomer[] = state.customers.filter(
diff --git a/src/views/Home.spec.tsx b/src/views/Home.spec.tsx
new file mode 100644
index 0000000..e433f47
--- /dev/null
+++ b/src/views/Home.spec.tsx
@@ -0,0 +1,9 @@
+import { render, screen } from "../utils/testUtils";
+import Home from "./Home";
+
+describe("", () => {
+ it("should render three customer's details as default", () => {
+ render();
+ expect(screen.getAllByRole("heading").length).toEqual(3);
+ });
+});
\ No newline at end of file
diff --git a/src/views/Home.tsx b/src/views/Home.tsx
index 8911b39..e55ccc2 100644
--- a/src/views/Home.tsx
+++ b/src/views/Home.tsx
@@ -19,6 +19,11 @@ const Home: React.FC = () => {
[dispatch]
);
+ const deleteCustomer = React.useCallback(
+ (customer: ICustomer) => dispatch(removeCustomer(customer)),
+ [dispatch]
+ );
+
return (
<>
@@ -26,7 +31,7 @@ const Home: React.FC = () => {
))}
>
From cc7197577cc13ad0b0a80eae77bb08713ac67921 Mon Sep 17 00:00:00 2001
From: Vincent He
Date: Sun, 5 Dec 2021 17:05:55 +1100
Subject: [PATCH 2/5] Add unique ID to customers, validation to form & testing
---
.../AddCustomer/AddCustomerForm.spec.tsx | 57 +++++++++++++++++--
1 file changed, 51 insertions(+), 6 deletions(-)
diff --git a/src/components/AddCustomer/AddCustomerForm.spec.tsx b/src/components/AddCustomer/AddCustomerForm.spec.tsx
index f401363..5e0b34a 100644
--- a/src/components/AddCustomer/AddCustomerForm.spec.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.spec.tsx
@@ -2,29 +2,29 @@ import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "../../utils/testUtils";
import { AddCustomerForm } from "./AddCustomerForm";
-describe("", () => {
+describe('', () => {
const saveCustomer = jest.fn();
afterEach(() => {
jest.clearAllMocks()
});
- it("should render a ", () => {
+ it('should render a ', () => {
const wrapper = render();
expect(wrapper.container).toMatchSnapshot();
});
//@TODO Add tests for entering data and clicking submit
- it("should render 3 input fields", () => {
+ it('should render 3 input fields', () => {
render();
- expect(screen.getAllByRole("textbox").length).toEqual(3);
+ expect(screen.getAllByRole('textbox').length).toEqual(3);
});
- it("should submit a form with input value when submit is clicked", async () => {
+ it('should submit a form with input value when submit is clicked', async () => {
render();
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"));
+ userEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(saveCustomer).toHaveBeenCalledWith({
firstName: 'Tom',
@@ -34,4 +34,49 @@ describe("", () => {
});
});
+ describe('validation', () => {
+ it('should display 3 error messages when submitting a blank form', async () => {
+ render();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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');
+ });
+ });
+ });
});
From d6066c0544ed27519bfd656a90a5cc8efa467f31 Mon Sep 17 00:00:00 2001
From: Vincent He
Date: Sun, 5 Dec 2021 17:08:47 +1100
Subject: [PATCH 3/5] Add unique ID to customers, validation to form & testing
---
.../AddCustomer/AddCustomerForm.tsx | 67 ++++++++++++-------
.../AddCustomer/StyledAddCustomerForm.ts | 4 ++
.../AddCustomerForm.spec.tsx.snap | 2 +-
src/components/Customer/Customer.spec.tsx | 3 +-
src/redux/reducers/customerReducers.spec.ts | 16 ++---
src/redux/reducers/customerReducers.tsx | 9 +--
src/types/types.ts | 2 +-
src/utils/validator.ts | 7 ++
8 files changed, 70 insertions(+), 40 deletions(-)
create mode 100644 src/utils/validator.ts
diff --git a/src/components/AddCustomer/AddCustomerForm.tsx b/src/components/AddCustomer/AddCustomerForm.tsx
index 52ebf9e..b402255 100644
--- a/src/components/AddCustomer/AddCustomerForm.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.tsx
@@ -6,13 +6,20 @@ 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 = ({ saveCustomer }) => {
+ const PHONE_NUMBER_REGEX = /^(?:\+?(61))? ?(?:\((?=.*\)))?(0?[2-57-8])\)? ?(\d\d(?:[- ](?=\d{3})|(?!\d\d[- ]?\d[- ]))\d\d[- ]?\d[- ]?\d{3})$/
+
return (
= ({ saveCustomer }) => {
setSubmitting(false);
}}
>
-
- First Name
-
+ {({ errors, touched }) => (
+
+ First Name
+ validateEmptyField(value, 'First name is required')}
+ />
+ {errors.firstName && touched.firstName && {errors.firstName}}
- Last Name
-
+ Last Name
+ validateEmptyField(value, 'Last name is required')}
+ />
+ {errors.lastName && touched.lastName && {errors.lastName}}
- Phone Number
-
+ Phone Number
+
+ validateEmptyField(value, 'Phone number is required') ||
+ validatePattern(value, PHONE_NUMBER_REGEX, 'Phone number is not valid')}
+ />
+ {errors.phoneNumber && touched.phoneNumber && {errors.phoneNumber}}
- Add Customer
-
+ Add Customer
+
+ )}
);
};
diff --git a/src/components/AddCustomer/StyledAddCustomerForm.ts b/src/components/AddCustomer/StyledAddCustomerForm.ts
index a7a4bb9..b1ca80f 100644
--- a/src/components/AddCustomer/StyledAddCustomerForm.ts
+++ b/src/components/AddCustomer/StyledAddCustomerForm.ts
@@ -30,3 +30,7 @@ export const StyledAddButton = styled.button`
border-radius: 4px;
font-size: 16px;
`;
+
+export const StyledErrorMessage = styled.span`
+ color: red;
+`;
diff --git a/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap b/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
index 63bdf1a..91120de 100644
--- a/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
+++ b/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
@@ -42,7 +42,7 @@ exports[` should render a 1`] = `
class="sc-dlnjwi fstXny"
id="phoneNumber"
name="phoneNumber"
- placeholder="john@acme.com"
+ placeholder="0411 222 333"
type="tel"
value=""
/>
diff --git a/src/components/Customer/Customer.spec.tsx b/src/components/Customer/Customer.spec.tsx
index c5836c9..6dfe86a 100644
--- a/src/components/Customer/Customer.spec.tsx
+++ b/src/components/Customer/Customer.spec.tsx
@@ -1,9 +1,10 @@
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",
diff --git a/src/redux/reducers/customerReducers.spec.ts b/src/redux/reducers/customerReducers.spec.ts
index 3b03881..bf37289 100644
--- a/src/redux/reducers/customerReducers.spec.ts
+++ b/src/redux/reducers/customerReducers.spec.ts
@@ -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",
},
@@ -37,7 +37,7 @@ describe("customer reducer", () => {
type: ADD_CUSTOMER,
customer: {
firstName: "Test",
- id: 1,
+ id: "1",
lastName: "Dummy",
phoneNumber: "000 000 000",
},
@@ -46,7 +46,7 @@ describe("customer reducer", () => {
customers: [
{
firstName: "Test",
- id: 1,
+ id: "1",
lastName: "Dummy",
phoneNumber: "000 000 000",
},
@@ -60,7 +60,7 @@ describe("customer reducer", () => {
type: REMOVE_CUSTOMER,
customer: {
firstName: "Charles",
- id: 1,
+ id: "1",
lastName: "Babbage",
phoneNumber: "0412 123 123",
},
@@ -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",
},
diff --git a/src/redux/reducers/customerReducers.tsx b/src/redux/reducers/customerReducers.tsx
index d9a825d..14e81b4 100644
--- a/src/redux/reducers/customerReducers.tsx
+++ b/src/redux/reducers/customerReducers.tsx
@@ -1,22 +1,23 @@
import { CustomerAction, CustomerState, ICustomer } from "../../types/types";
import { ADD_CUSTOMER, REMOVE_CUSTOMER } from "../actions/customerTypes";
+import { v4 as uuidv4 } from 'uuid';
export const initialState: CustomerState = {
customers: [
{
- id: 1,
+ id: "1",
firstName: "Charles",
lastName: "Babbage",
phoneNumber: "0412 123 123",
},
{
- id: 2,
+ id: "2",
firstName: "Alan",
lastName: "Turing",
phoneNumber: "(03) 9599 1234",
},
{
- id: 3,
+ id: "3",
firstName: "Ada",
lastName: "Lovelace",
phoneNumber: "+61 423 345 567",
@@ -31,7 +32,7 @@ export const customerReducer = (
switch (action.type) {
case ADD_CUSTOMER:
const newCustomer: ICustomer = {
- id: action.customer.id ?? Math.random(), // not really unique but it's just an example
+ id: action.customer.id ?? uuidv4(),
firstName: action.customer.firstName,
lastName: action.customer.lastName,
phoneNumber: action.customer.phoneNumber,
diff --git a/src/types/types.ts b/src/types/types.ts
index 83e2415..1b781b4 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -5,7 +5,7 @@ export interface Customer {
}
export interface ICustomer extends Customer {
- id: number;
+ id: string;
}
export type CustomerState = {
diff --git a/src/utils/validator.ts b/src/utils/validator.ts
new file mode 100644
index 0000000..a2ca360
--- /dev/null
+++ b/src/utils/validator.ts
@@ -0,0 +1,7 @@
+export const validateEmptyField = (value:string, errorMessage: string) => {
+ return !value ? errorMessage : undefined
+ }
+
+export const validatePattern = (value: string, pattern: RegExp, errorMessage: string) => {
+ return !pattern.test(value) ? errorMessage : undefined
+ }
\ No newline at end of file
From 4ab331e2364687e19d20f14c959156dc5cf83f9d Mon Sep 17 00:00:00 2001
From: Vincent He
Date: Sun, 5 Dec 2021 18:57:10 +1100
Subject: [PATCH 4/5] Add filter function & tests, add cursor for buttons
---
.../AddCustomer/AddCustomerForm.spec.tsx | 1 -
.../AddCustomer/StyledAddCustomerForm.ts | 1 +
.../AddCustomerForm.spec.tsx.snap | 2 +-
src/components/Customer/StyledCustomer.ts | 1 +
.../__snapshots__/Customer.spec.tsx.snap | 2 +-
src/views/Home.spec.tsx | 7 +++++
src/views/Home.tsx | 29 ++++++++++++++++---
src/views/StyledHome.ts | 10 +++++++
8 files changed, 46 insertions(+), 7 deletions(-)
create mode 100644 src/views/StyledHome.ts
diff --git a/src/components/AddCustomer/AddCustomerForm.spec.tsx b/src/components/AddCustomer/AddCustomerForm.spec.tsx
index 5e0b34a..a872520 100644
--- a/src/components/AddCustomer/AddCustomerForm.spec.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.spec.tsx
@@ -12,7 +12,6 @@ describe('', () => {
expect(wrapper.container).toMatchSnapshot();
});
- //@TODO Add tests for entering data and clicking submit
it('should render 3 input fields', () => {
render();
expect(screen.getAllByRole('textbox').length).toEqual(3);
diff --git a/src/components/AddCustomer/StyledAddCustomerForm.ts b/src/components/AddCustomer/StyledAddCustomerForm.ts
index b1ca80f..ed7a3ac 100644
--- a/src/components/AddCustomer/StyledAddCustomerForm.ts
+++ b/src/components/AddCustomer/StyledAddCustomerForm.ts
@@ -29,6 +29,7 @@ export const StyledAddButton = styled.button`
border: none;
border-radius: 4px;
font-size: 16px;
+ cursor: pointer;
`;
export const StyledErrorMessage = styled.span`
diff --git a/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap b/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
index 91120de..94a5119 100644
--- a/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
+++ b/src/components/AddCustomer/__snapshots__/AddCustomerForm.spec.tsx.snap
@@ -47,7 +47,7 @@ exports[` should render a 1`] = `
value=""
/>
diff --git a/src/views/Home.spec.tsx b/src/views/Home.spec.tsx
index e433f47..3e26bd3 100644
--- a/src/views/Home.spec.tsx
+++ b/src/views/Home.spec.tsx
@@ -1,3 +1,4 @@
+import userEvent from "@testing-library/user-event";
import { render, screen } from "../utils/testUtils";
import Home from "./Home";
@@ -6,4 +7,10 @@ describe("", () => {
render();
expect(screen.getAllByRole("heading").length).toEqual(3);
});
+
+ it('should show customer Alan Turning only if Alan is inputted in search box', () => {
+ render();
+ userEvent.type(screen.getByRole('searchbox'), 'alan');
+ expect(screen.getByRole("heading")).toHaveTextContent('Alan Turing');
+ });
});
\ No newline at end of file
diff --git a/src/views/Home.tsx b/src/views/Home.tsx
index e55ccc2..f12e396 100644
--- a/src/views/Home.tsx
+++ b/src/views/Home.tsx
@@ -1,33 +1,54 @@
-import * as React from "react";
+import { useState, useCallback, useEffect, useMemo } from "react";
import { useSelector, shallowEqual, useDispatch } from "react-redux";
import { Customer } from "../components/Customer/Customer";
import { AddCustomerForm } from "../components/AddCustomer/AddCustomerForm";
import { Dispatch } from "redux";
import { CustomerState, ICustomer } from "../types/types";
import { addCustomer, removeCustomer } from "../redux/actions/customerActions";
+import { StyledInput } from "./StyledHome"
const Home: React.FC = () => {
+ const [input, setInput] = useState("");
+ const [customersForDisplay, setCustomersForDisplay] = useState([]);
const customers: readonly ICustomer[] = useSelector(
(state: CustomerState) => state.customers,
shallowEqual
);
+ const memoedCustomers = useMemo(()=> customers, [customers]);
const dispatch: Dispatch = useDispatch();
- const saveCustomer = React.useCallback(
+ const saveCustomer = useCallback(
(customer: ICustomer) => dispatch(addCustomer(customer)),
[dispatch]
);
- const deleteCustomer = React.useCallback(
+ const deleteCustomer = useCallback(
(customer: ICustomer) => dispatch(removeCustomer(customer)),
[dispatch]
);
+ const filterCustomers = (customers: readonly ICustomer[], input: string) => customers.filter((customer) =>
+ customer.firstName.toLowerCase().includes(input.toLowerCase()) ||
+ customer.lastName.toLowerCase().includes(input.toLowerCase()) ||
+ customer.phoneNumber.toLowerCase().includes(input.toLowerCase()));
+
+ useEffect(() => {
+ setCustomersForDisplay(filterCustomers(memoedCustomers, input))
+ }, [memoedCustomers, input]);
+
return (
<>
- {customers.map((customer: ICustomer) => (
+ {
+ setInput(e.target.value)
+ }}
+ />
+ {customersForDisplay.map((customer: ICustomer) => (
Date: Sun, 5 Dec 2021 19:15:52 +1100
Subject: [PATCH 5/5] fix style issue
---
.../AddCustomer/AddCustomerForm.spec.tsx | 60 +++++++++++--------
.../AddCustomer/AddCustomerForm.tsx | 47 ++++++++++-----
src/components/Customer/Customer.spec.tsx | 20 ++++---
src/components/Customer/Customer.tsx | 2 -
src/redux/reducers/customerReducers.tsx | 2 +-
src/views/Home.spec.tsx | 22 +++----
src/views/Home.tsx | 39 ++++++------
src/views/StyledHome.ts | 14 ++---
8 files changed, 120 insertions(+), 86 deletions(-)
diff --git a/src/components/AddCustomer/AddCustomerForm.spec.tsx b/src/components/AddCustomer/AddCustomerForm.spec.tsx
index a872520..1ff5414 100644
--- a/src/components/AddCustomer/AddCustomerForm.spec.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.spec.tsx
@@ -2,79 +2,87 @@ import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "../../utils/testUtils";
import { AddCustomerForm } from "./AddCustomerForm";
-describe('', () => {
+describe("", () => {
const saveCustomer = jest.fn();
afterEach(() => {
- jest.clearAllMocks()
+ jest.clearAllMocks();
});
- it('should render a ', () => {
+ it("should render a ", () => {
const wrapper = render();
expect(wrapper.container).toMatchSnapshot();
});
- it('should render 3 input fields', () => {
+ it("should render 3 input fields", () => {
render();
- expect(screen.getAllByRole('textbox').length).toEqual(3);
+ expect(screen.getAllByRole("textbox").length).toEqual(3);
});
- it('should submit a form with input value when submit is clicked', async () => {
+ it("should submit a form with input value when submit is clicked", async () => {
render();
- 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'));
+ 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',
+ firstName: "Tom",
+ lastName: "Smith",
+ phoneNumber: "0400 111 222",
});
});
});
- describe('validation', () => {
- it('should display 3 error messages when submitting a blank form', async () => {
+ describe("validation", () => {
+ it("should display 3 error messages when submitting a blank form", async () => {
render();
- userEvent.click(screen.getByRole('button'));
+ userEvent.click(screen.getByRole("button"));
await waitFor(() => {
- expect(screen.getAllByRole('alert').length).toEqual(3);
+ expect(screen.getAllByRole("alert").length).toEqual(3);
});
});
- it('should display an error message when first name field is touched and left empty', async () => {
+ it("should display an error message when first name field is touched and left empty", async () => {
render();
userEvent.click(screen.getByLabelText(/First Name/));
userEvent.click(screen.getByLabelText(/Last Name/));
await waitFor(() => {
- expect(screen.getByRole('alert')).toHaveTextContent('First name is required');
+ 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 () => {
+ it("should display an error message when last name field is touched and left empty", async () => {
render();
userEvent.click(screen.getByLabelText(/Last Name/));
userEvent.click(screen.getByLabelText(/Phone Number/));
await waitFor(() => {
- expect(screen.getByRole('alert')).toHaveTextContent('Last name is required');
+ 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 () => {
+ it("should display an error message when phone number field is touched and left empty", async () => {
render();
userEvent.click(screen.getByLabelText(/Phone Number/));
userEvent.click(screen.getByLabelText(/First Name/));
await waitFor(() => {
- expect(screen.getByRole('alert')).toHaveTextContent('Phone number is required');
+ expect(screen.getByRole("alert")).toHaveTextContent(
+ "Phone number is required"
+ );
});
});
- it('should display an error message when inputted an invalid phone number', async () => {
+ it("should display an error message when inputted an invalid phone number", async () => {
render();
- userEvent.type(screen.getByLabelText(/Phone Number/), '000');
+ 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');
+ expect(screen.getByRole("alert")).toHaveTextContent(
+ "Phone number is not valid"
+ );
});
});
});
diff --git a/src/components/AddCustomer/AddCustomerForm.tsx b/src/components/AddCustomer/AddCustomerForm.tsx
index b402255..5e1b310 100644
--- a/src/components/AddCustomer/AddCustomerForm.tsx
+++ b/src/components/AddCustomer/AddCustomerForm.tsx
@@ -6,19 +6,17 @@ import {
StyledInput,
StyledLabel,
StyledAddButton,
- StyledErrorMessage
+ StyledErrorMessage,
} from "./StyledAddCustomerForm";
-import {
- validateEmptyField,
- validatePattern
-} from '../../utils/validator'
+import { validateEmptyField, validatePattern } from "../../utils/validator";
type Props = {
saveCustomer: (customer: ICustomer | any) => void;
};
export const AddCustomerForm: React.FC = ({ saveCustomer }) => {
- const PHONE_NUMBER_REGEX = /^(?:\+?(61))? ?(?:\((?=.*\)))?(0?[2-57-8])\)? ?(\d\d(?:[- ](?=\d{3})|(?!\d\d[- ]?\d[- ]))\d\d[- ]?\d[- ]?\d{3})$/
+ const PHONE_NUMBER_REGEX =
+ /^(?:\+?(61))? ?(?:\((?=.*\)))?(0?[2-57-8])\)? ?(\d\d(?:[- ](?=\d{3})|(?!\d\d[- ]?\d[- ]))\d\d[- ]?\d[- ]?\d{3})$/;
return (
= ({ saveCustomer }) => {
id="firstName"
name="firstName"
placeholder="John"
- validate={(value: string) => validateEmptyField(value, 'First name is required')}
+ validate={(value: string) =>
+ validateEmptyField(value, "First name is required")
+ }
/>
- {errors.firstName && touched.firstName && {errors.firstName}}
+ {errors.firstName && touched.firstName && (
+
+ {errors.firstName}
+
+ )}
Last Name
= ({ saveCustomer }) => {
id="lastName"
name="lastName"
placeholder="Doe"
- validate={(value: string) => validateEmptyField(value, 'Last name is required')}
+ validate={(value: string) =>
+ validateEmptyField(value, "Last name is required")
+ }
/>
- {errors.lastName && touched.lastName && {errors.lastName}}
+ {errors.lastName && touched.lastName && (
+
+ {errors.lastName}
+
+ )}
Phone Number
= ({ saveCustomer }) => {
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')}
+ validate={(value: string) =>
+ validateEmptyField(value, "Phone number is required") ||
+ validatePattern(
+ value,
+ PHONE_NUMBER_REGEX,
+ "Phone number is not valid"
+ )
+ }
/>
- {errors.phoneNumber && touched.phoneNumber && {errors.phoneNumber}}
+ {errors.phoneNumber && touched.phoneNumber && (
+
+ {errors.phoneNumber}
+
+ )}
Add Customer
diff --git a/src/components/Customer/Customer.spec.tsx b/src/components/Customer/Customer.spec.tsx
index 6dfe86a..9596029 100644
--- a/src/components/Customer/Customer.spec.tsx
+++ b/src/components/Customer/Customer.spec.tsx
@@ -1,7 +1,7 @@
import userEvent from "@testing-library/user-event";
import { render, screen, waitFor } from "../../utils/testUtils";
import { Customer } from "./Customer";
-import { v4 as uuidv4 } from 'uuid';
+import { v4 as uuidv4 } from "uuid";
const customer = {
id: uuidv4(),
@@ -12,7 +12,7 @@ const customer = {
describe("", () => {
const deleteCustomer = jest.fn();
-
+
afterEach(() => {
jest.clearAllMocks();
});
@@ -26,15 +26,19 @@ describe("", () => {
it("should render the customer details that matches mock customer's details", () => {
render();
- expect(screen.getByRole('heading')).toHaveTextContent('Test Tester');
- expect(screen.getByText(/Phone number: /)).toHaveTextContent("Phone number: 00000000");
+ expect(screen.getByRole("heading")).toHaveTextContent("Test Tester");
+ expect(screen.getByText(/Phone number: /)).toHaveTextContent(
+ "Phone number: 00000000"
+ );
});
it("should render a delete button", () => {
render();
- expect(screen.getByRole("button", {
- name:"Delete"
- })).toBeVisible();
+ expect(
+ screen.getByRole("button", {
+ name: "Delete",
+ })
+ ).toBeVisible();
});
it("should call deleteCustomer when delete is clicked", async () => {
@@ -43,5 +47,5 @@ describe("", () => {
await waitFor(() => {
expect(deleteCustomer).toHaveBeenCalledWith(customer);
});
- })
+ });
});
diff --git a/src/components/Customer/Customer.tsx b/src/components/Customer/Customer.tsx
index d658b93..27d5960 100644
--- a/src/components/Customer/Customer.tsx
+++ b/src/components/Customer/Customer.tsx
@@ -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,
diff --git a/src/redux/reducers/customerReducers.tsx b/src/redux/reducers/customerReducers.tsx
index 14e81b4..b66c7c9 100644
--- a/src/redux/reducers/customerReducers.tsx
+++ b/src/redux/reducers/customerReducers.tsx
@@ -1,6 +1,6 @@
import { CustomerAction, CustomerState, ICustomer } from "../../types/types";
import { ADD_CUSTOMER, REMOVE_CUSTOMER } from "../actions/customerTypes";
-import { v4 as uuidv4 } from 'uuid';
+import { v4 as uuidv4 } from "uuid";
export const initialState: CustomerState = {
customers: [
diff --git a/src/views/Home.spec.tsx b/src/views/Home.spec.tsx
index 3e26bd3..5a69d1f 100644
--- a/src/views/Home.spec.tsx
+++ b/src/views/Home.spec.tsx
@@ -1,16 +1,16 @@
import userEvent from "@testing-library/user-event";
import { render, screen } from "../utils/testUtils";
-import Home from "./Home";
+import Home from "./Home";
describe("", () => {
- it("should render three customer's details as default", () => {
- render();
- expect(screen.getAllByRole("heading").length).toEqual(3);
- });
+ it("should render three customer's details as default", () => {
+ render();
+ expect(screen.getAllByRole("heading").length).toEqual(3);
+ });
- it('should show customer Alan Turning only if Alan is inputted in search box', () => {
- render();
- userEvent.type(screen.getByRole('searchbox'), 'alan');
- expect(screen.getByRole("heading")).toHaveTextContent('Alan Turing');
- });
-});
\ No newline at end of file
+ it("should show customer Alan Turning only if Alan is inputted in search box", () => {
+ render();
+ userEvent.type(screen.getByRole("searchbox"), "alan");
+ expect(screen.getByRole("heading")).toHaveTextContent("Alan Turing");
+ });
+});
diff --git a/src/views/Home.tsx b/src/views/Home.tsx
index f12e396..9add968 100644
--- a/src/views/Home.tsx
+++ b/src/views/Home.tsx
@@ -5,16 +5,18 @@ import { AddCustomerForm } from "../components/AddCustomer/AddCustomerForm";
import { Dispatch } from "redux";
import { CustomerState, ICustomer } from "../types/types";
import { addCustomer, removeCustomer } from "../redux/actions/customerActions";
-import { StyledInput } from "./StyledHome"
+import { StyledInput } from "./StyledHome";
const Home: React.FC = () => {
const [input, setInput] = useState("");
- const [customersForDisplay, setCustomersForDisplay] = useState([]);
+ const [customersForDisplay, setCustomersForDisplay] = useState(
+ []
+ );
const customers: readonly ICustomer[] = useSelector(
(state: CustomerState) => state.customers,
shallowEqual
);
- const memoedCustomers = useMemo(()=> customers, [customers]);
+ const memoedCustomers = useMemo(() => customers, [customers]);
const dispatch: Dispatch = useDispatch();
@@ -28,26 +30,29 @@ const Home: React.FC = () => {
[dispatch]
);
- const filterCustomers = (customers: readonly ICustomer[], input: string) => customers.filter((customer) =>
- customer.firstName.toLowerCase().includes(input.toLowerCase()) ||
- customer.lastName.toLowerCase().includes(input.toLowerCase()) ||
- customer.phoneNumber.toLowerCase().includes(input.toLowerCase()));
-
+ const filterCustomers = (customers: readonly ICustomer[], input: string) =>
+ customers.filter(
+ (customer) =>
+ customer.firstName.toLowerCase().includes(input.toLowerCase()) ||
+ customer.lastName.toLowerCase().includes(input.toLowerCase()) ||
+ customer.phoneNumber.toLowerCase().includes(input.toLowerCase())
+ );
+
useEffect(() => {
- setCustomersForDisplay(filterCustomers(memoedCustomers, input))
+ setCustomersForDisplay(filterCustomers(memoedCustomers, input));
}, [memoedCustomers, input]);
return (
<>
- {
- setInput(e.target.value)
- }}
- />
+ {
+ setInput(e.target.value);
+ }}
+ />
{customersForDisplay.map((customer: ICustomer) => (