Skip to content
Merged
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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
34 changes: 19 additions & 15 deletions src/components/App/App.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={store}>
<App />
</Provider>
);
return container;
};
const setup = (initialState = {}) =>
renderConnected({
ui: <App />,
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();
});
Expand Down
21 changes: 18 additions & 3 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
@@ -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 <Loader />;
}

return (
<div className="app bg-gray-50 min-h-screen">
<p>App</p>
<UserHeader />
</div>
);
};
Expand Down
9 changes: 9 additions & 0 deletions src/components/Loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { FC } from "react";

const Loader: FC = () => (
<div className="loader">
<p>Loading...</p>
</div>
);

export default Loader;
23 changes: 23 additions & 0 deletions src/components/UserHeader/UserHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -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: <UserHeader />,
initialState,
});

describe("UserHeader", () => {
it("should display username", () => {
setup({
userProfile: {
username: "Steve",
},
});
const userHeaderText = screen.getByText("Logged in as Steve");
expect(userHeaderText).toBeInTheDocument();
});
});
11 changes: 11 additions & 0 deletions src/components/UserHeader/UserHeader.tsx
Original file line number Diff line number Diff line change
@@ -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 <div className="user-profile">Logged in as {userName}</div>;
};

export default UserHeader;
4 changes: 2 additions & 2 deletions src/state/userProfile/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
3 changes: 3 additions & 0 deletions src/state/userProfile/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { RootState } from "../rootReducer";

export const getUsername = (state: RootState): string => state.userProfile.username;
35 changes: 35 additions & 0 deletions src/utilities/test/renderConnected.tsx
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
store?: MockStore;
}

interface RenderConnectedWithStore extends RenderResult {
store: MockStore;
}

const middleware = [thunk];
const mockStore = configureStore(middleware);

export const makeStore = (initialState: Record<string, unknown>): MockStore =>
mockStore(initialState);

const renderConnected = ({
ui,
initialState = {},
store = makeStore(initialState),
}: RenderConnected): RenderConnectedWithStore => {
const rendered = render(<Provider store={store}>{ui}</Provider>);
return {
...rendered,
store,
};
};

export default renderConnected;