diff --git a/package.json b/package.json
index 4838c4d..ec99e50 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"@types/node": "^14.14.31",
"@types/react-dom": "^17.0.1",
"@types/react-redux": "^7.1.16",
+ "@types/redux-mock-store": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^4.15.2",
"@typescript-eslint/parser": "^4.15.2",
"autoprefixer": "^10.2.4",
@@ -60,6 +61,7 @@
"prettier": "^2.2.1",
"prettier-quick": "0.0.5",
"pretty-quick": "^3.1.0",
+ "redux-mock-store": "^1.5.4",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"style-loader": "^2.0.0",
diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx
index 7bacd7f..0d26919 100644
--- a/src/components/App/App.test.tsx
+++ b/src/components/App/App.test.tsx
@@ -1,24 +1,28 @@
import React from "react";
-import { Provider } from "react-redux";
-import { render } from "@testing-library/react";
-
-import store from "../../state/store";
+import renderConnected from "../../utilities/test/renderConnected";
import App from "./App";
-const setup = () => {
- const container = render(
-
-
-
- );
- return container;
-};
+const setup = (initialState = {}) =>
+ renderConnected({
+ ui: ,
+ initialState,
+ });
describe("App", () => {
- it("renders app", () => {
- const { container } = setup();
-
+ it("renders Loader when userProfile not loaded", () => {
+ const { container } = setup({
+ userProfile: {},
+ });
+ const app = container.querySelector(".loader");
+ expect(app).toBeInTheDocument();
+ });
+ it("renders App when userProfile loaded", () => {
+ const { container } = setup({
+ userProfile: {
+ username: "Steve",
+ },
+ });
const app = container.querySelector(".app");
expect(app).toBeInTheDocument();
});
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 9b3a37c..a7bca60 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -1,18 +1,33 @@
import React, { FC, useEffect } from "react";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
import { fetchUserById } from "../../state/userProfile/actions";
+import { getUsername } from "../../state/userProfile/selectors";
+
+import Loader from "../Loader/Loader";
+import UserHeader from "../UserHeader/UserHeader";
import "../../styles/app.scss";
const App: FC = () => {
const dispatch = useDispatch();
+ const username = useSelector(getUsername);
+
useEffect(() => {
- dispatch(fetchUserById());
+ if (!username) {
+ dispatch(fetchUserById());
+ }
}, [dispatch]);
+
+ const isPageReady = username;
+
+ if (!isPageReady) {
+ return ;
+ }
+
return (
);
};
diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx
new file mode 100644
index 0000000..478cb07
--- /dev/null
+++ b/src/components/Loader/Loader.tsx
@@ -0,0 +1,9 @@
+import React, { FC } from "react";
+
+const Loader: FC = () => (
+
+);
+
+export default Loader;
diff --git a/src/components/UserHeader/UserHeader.test.tsx b/src/components/UserHeader/UserHeader.test.tsx
new file mode 100644
index 0000000..90501f7
--- /dev/null
+++ b/src/components/UserHeader/UserHeader.test.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { screen } from "@testing-library/react";
+import renderConnected from "../../utilities/test/renderConnected";
+
+import UserHeader from "./UserHeader";
+
+const setup = (initialState = {}) =>
+ renderConnected({
+ ui: ,
+ initialState,
+ });
+
+describe("UserHeader", () => {
+ it("should display username", () => {
+ setup({
+ userProfile: {
+ username: "Steve",
+ },
+ });
+ const userHeaderText = screen.getByText("Logged in as Steve");
+ expect(userHeaderText).toBeInTheDocument();
+ });
+});
diff --git a/src/components/UserHeader/UserHeader.tsx b/src/components/UserHeader/UserHeader.tsx
new file mode 100644
index 0000000..8f6862b
--- /dev/null
+++ b/src/components/UserHeader/UserHeader.tsx
@@ -0,0 +1,11 @@
+import React, { FC } from "react";
+import { useSelector } from "react-redux";
+
+import { getUsername } from "../../state/userProfile/selectors";
+
+const UserHeader: FC = () => {
+ const userName = useSelector(getUsername);
+ return Logged in as {userName}
;
+};
+
+export default UserHeader;
diff --git a/src/state/userProfile/reducer.ts b/src/state/userProfile/reducer.ts
index 725a468..ec0751f 100644
--- a/src/state/userProfile/reducer.ts
+++ b/src/state/userProfile/reducer.ts
@@ -4,10 +4,10 @@ import { fetchUserById } from "./actions";
interface UserState {
name?: string;
- userName?: string;
+ username?: string;
}
-const initialState = {} as UserState;
+export const initialState = {} as UserState;
const userProfileReducer = createReducer(initialState, (builder) => {
builder.addCase(fetchUserById.fulfilled, (state, action) => {
diff --git a/src/state/userProfile/selectors.ts b/src/state/userProfile/selectors.ts
new file mode 100644
index 0000000..d12acfc
--- /dev/null
+++ b/src/state/userProfile/selectors.ts
@@ -0,0 +1,3 @@
+import { RootState } from "../rootReducer";
+
+export const getUsername = (state: RootState): string => state.userProfile.username;
diff --git a/src/utilities/test/renderConnected.tsx b/src/utilities/test/renderConnected.tsx
new file mode 100644
index 0000000..b0f5d48
--- /dev/null
+++ b/src/utilities/test/renderConnected.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import { render, RenderResult } from "@testing-library/react";
+import { Provider } from "react-redux";
+import configureStore, { MockStore } from "redux-mock-store";
+import thunk from "redux-thunk";
+
+interface RenderConnected {
+ ui: JSX.Element;
+ initialState: Record;
+ store?: MockStore;
+}
+
+interface RenderConnectedWithStore extends RenderResult {
+ store: MockStore;
+}
+
+const middleware = [thunk];
+const mockStore = configureStore(middleware);
+
+export const makeStore = (initialState: Record): MockStore =>
+ mockStore(initialState);
+
+const renderConnected = ({
+ ui,
+ initialState = {},
+ store = makeStore(initialState),
+}: RenderConnected): RenderConnectedWithStore => {
+ const rendered = render({ui});
+ return {
+ ...rendered,
+ store,
+ };
+};
+
+export default renderConnected;