mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-09-11 22:06:29 +02:00
Initial commit
This commit is contained in:
46
src/pages/Home/Home.tsx
Executable file
46
src/pages/Home/Home.tsx
Executable file
@@ -0,0 +1,46 @@
|
||||
import { FC, useEffect, memo } from "react"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppState, AppThunk } from "../../store"
|
||||
import { fetchJavGameListIfNeed } from "../../store/javGameList"
|
||||
import { JavGameList } from "../../components"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
|
||||
function useList(stateToProp: (state: AppState) => any, fetchDataIfNeed: () => AppThunk) {
|
||||
const dispatch = useDispatch()
|
||||
const { readyStatus, items } = useSelector(stateToProp, shallowEqual)
|
||||
|
||||
// Fetch client-side data here
|
||||
useEffect(() => {
|
||||
dispatch(fetchDataIfNeed())
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch])
|
||||
|
||||
return () => {
|
||||
if (!readyStatus || readyStatus === "invalid" || readyStatus === "request")
|
||||
return <p>Loading...</p>
|
||||
|
||||
if (readyStatus === "failure") return <p>Oops, Failed to load list!</p>
|
||||
|
||||
return <JavGameList items={items} />
|
||||
}
|
||||
}
|
||||
|
||||
const Home: FC<Props> = (): JSX.Element => (
|
||||
<div className={styles.Home}>
|
||||
<Helmet title="Home" />
|
||||
{useList((state: AppState) => state.javGameList, fetchJavGameListIfNeed)()}
|
||||
</div>
|
||||
)
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => [
|
||||
fetchJavGameListIfNeed(),
|
||||
// More pre-fetched actions...
|
||||
]
|
||||
|
||||
export default memo(Home)
|
78
src/pages/Home/__tests__/Home.tsx
Executable file
78
src/pages/Home/__tests__/Home.tsx
Executable file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render } from "@testing-library/react"
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
|
||||
import { fetchJavGameListIfNeed } from "../../../store/javGameList"
|
||||
import mockStore from "../../../utils/mockStore"
|
||||
import Home from "../Home"
|
||||
|
||||
describe("<Home />", () => {
|
||||
const renderHelper = (reducer = { readyStatus: "invalid" }) => {
|
||||
const { dispatch, ProviderWithStore } = mockStore({ javGameList: reducer })
|
||||
const { container } = render(
|
||||
<ProviderWithStore>
|
||||
<MemoryRouter>
|
||||
{/*
|
||||
@ts-expect-error */}
|
||||
<Home />
|
||||
</MemoryRouter>
|
||||
</ProviderWithStore>
|
||||
)
|
||||
|
||||
return { dispatch, firstChild: container.firstChild }
|
||||
}
|
||||
|
||||
it("should fetch data when page loaded", () => {
|
||||
const { dispatch } = renderHelper()
|
||||
|
||||
expect(dispatch).toHaveBeenCalledTimes(1)
|
||||
expect(dispatch.mock.calls[0][0].toString()).toBe(fetchJavGameListIfNeed().toString())
|
||||
})
|
||||
|
||||
it("renders the loading status if data invalid", () => {
|
||||
expect(renderHelper().firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders the loading status if requesting data", () => {
|
||||
const reducer = { readyStatus: "request" }
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders an error if loading failed", () => {
|
||||
const reducer = { readyStatus: "failure" }
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders the <List /> if loading was successful", () => {
|
||||
const reducer = {
|
||||
readyStatus: "success",
|
||||
items: [
|
||||
{
|
||||
id: 5,
|
||||
titre: "6 qui prend!",
|
||||
auteur: "Wolfgang Kramer",
|
||||
editeur: "(uncredited) , Design Edge , B",
|
||||
minJoueurs: 2,
|
||||
maxJoueurs: 10,
|
||||
duree: 45,
|
||||
type: "Ambiance",
|
||||
poufpaf: "0-9-2/6-qui-prend-6-nimmt",
|
||||
photo: "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
|
||||
bggPhoto: "",
|
||||
bggId: 432,
|
||||
exemplaires: 1,
|
||||
dispoPret: 1,
|
||||
nonRangee: 0,
|
||||
horodatage: "0000-00-00",
|
||||
ean: "3421272101313",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
})
|
54
src/pages/Home/__tests__/__snapshots__/Home.tsx.snap
Normal file
54
src/pages/Home/__tests__/__snapshots__/Home.tsx.snap
Normal file
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Home /> renders an error if loading failed 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<p>
|
||||
Oops, Failed to load list!
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Home /> renders the <List /> if loading was successful 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<div
|
||||
class="JavGameList"
|
||||
>
|
||||
<h4>
|
||||
JAV Games
|
||||
</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="/UserInfo/5"
|
||||
>
|
||||
6 qui prend!
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Home /> renders the loading status if data invalid 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Home /> renders the loading status if requesting data 1`] = `
|
||||
<div
|
||||
class="Home"
|
||||
>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
`;
|
15
src/pages/Home/index.tsx
Executable file
15
src/pages/Home/index.tsx
Executable file
@@ -0,0 +1,15 @@
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
import { Loading, ErrorBoundary } from "../../components"
|
||||
import { Props, loadData } from "./Home"
|
||||
|
||||
const Home = loadable(() => import("./Home"), {
|
||||
fallback: <Loading />,
|
||||
})
|
||||
|
||||
export default (props: Props): JSX.Element => (
|
||||
<ErrorBoundary>
|
||||
<Home {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
export { loadData }
|
3
src/pages/Home/styles.module.scss
Executable file
3
src/pages/Home/styles.module.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.Home {
|
||||
padding: 0 15px;
|
||||
}
|
21
src/pages/NotFound/__tests__/NotFound.tsx
Executable file
21
src/pages/NotFound/__tests__/NotFound.tsx
Executable file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render } from "@testing-library/react"
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
|
||||
import NotFound from "../index"
|
||||
|
||||
describe("<NotFound />", () => {
|
||||
it("renders", () => {
|
||||
const tree = render(
|
||||
<MemoryRouter>
|
||||
{/*
|
||||
@ts-expect-error */}
|
||||
<NotFound />
|
||||
</MemoryRouter>
|
||||
).container.firstChild
|
||||
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
11
src/pages/NotFound/__tests__/__snapshots__/NotFound.tsx.snap
Normal file
11
src/pages/NotFound/__tests__/__snapshots__/NotFound.tsx.snap
Normal file
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<NotFound /> renders 1`] = `
|
||||
<div
|
||||
class="NotFound"
|
||||
>
|
||||
<p>
|
||||
Oops, Page was not found!
|
||||
</p>
|
||||
</div>
|
||||
`;
|
23
src/pages/NotFound/index.tsx
Executable file
23
src/pages/NotFound/index.tsx
Executable file
@@ -0,0 +1,23 @@
|
||||
import { memo } from "react"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
type Props = RouteComponentProps
|
||||
|
||||
const NotFound = ({ staticContext }: Props) => {
|
||||
// We have to check if staticContext exists
|
||||
// because it will be undefined if rendered through a BrowserRoute
|
||||
/* istanbul ignore next */
|
||||
if (staticContext) staticContext.statusCode = 404
|
||||
|
||||
return (
|
||||
<div className={styles.NotFound}>
|
||||
<Helmet title="Oops" />
|
||||
<p>Oops, Page was not found!</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NotFound)
|
3
src/pages/NotFound/styles.module.scss
Executable file
3
src/pages/NotFound/styles.module.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.NotFound {
|
||||
padding: 0 15px;
|
||||
}
|
47
src/pages/UserInfo/UserInfo.tsx
Executable file
47
src/pages/UserInfo/UserInfo.tsx
Executable file
@@ -0,0 +1,47 @@
|
||||
import { useEffect, memo } from "react"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppState, AppThunk } from "../../store"
|
||||
import { User } from "../../services/jsonPlaceholder"
|
||||
import { fetchUserDataIfNeed } from "../../store/userData"
|
||||
import { Info } from "../../components"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
export type Props = RouteComponentProps<{ id: string }>
|
||||
|
||||
const UserInfo = ({ match }: Props): JSX.Element => {
|
||||
const { id } = match.params
|
||||
const dispatch = useDispatch()
|
||||
const userData = useSelector((state: AppState) => state.userData, shallowEqual)
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchUserDataIfNeed(id))
|
||||
}, [dispatch, id])
|
||||
|
||||
const renderInfo = () => {
|
||||
const userInfo = userData[id]
|
||||
|
||||
if (!userInfo || userInfo.readyStatus === "request") return <p>Loading...</p>
|
||||
|
||||
if (userInfo.readyStatus === "failure") return <p>Oops! Failed to load data.</p>
|
||||
|
||||
return <Info item={userInfo.item as User} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.UserInfo}>
|
||||
<Helmet title="User Info" />
|
||||
{renderInfo()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface LoadDataArgs {
|
||||
params: { id: string }
|
||||
}
|
||||
|
||||
export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchUserDataIfNeed(params.id)]
|
||||
|
||||
export default memo(UserInfo)
|
66
src/pages/UserInfo/__tests__/UserInfo.tsx
Executable file
66
src/pages/UserInfo/__tests__/UserInfo.tsx
Executable file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render } from "@testing-library/react"
|
||||
import { MemoryRouter } from "react-router-dom"
|
||||
|
||||
import { fetchUserDataIfNeed } from "../../../store/userData"
|
||||
import mockStore from "../../../utils/mockStore"
|
||||
import UserInfo from "../UserInfo"
|
||||
|
||||
describe("<UserInfo />", () => {
|
||||
const mockData = {
|
||||
id: "1",
|
||||
name: "PeL",
|
||||
phone: "+886 0970...",
|
||||
email: "forceoranj@gmail.com",
|
||||
website: "https://www.parisestludique.fr",
|
||||
}
|
||||
const { id } = mockData
|
||||
|
||||
const renderHelper = (reducer = {}) => {
|
||||
const { dispatch, ProviderWithStore } = mockStore({ userData: reducer })
|
||||
const { container } = render(
|
||||
<ProviderWithStore>
|
||||
<MemoryRouter>
|
||||
{/*
|
||||
@ts-expect-error */}
|
||||
<UserInfo match={{ params: { id } }} />
|
||||
</MemoryRouter>
|
||||
</ProviderWithStore>
|
||||
)
|
||||
|
||||
return { dispatch, firstChild: container.firstChild }
|
||||
}
|
||||
|
||||
it("should fetch data when page loaded", () => {
|
||||
const { dispatch } = renderHelper()
|
||||
|
||||
expect(dispatch).toHaveBeenCalledTimes(1)
|
||||
expect(dispatch.mock.calls[0][0].toString()).toBe(
|
||||
fetchUserDataIfNeed(id.toString()).toString()
|
||||
)
|
||||
})
|
||||
|
||||
it("renders the loading status if data invalid", () => {
|
||||
expect(renderHelper().firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders the loading status if requesting data", () => {
|
||||
const reducer = { [id]: { readyStatus: "request" } }
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders an error if loading failed", () => {
|
||||
const reducer = { [id]: { readyStatus: "failure" } }
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("renders the <Info /> if loading was successful", () => {
|
||||
const reducer = { [id]: { readyStatus: "success", item: mockData } }
|
||||
|
||||
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
|
||||
})
|
||||
})
|
63
src/pages/UserInfo/__tests__/__snapshots__/UserInfo.tsx.snap
Normal file
63
src/pages/UserInfo/__tests__/__snapshots__/UserInfo.tsx.snap
Normal file
@@ -0,0 +1,63 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UserInfo /> renders an error if loading failed 1`] = `
|
||||
<div
|
||||
class="UserInfo"
|
||||
>
|
||||
<p>
|
||||
Oops! Failed to load data.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfo /> renders the <Info /> if loading was successful 1`] = `
|
||||
<div
|
||||
class="UserInfo"
|
||||
>
|
||||
<div
|
||||
class="UserCard"
|
||||
>
|
||||
<h4>
|
||||
User Info
|
||||
</h4>
|
||||
<ul>
|
||||
<li>
|
||||
Name:
|
||||
PeL
|
||||
</li>
|
||||
<li>
|
||||
Phone:
|
||||
+886 0970...
|
||||
</li>
|
||||
<li>
|
||||
Email:
|
||||
forceoranj@gmail.com
|
||||
</li>
|
||||
<li>
|
||||
Website:
|
||||
https://www.parisestludique.fr
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfo /> renders the loading status if data invalid 1`] = `
|
||||
<div
|
||||
class="UserInfo"
|
||||
>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<UserInfo /> renders the loading status if requesting data 1`] = `
|
||||
<div
|
||||
class="UserInfo"
|
||||
>
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
`;
|
15
src/pages/UserInfo/index.tsx
Executable file
15
src/pages/UserInfo/index.tsx
Executable file
@@ -0,0 +1,15 @@
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
import { Loading, ErrorBoundary } from "../../components"
|
||||
import { Props, loadData } from "./UserInfo"
|
||||
|
||||
const UserInfo = loadable(() => import("./UserInfo"), {
|
||||
fallback: <Loading />,
|
||||
})
|
||||
|
||||
export default (props: Props): JSX.Element => (
|
||||
<ErrorBoundary>
|
||||
<UserInfo {...props} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
export { loadData }
|
3
src/pages/UserInfo/styles.module.scss
Executable file
3
src/pages/UserInfo/styles.module.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.UserInfo {
|
||||
padding: 0 15px;
|
||||
}
|
Reference in New Issue
Block a user