Adds gSheet specific entity read

This commit is contained in:
forceoranj 2021-11-14 01:44:11 +01:00
parent 193f51b54d
commit c33b9d8f79
43 changed files with 546 additions and 418 deletions

View File

@ -313,7 +313,7 @@ function formulaSafe(value: string): string {
export { SCOPES }
class Test {
envieId = 5
id = 5
envies = ""
@ -348,7 +348,7 @@ class Test {
async function testGSheetAPi(): Promise<void> {
const dataset: Test[] = [
{
envieId: 1,
id: 1,
envies: "Présenter le festival et son organisation à un nouveau bénévol au téléphone",
dateAjout: new Date("2021-10-18T22:00:00.000Z"),
ignore: true,
@ -362,7 +362,7 @@ async function testGSheetAPi(): Promise<void> {
tictactoe: [true, false, true, false, false, true],
},
{
envieId: 5,
id: 5,
envies: "Créer de jolies pages webs",
dateAjout: new Date("2021-10-18T22:00:00.000Z"),
ignore: false,
@ -372,7 +372,7 @@ async function testGSheetAPi(): Promise<void> {
tictactoe: [],
},
{
envieId: 6,
id: 6,
envies: "Modérer un salon Discord",
dateAjout: new Date("2021-10-18T22:00:00.000Z"),
ignore: true,

View File

@ -1,27 +0,0 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import Info from "../index"
describe("<Info />", () => {
it("renders", () => {
const tree = render(
<MemoryRouter>
<Info
item={{
membreId: 1,
name: "PeL",
phone: "+886 0970...",
email: "forceoranj@gmail.com",
website: "https://www.parisestludique.fr",
}}
/>
</MemoryRouter>
).container.firstChild
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,29 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Info /> renders 1`] = `
<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>
`;

View File

@ -1,22 +0,0 @@
import { memo } from "react"
import { User } from "../../services/jsonPlaceholder"
import styles from "./styles.module.scss"
interface Props {
item: User
}
const Info = ({ item }: Props) => (
<div className={styles.UserCard}>
<h4>User Info</h4>
<ul>
<li>Name: {item.name}</li>
<li>Phone: {item.phone}</li>
<li>Email: {item.email}</li>
<li>Website: {item.website}</li>
</ul>
</div>
)
export default memo(Info)

View File

@ -5,7 +5,7 @@ import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import mockStore from "../../../utils/mockStore"
import List from "../index"
import JeuJavList from "../index"
describe("<List />", () => {
const renderHelper = (reducer = { readyStatus: "idle" }) => {
@ -13,7 +13,7 @@ describe("<List />", () => {
const { container } = render(
<ProviderWithStore>
<MemoryRouter>
<List ids={[5]} />
<JeuJavList ids={[5]} />
</MemoryRouter>
</ProviderWithStore>
)

View File

@ -10,7 +10,7 @@ interface Props {
ids: EntityId[]
}
const List = ({ ids }: Props) => {
const JeuJavList = ({ ids }: Props) => {
const { entities: jeuxJav } = useSelector((state: AppState) => state.jeuJavList, shallowEqual)
return (
<div className={styles.JeuJavList}>
@ -33,4 +33,4 @@ const List = ({ ids }: Props) => {
)
}
export default memo(List)
export default memo(JeuJavList)

View File

@ -1,29 +0,0 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import List from "../index"
describe("<List />", () => {
it("renders", () => {
const tree = render(
<MemoryRouter>
<List
items={[
{
membreId: 1,
name: "PeL",
phone: "+886 0970...",
email: "forceoranj@gmail.com",
website: "https://www.parisestludique.fr",
},
]}
/>
</MemoryRouter>
).container.firstChild
expect(tree).toMatchSnapshot()
})
})

View File

@ -1,20 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<List /> renders 1`] = `
<div
class="UserList"
>
<h4>
User List
</h4>
<ul>
<li>
<a
href="/UserInfo/1"
>
PeL
</a>
</li>
</ul>
</div>
`;

View File

@ -1,24 +0,0 @@
import { memo } from "react"
import { Link } from "react-router-dom"
import { User } from "../../services/jsonPlaceholder"
import styles from "./styles.module.scss"
interface Props {
items: User[]
}
const List = ({ items }: Props) => (
<div className={styles.UserList}>
<h4>User List</h4>
<ul>
{items.map(({ membreId, name }) => (
<li key={membreId}>
<Link to={`/UserInfo/${membreId}`}>{name}</Link>
</li>
))}
</ul>
</div>
)
export default memo(List)

View File

@ -0,0 +1,35 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import MembreInfo from "../index"
describe("<MembreInfo />", () => {
it("renders", () => {
const tree = render(
<MemoryRouter>
<MembreInfo
item={{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}}
/>
</MemoryRouter>
).container.firstChild
expect(tree).toMatchSnapshot()
})
})

View File

@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MembreInfo /> renders 1`] = `
<div
class="MembreCard"
>
<h4>
Membre Info
</h4>
<ul>
<li>
Prénom:
Amélie
</li>
<li>
Nom:
Aupeix
</li>
</ul>
</div>
`;

View File

@ -0,0 +1,20 @@
import { memo } from "react"
import { Membre } from "../../services/membres"
import styles from "./styles.module.scss"
interface Props {
item: Membre
}
const MembreInfo = ({ item }: Props) => (
<div className={styles.MembreCard}>
<h4>Membre Info</h4>
<ul>
<li>Prénom: {item.prenom}</li>
<li>Nom: {item.nom}</li>
</ul>
</div>
)
export default memo(MembreInfo)

View File

@ -1,4 +1,4 @@
.user-card {
.membre-card {
ul {
padding-left: 17px;

View File

@ -0,0 +1,37 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import MembreList from "../index"
describe("<MembreList />", () => {
it("renders", () => {
const tree = render(
<MemoryRouter>
<MembreList
items={[
{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
},
]}
/>
</MemoryRouter>
).container.firstChild
expect(tree).toMatchSnapshot()
})
})

View File

@ -0,0 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MembreList /> renders 1`] = `
<div
class="MembreList"
>
<h4>
Membre List
</h4>
<ul>
<li>
<a
href="/Membre/1"
>
<b>
Amélie
</b>
Aupeix
</a>
</li>
</ul>
</div>
`;

View File

@ -0,0 +1,26 @@
import { memo } from "react"
import { Link } from "react-router-dom"
import { Membre } from "../../services/membres"
import styles from "./styles.module.scss"
interface Props {
items: Membre[]
}
const MembreList = ({ items }: Props) => (
<div className={styles.MembreList}>
<h4>Membre List</h4>
<ul>
{items.map(({ id, nom, prenom }) => (
<li key={id}>
<Link to={`/Membre/${id}`}>
<b>{prenom}</b> {nom}
</Link>
</li>
))}
</ul>
</div>
)
export default memo(MembreList)

View File

@ -1,8 +1,8 @@
import List from "./List"
import MembreList from "./MembreList"
import JeuJavList from "./JeuJavList"
import Info from "./Info"
import MembreInfo from "./MembreInfo"
import ErrorBoundary from "./ErrorBoundary"
import Loading from "./Loading"
import AddEnvie from "./AddEnvie"
export { List, JeuJavList, Info, ErrorBoundary, Loading, AddEnvie }
export { MembreList, JeuJavList, MembreInfo, ErrorBoundary, Loading, AddEnvie }

View File

@ -23,7 +23,7 @@ export const addEnvie = async (
_next: NextFunction
): Promise<void> => {
try {
const envie = await add<EnvieWithoutId, Envie>("Envies d'aider", "envieId", request.body)
const envie = await add<EnvieWithoutId, Envie>("Envies d'aider", "id", request.body)
if (envie) {
response.status(200).json(envie)
}

47
src/gsheets/membres.ts Normal file
View File

@ -0,0 +1,47 @@
import { Request, Response, NextFunction } from "express"
import { getList, get, add } from "./utils"
import { Membre, MembreWithoutId } from "../services/membres"
export const getMembreList = async (
_request: Request,
response: Response,
_next: NextFunction
): Promise<void> => {
try {
const list = await getList<Membre>("Membres", new Membre())
if (list) {
response.status(200).json(list)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}
export const getMembre = async (
request: Request,
response: Response,
_next: NextFunction
): Promise<void> => {
try {
const id = parseInt(request.query.id as string, 10) || -1
const membre = await get<Membre>("Membres", id, new Membre())
response.status(200).json(membre)
} catch (e: unknown) {
response.status(400).json(e)
}
}
export const addMembre = async (
request: Request,
response: Response,
_next: NextFunction
): Promise<void> => {
try {
const membre = await add<MembreWithoutId, Membre>("Membres", "membreId", request.body)
if (membre) {
response.status(200).json(membre)
}
} catch (e: unknown) {
response.status(400).json(e)
}
}

View File

@ -6,8 +6,10 @@ import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from "google-spreadshee
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
type ElementWithId = unknown & { id: number }
// eslint-disable-next-line @typescript-eslint/ban-types
export async function getList<Element extends object>(
export async function getList<Element extends ElementWithId>(
sheetName: string,
specimen: Element
): Promise<Element[]> {
@ -37,7 +39,17 @@ export async function getList<Element extends object>(
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function setList<Element extends object>(
export async function get<Element extends ElementWithId>(
sheetName: string,
membreId: number,
specimen: Element
): Promise<Element | undefined> {
const list = await getList<Element>(sheetName, specimen)
return list.find((element) => element.id === membreId)
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function setList<Element extends ElementWithId>(
sheetName: string,
elements: Element[]
): Promise<true | undefined> {
@ -90,7 +102,7 @@ export async function setList<Element extends object>(
}
// eslint-disable-next-line @typescript-eslint/ban-types
export async function add<ElementNoId extends object, Element extends ElementNoId>(
export async function add<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
sheetName: string,
idFieldName: string,
partialElement: Partial<ElementNoId>

View File

@ -0,0 +1,48 @@
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 { fetchMembreDataIfNeed } from "../../store/membre"
import { MembreInfo } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps<{ id: string }>
const MembrePage = ({ match }: Props): JSX.Element => {
const { id: rawId } = match.params
const id = +rawId
const dispatch = useDispatch()
const membre = useSelector((state: AppState) => state.membre, shallowEqual)
useEffect(() => {
dispatch(fetchMembreDataIfNeed(id))
}, [dispatch, id])
const renderInfo = () => {
const membreInfo = membre
if (!membreInfo || membreInfo.readyStatus === "request") return <p>Loading...</p>
if (membreInfo.readyStatus === "failure" || !membreInfo.entity)
return <p>Oops! Failed to load data.</p>
return <MembreInfo item={membreInfo.entity} />
}
return (
<div className={styles.Membre}>
<Helmet title="User Info" />
{renderInfo()}
</div>
)
}
interface LoadDataArgs {
params: { id: number }
}
export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchMembreDataIfNeed(params.id)]
export default memo(MembrePage)

View File

@ -4,27 +4,36 @@
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import { fetchMembreDataIfNeed } from "../../../store/membre"
import mockStore from "../../../utils/mockStore"
import UserInfo from "../UserInfo"
import MembrePage from "../MembrePage"
describe("<UserInfo />", () => {
describe("<MembrePage />", () => {
const mockData = {
memberId: 1,
name: "PeL",
phone: "+886 0970...",
email: "forceoranj@gmail.com",
website: "https://www.parisestludique.fr",
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const { memberId } = mockData
const { id } = mockData
const renderHelper = (reducer = {}) => {
const { dispatch, ProviderWithStore } = mockStore({ userData: reducer })
const { dispatch, ProviderWithStore } = mockStore({ membre: reducer })
const { container } = render(
<ProviderWithStore>
<MemoryRouter>
{/*
@ts-expect-error */}
<UserInfo match={{ params: { memberId } }} />
<MembrePage match={{ params: { id } }} />
</MemoryRouter>
</ProviderWithStore>
)
@ -32,6 +41,13 @@ describe("<UserInfo />", () => {
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(fetchMembreDataIfNeed(id).toString())
})
it("renders the loading status if data invalid", () => {
expect(renderHelper().firstChild).toMatchSnapshot()
})

View File

@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MembrePage /> renders an error if loading failed 1`] = `
<div
class="Membre"
>
<p>
Oops! Failed to load data.
</p>
</div>
`;
exports[`<MembrePage /> renders the <Info /> if loading was successful 1`] = `
<div
class="Membre"
>
<div
class="MembreCard"
>
<h4>
Membre Info
</h4>
<ul>
<li>
Prénom:
Amélie
</li>
<li>
Nom:
Aupeix
</li>
</ul>
</div>
</div>
`;
exports[`<MembrePage /> renders the loading status if data invalid 1`] = `
<div
class="Membre"
>
<p>
Oops! Failed to load data.
</p>
</div>
`;
exports[`<MembrePage /> renders the loading status if requesting data 1`] = `
<div
class="Membre"
>
<p>
Loading...
</p>
</div>
`;

View File

@ -1,15 +1,15 @@
import loadable from "@loadable/component"
import { Loading, ErrorBoundary } from "../../components"
import { Props, loadData } from "./UserInfo"
import { Props, loadData } from "./MembrePage"
const UserInfo = loadable(() => import("./UserInfo"), {
const MembrePage = loadable(() => import("./MembrePage"), {
fallback: <Loading />,
})
export default (props: Props): JSX.Element => (
<ErrorBoundary>
<UserInfo {...props} />
<MembrePage {...props} />
</ErrorBoundary>
)
export { loadData }

View File

@ -1,3 +1,3 @@
.UserInfo {
.Membre {
padding: 0 15px;
}

View File

@ -1,48 +0,0 @@
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 { fetchUserDataIfNeed } from "../../store/userData"
import { Info } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps<{ memberId: string }>
const UserInfo = ({ match }: Props): JSX.Element => {
const { memberId: rawId } = match.params
const id = +rawId
const dispatch = useDispatch()
const userData = useSelector((state: AppState) => state.userData, shallowEqual)
useEffect(() => {
dispatch(fetchUserDataIfNeed(id))
}, [dispatch, id])
const renderInfo = () => {
const userInfo = userData
if (!userInfo || userInfo.readyStatus === "request") return <p>Loading...</p>
if (userInfo.readyStatus === "failure" || !userInfo.entity)
return <p>Oops! Failed to load data.</p>
return <Info item={userInfo.entity} />
}
return (
<div className={styles.UserInfo}>
<Helmet title="User Info" />
{renderInfo()}
</div>
)
}
interface LoadDataArgs {
params: { id: number }
}
export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchUserDataIfNeed(params.id)]
export default memo(UserInfo)

View File

@ -1,63 +0,0 @@
// 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>
Oops! Failed to load data.
</p>
</div>
`;
exports[`<UserInfo /> renders the loading status if requesting data 1`] = `
<div
class="UserInfo"
>
<p>
Loading...
</p>
</div>
`;

View File

@ -2,7 +2,7 @@ import { RouteConfig } from "react-router-config"
import App from "../app"
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
import AsyncUserInfo, { loadData as loadUserInfoData } from "../pages/UserInfo"
import AsyncMembrePage, { loadData as loadMembrePageData } from "../pages/MembrePage"
import NotFound from "../pages/NotFound"
export default [
@ -16,9 +16,9 @@ export default [
loadData: loadHomeData, // Add your pre-fetch method here
},
{
path: "/UserInfo/:id",
component: AsyncUserInfo,
loadData: loadUserInfoData,
path: "/MembrePage/:id",
component: AsyncMembrePage,
loadData: loadMembrePageData,
},
{
component: NotFound,

View File

@ -12,6 +12,7 @@ import ssr from "./ssr"
import { getJeuJavList } from "../gsheets/jeuJav"
import { getEnvieList, addEnvie } from "../gsheets/envies"
import { getMembre } from "../gsheets/membres"
import config from "../config"
const app = express()
@ -33,8 +34,9 @@ if (__DEV__) devServer(app)
// Google Sheets requests
app.use(express.json())
app.get("/JeuJav", getJeuJavList)
app.get("/JeuJavList", getJeuJavList)
app.get("/GetEnvieList", getEnvieList)
app.get("/GetMembre", getMembre)
app.post("/AddEnvie", addEnvie)
// Use React server-side rendering middleware

View File

@ -3,7 +3,7 @@ import axios from "axios"
import config from "../config"
export class Envie {
envieId = 0
id = 0
domaine = ""
@ -15,7 +15,7 @@ export class Envie {
dateAjout = ""
}
export type EnvieWithoutId = Omit<Envie, "envieId">
export type EnvieWithoutId = Omit<Envie, "id">
export interface GetEnvieListResponse {
data?: Envie[]

View File

@ -3,7 +3,7 @@ import axios from "axios"
import config from "../config"
export class JeuJav {
jeuId = 0
id = 0
titre = ""
@ -46,7 +46,7 @@ export interface JeuJavData {
export const getJeuJavList = async (): Promise<JeuJavList> => {
try {
const { data } = await axios.get(`${config.API_URL}/JeuJav`)
const { data } = await axios.get(`${config.API_URL}/JeuJavList`)
return { data }
} catch (error) {
return { error: error as Error }
@ -55,7 +55,7 @@ export const getJeuJavList = async (): Promise<JeuJavList> => {
export const getJeuJavData = async (id: string): Promise<JeuJavData> => {
try {
const { data } = await axios.get(`${config.API_URL}/users/${id}`)
const { data } = await axios.get(`${config.API_URL}/JeuJav`, { params: { id } })
return { data }
} catch (error) {
return { error: error as Error }

View File

@ -1,37 +0,0 @@
import axios from "axios"
export interface User {
membreId: number
name: string
phone: string
email: string
website: string
}
interface UserList {
data?: User[]
error?: Error
}
interface UserData {
data?: User
error?: Error
}
export const getUserList = async (): Promise<UserList> => {
try {
const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users`)
return { data }
} catch (error) {
return { error: error as Error }
}
}
export const getUserData = async (id: number): Promise<UserData> => {
try {
const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`)
return { data }
} catch (error) {
return { error: error as Error }
}
}

71
src/services/membres.ts Normal file
View File

@ -0,0 +1,71 @@
import axios from "axios"
import config from "../config"
export class Membre {
id = 0
nom = ""
prenom = ""
mail = ""
telephone = ""
photo = ""
alimentation = ""
majeur = 1
privilege = 0
actif = 0
commentaire = ""
horodatage = ""
passe = ""
}
export type MembreWithoutId = Omit<Membre, "membreId">
export interface GetMembreListResponse {
data?: Membre[]
error?: Error
}
export const getMembreList = async (): Promise<GetMembreListResponse> => {
try {
const { data } = await axios.get(`${config.API_URL}/GetMembreList`)
return { data }
} catch (error) {
return { error: error as Error }
}
}
export interface GetMembreResponse {
data?: Membre
error?: Error
}
export const getMembre = async (id: number): Promise<GetMembreResponse> => {
try {
const { data } = await axios.get(`${config.API_URL}/GetMembre`, { params: { id } })
return { data }
} catch (error) {
return { error: error as Error }
}
}
export interface AddMembreResponse {
data?: Membre
error?: Error
}
export const addMembre = async (membreWithoutId: MembreWithoutId): Promise<AddMembreResponse> => {
try {
const { data } = await axios.post(`${config.API_URL}/AddMembre`, membreWithoutId)
return { data }
} catch (error) {
return { error: error as Error }
}
}

View File

@ -13,7 +13,7 @@ jest.mock("axios")
const mockData = {
"5": {
jeuId: 5,
id: 5,
titre: "6 qui prend!",
auteur: "Wolfgang Kramer",
editeur: "(uncredited) , Design Edge , B",

View File

@ -1,41 +1,49 @@
import axios from "axios"
import mockStore from "../../utils/mockStore"
import userData, {
import membre, {
getRequesting,
getSuccess,
getFailure,
fetchUserData,
fetchMembreData,
initialState,
} from "../userData"
} from "../membre"
jest.mock("axios")
const mockData = {
membreId: 1,
name: "PeL",
phone: "+886 0970...",
email: "forceoranj@gmail.com",
website: "https://www.parisestludique.fr",
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const { membreId } = mockData
const { id } = mockData
const mockError = "Oops! Something went wrong."
describe("userData reducer", () => {
describe("membre reducer", () => {
it("should handle initial state correctly", () => {
// @ts-expect-error
expect(userData(undefined, {})).toEqual(initialState)
expect(membre(undefined, {})).toEqual(initialState)
})
it("should handle requesting correctly", () => {
expect(userData(undefined, { type: getRequesting.type, payload: membreId })).toEqual({
expect(membre(undefined, { type: getRequesting.type, payload: id })).toEqual({
readyStatus: "request",
})
})
it("should handle success correctly", () => {
expect(
userData(undefined, {
membre(undefined, {
type: getSuccess.type,
payload: mockData,
})
@ -44,7 +52,7 @@ describe("userData reducer", () => {
it("should handle failure correctly", () => {
expect(
userData(undefined, {
membre(undefined, {
type: getFailure.type,
payload: mockError,
})
@ -52,8 +60,8 @@ describe("userData reducer", () => {
})
})
describe("userData action", () => {
it("fetches user data successful", async () => {
describe("membre action", () => {
it("fetches membre data successful", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
@ -63,11 +71,11 @@ describe("userData action", () => {
// @ts-expect-error
axios.get.mockResolvedValue({ data: mockData })
await dispatch(fetchUserData(membreId))
await dispatch(fetchMembreData(id))
expect(getActions()).toEqual(expectedActions)
})
it("fetches user data failed", async () => {
it("fetches membre data failed", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
@ -77,7 +85,7 @@ describe("userData action", () => {
// @ts-expect-error
axios.get.mockRejectedValue({ message: mockError })
await dispatch(fetchUserData(membreId))
await dispatch(fetchMembreData(id))
expect(getActions()).toEqual(expectedActions)
})
})

View File

@ -1,35 +1,43 @@
import axios from "axios"
import mockStore from "../../utils/mockStore"
import userList, {
import membreList, {
initialState,
getRequesting,
getSuccess,
getFailure,
fetchUserList,
} from "../userList"
fetchMembreList,
} from "../membreList"
jest.mock("axios")
const mockData = {
"1": {
membreId: 1,
name: "PeL",
phone: "+886 0970...",
email: "forceoranj@gmail.com",
website: "https://www.parisestludique.fr",
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
},
}
const mockError = "Oops! Something went wrong."
describe("userList reducer", () => {
describe("membreList reducer", () => {
it("should handle initial state", () => {
// @ts-expect-error
expect(userList(undefined, {})).toEqual(initialState)
expect(membreList(undefined, {})).toEqual(initialState)
})
it("should handle requesting correctly", () => {
expect(userList(undefined, { type: getRequesting.type })).toEqual({
expect(membreList(undefined, { type: getRequesting.type })).toEqual({
readyStatus: "request",
ids: [],
entities: {},
@ -37,7 +45,7 @@ describe("userList reducer", () => {
})
it("should handle success correctly", () => {
expect(userList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({
expect(membreList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({
...initialState,
readyStatus: "success",
ids: [1],
@ -46,7 +54,7 @@ describe("userList reducer", () => {
})
it("should handle failure correctly", () => {
expect(userList(undefined, { type: getFailure.type, payload: mockError })).toEqual({
expect(membreList(undefined, { type: getFailure.type, payload: mockError })).toEqual({
...initialState,
readyStatus: "failure",
error: mockError,
@ -54,8 +62,8 @@ describe("userList reducer", () => {
})
})
describe("userList action", () => {
it("fetches user list successful", async () => {
describe("membreList action", () => {
it("fetches membre list successful", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
@ -65,11 +73,11 @@ describe("userList action", () => {
// @ts-expect-error
axios.get.mockResolvedValue({ data: mockData })
await dispatch(fetchUserList())
await dispatch(fetchMembreList())
expect(getActions()).toEqual(expectedActions)
})
it("fetches user list failed", async () => {
it("fetches membre list failed", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
@ -79,7 +87,7 @@ describe("userList action", () => {
// @ts-expect-error
axios.get.mockRejectedValue({ message: mockError })
await dispatch(fetchUserList())
await dispatch(fetchMembreList())
expect(getActions()).toEqual(expectedActions)
})
})

View File

@ -6,7 +6,7 @@ import { Envie, EnvieWithoutId, addEnvie } from "../services/envies"
import { AppThunk } from "."
const envieAdapter = createEntityAdapter<Envie>({
selectId: (envie) => envie.envieId,
selectId: (envie) => envie.id,
})
const envieAdd = createSlice({

View File

@ -6,7 +6,7 @@ import { Envie, getEnvieList } from "../services/envies"
import { AppThunk, AppState } from "."
const envieAdapter = createEntityAdapter<Envie>({
selectId: (envie) => envie.envieId,
selectId: (envie) => envie.id,
})
const envieList = createSlice({

View File

@ -6,7 +6,7 @@ import { JeuJav, getJeuJavList } from "../services/jeuJav"
import { AppThunk, AppState } from "."
const jeuJavAdapter = createEntityAdapter<JeuJav>({
selectId: (jeuJav) => jeuJav.jeuId,
selectId: (jeuJav) => jeuJav.id,
})
export const initialState = jeuJavAdapter.getInitialState({

View File

@ -2,23 +2,23 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { toast } from "react-toastify"
import { StateRequest } from "./utils"
import { User, getUserData } from "../services/jsonPlaceholder"
import { Membre, getMembre } from "../services/membres"
import { AppThunk, AppState } from "."
type StateUser = { entity?: User } & StateRequest
type StateMembre = { entity?: Membre } & StateRequest
export const initialState: StateUser = {
export const initialState: StateMembre = {
readyStatus: "idle",
}
const userData = createSlice({
name: "userData",
const membre = createSlice({
name: "membre",
initialState,
reducers: {
getRequesting: (_) => ({
readyStatus: "request",
}),
getSuccess: (_, { payload }: PayloadAction<User>) => ({
getSuccess: (_, { payload }: PayloadAction<Membre>) => ({
readyStatus: "success",
entity: payload,
}),
@ -29,19 +29,19 @@ const userData = createSlice({
},
})
export default userData.reducer
export const { getRequesting, getSuccess, getFailure } = userData.actions
export default membre.reducer
export const { getRequesting, getSuccess, getFailure } = membre.actions
export const fetchUserData =
export const fetchMembreData =
(id: number): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
const { error, data } = await getUserData(id)
const { error, data } = await getMembre(id)
if (error) {
dispatch(getFailure(error.message))
toast.error(`Erreur lors du chargement de l'utilisateur ${id}: ${error.message}`, {
toast.error(`Erreur lors du chargement du membre ${id}: ${error.message}`, {
position: "top-center",
autoClose: 6000,
hideProgressBar: true,
@ -51,18 +51,17 @@ export const fetchUserData =
progress: undefined,
})
} else {
dispatch(getSuccess(data as User))
dispatch(getSuccess(data as Membre))
}
}
const shouldFetchUserData = (state: AppState, id: number) =>
state.userData.readyStatus !== "success" ||
(state.userData.entity && state.userData.entity.membreId !== id)
const shouldFetchMembreData = (state: AppState, id: number) =>
state.membre.readyStatus !== "success" || (state.membre.entity && state.membre.entity.id !== id)
export const fetchUserDataIfNeed =
export const fetchMembreDataIfNeed =
(id: number): AppThunk =>
(dispatch, getState) => {
if (shouldFetchUserData(getState(), id)) return dispatch(fetchUserData(id))
if (shouldFetchMembreData(getState(), id)) return dispatch(fetchMembreData(id))
return null
}

View File

@ -2,27 +2,25 @@ import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolki
import { toast } from "react-toastify"
import { StateRequest } from "./utils"
import { User, getUserList } from "../services/jsonPlaceholder"
import { Membre, getMembreList } from "../services/membres"
import { AppThunk, AppState } from "."
const userAdapter = createEntityAdapter<User>({
selectId: (user) => user.membreId,
})
const membreAdapter = createEntityAdapter<Membre>()
export const initialState = userAdapter.getInitialState({
export const initialState = membreAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const userList = createSlice({
name: "userList",
const membreList = createSlice({
name: "membreList",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<User[]>) => {
getSuccess: (state, { payload }: PayloadAction<Membre[]>) => {
state.readyStatus = "success"
userAdapter.setAll(state, payload)
membreAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
@ -31,13 +29,13 @@ const userList = createSlice({
},
})
export default userList.reducer
export const { getRequesting, getSuccess, getFailure } = userList.actions
export default membreList.reducer
export const { getRequesting, getSuccess, getFailure } = membreList.actions
export const fetchUserList = (): AppThunk => async (dispatch) => {
export const fetchMembreList = (): AppThunk => async (dispatch) => {
dispatch(getRequesting())
const { error, data } = await getUserList()
const { error, data } = await getMembreList()
if (error) {
dispatch(getFailure(error.message))
@ -51,14 +49,14 @@ export const fetchUserList = (): AppThunk => async (dispatch) => {
progress: undefined,
})
} else {
dispatch(getSuccess(data as User[]))
dispatch(getSuccess(data as Membre[]))
}
}
const shouldFetchUserList = (state: AppState) => state.userList.readyStatus !== "success"
const shouldFetchMembreList = (state: AppState) => state.membreList.readyStatus !== "success"
export const fetchUserListIfNeed = (): AppThunk => (dispatch, getState) => {
if (shouldFetchUserList(getState())) return dispatch(fetchUserList())
export const fetchMembreListIfNeed = (): AppThunk => (dispatch, getState) => {
if (shouldFetchMembreList(getState())) return dispatch(fetchMembreList())
return null
}

View File

@ -1,16 +1,16 @@
import { History } from "history"
import { connectRouter } from "connected-react-router"
import userList from "./userList"
import userData from "./userData"
import membreList from "./membreList"
import membre from "./membre"
import jeuJavList from "./jeuJavList"
import envieList from "./envieList"
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default (history: History) => ({
userList,
userData,
membreList,
membre,
jeuJavList,
envieList,
router: connectRouter(history) as any,