Skip to content

Commit 798b444

Browse files
committed
chore: refactor
1 parent 6a57a70 commit 798b444

33 files changed

+450
-13728
lines changed

Diff for: .husky/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_

Diff for: .husky/commit-msg

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
yarn commitlint --edit

Diff for: .husky/pre-commit

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
npx lint-staged

Diff for: README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# 2021 必修:React + React Hook + TS 最佳实践仿 Jira 企业级项目
22

3-
Hi,同学们好,感谢大家的信任购买这个课程。这个代码仓库的进度会领先课程进度,查看课程对应代码请 checkout 到对应的 commit
3+
Hi,同学们好,感谢大家的信任购买这个课程。查看课程对应代码请 checkout 到对应的 commit
44

55
这里是配套课件的链接:[点击我查看配套课件](https://www.notion.so/React-491ad0643476437cafde50bee4dde6ed)
6-
7-
有任何问题及时和我反馈,再次感谢!

Diff for: package.json

+8-11
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"test": "craco test",
3939
"eject": "react-scripts eject",
4040
"predeploy": "npm run build",
41-
"deploy":"gh-pages -d build -r [email protected]:sindu12jun/sindu12jun.github.io.git -b main"
41+
"deploy": "gh-pages -d build -r [email protected]:sindu12jun/sindu12jun.github.io.git -b main",
42+
"prepare": "husky install"
4243
},
4344
"eslintConfig": {
4445
"rules": {
@@ -63,22 +64,18 @@
6364
]
6465
},
6566
"devDependencies": {
66-
"@commitlint/cli": "^11.0.0",
67-
"@commitlint/config-conventional": "^11.0.0",
67+
"@commitlint/cli": "^12.1.1",
68+
"@commitlint/config-conventional": "^12.1.1",
69+
"@testing-library/react-hooks": "^5.1.1",
6870
"@types/qs": "^6.9.5",
6971
"@types/react-beautiful-dnd": "^13.0.0",
7072
"@types/react-helmet": "^6.1.0",
7173
"eslint-config-prettier": "^6.15.0",
72-
"husky": ">=4",
74+
"husky": ">=6",
7375
"json-server": "^0.16.2",
7476
"lint-staged": ">=10",
75-
"prettier": "2.1.2"
76-
},
77-
"husky": {
78-
"hooks": {
79-
"pre-commit": "lint-staged",
80-
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
81-
}
77+
"msw": "^0.28.1",
78+
"prettier": "^2.2.1"
8279
},
8380
"lint-staged": {
8481
"*.{js,css,md,ts,tsx}": "prettier --write"

Diff for: 404.html renamed to public/404.html

File renamed without changes.

Diff for: src/App.test.tsx

-9
This file was deleted.

Diff for: src/App.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import React from "react";
22
import "./App.css";
33
import { useAuth } from "context/auth-context";
4-
import { AuthenticatedApp } from "authenticated-app";
5-
import { UnauthenticatedApp } from "unauthenticated-app";
64
import { ErrorBoundary } from "components/error-boundary";
7-
import { FullPageErrorFallback } from "components/lib";
5+
import { FullPageErrorFallback, FullPageLoading } from "components/lib";
6+
7+
const AuthenticatedApp = React.lazy(() => import("authenticated-app"));
8+
const UnauthenticatedApp = React.lazy(() => import("unauthenticated-app"));
89

910
function App() {
1011
const { user } = useAuth();
1112

1213
return (
1314
<div className="App">
1415
<ErrorBoundary fallbackRender={FullPageErrorFallback}>
15-
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
16+
<React.Suspense fallback={<FullPageLoading />}>
17+
{user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
18+
</React.Suspense>
1619
</ErrorBoundary>
1720
</div>
1821
);

Diff for: src/__tests__/fake.json

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"users": [
3+
{
4+
"id": 1,
5+
"name": "高修文"
6+
},
7+
{
8+
"id": 2,
9+
"name": "熊天成"
10+
},
11+
{
12+
"id": 3,
13+
"name": "郑华"
14+
},
15+
{
16+
"id": 4,
17+
"name": "王文静"
18+
}
19+
],
20+
"projects": [
21+
{
22+
"id": 1,
23+
"name": "骑手管理",
24+
"personId": 1,
25+
"organization": "外卖组",
26+
"created": 1604989757139
27+
},
28+
{
29+
"id": 2,
30+
"name": "团购 APP",
31+
"personId": 2,
32+
"organization": "团购组",
33+
"created": 1604989757139
34+
},
35+
{
36+
"id": 3,
37+
"name": "物料管理系统",
38+
"personId": 2,
39+
"organization": "物料组",
40+
"created": 1546300800000
41+
},
42+
{
43+
"id": 4,
44+
"name": "总部管理系统",
45+
"personId": 3,
46+
"organization": "总部",
47+
"created": 1604980000011
48+
},
49+
{
50+
"id": 5,
51+
"name": "送餐路线规划系统",
52+
"personId": 4,
53+
"organization": "外卖组",
54+
"created": 1546900800000
55+
}
56+
]
57+
}

Diff for: src/__tests__/http.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { setupServer } from "msw/node";
2+
import { rest } from "msw";
3+
import { http } from "utils/http";
4+
5+
const apiUrl = process.env.REACT_APP_API_URL;
6+
7+
const server = setupServer();
8+
9+
// jest 是对react最友好的一个测试库
10+
// beforeAll 代表执行所有的测试之前,先来执行一下回调函数
11+
beforeAll(() => server.listen());
12+
13+
// 每一个测试跑完以后,都重置mock路由
14+
afterEach(() => server.resetHandlers());
15+
16+
// 所有的测试跑完后,关闭mock路由
17+
afterAll(() => server.close());
18+
19+
test("http方法发送异步请求", async () => {
20+
const endpoint = "test-endpoint";
21+
const mockResult = { mockValue: "mock" };
22+
23+
server.use(
24+
rest.get(`${apiUrl}/${endpoint}`, (req, res, ctx) =>
25+
res(ctx.json(mockResult))
26+
)
27+
);
28+
29+
const result = await http(endpoint);
30+
expect(result).toEqual(mockResult);
31+
});
32+
33+
test("http请求时会在header里带上token", async () => {
34+
const token = "FAKE_TOKEN";
35+
const endpoint = "test-endpoint";
36+
const mockResult = { mockValue: "mock" };
37+
38+
let request: any;
39+
40+
server.use(
41+
rest.get(`${apiUrl}/${endpoint}`, async (req, res, ctx) => {
42+
request = req;
43+
return res(ctx.json(mockResult));
44+
})
45+
);
46+
47+
await http(endpoint, { token });
48+
expect(request.headers.get("Authorization")).toBe(`Bearer ${token}`);
49+
});

Diff for: src/__tests__/mark.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from "react";
2+
import { render, screen } from "@testing-library/react";
3+
import { Mark } from "components/mark";
4+
5+
test("Mark 组件正确高亮关键词", () => {
6+
const name = "物料管理";
7+
const keyword = "管理";
8+
9+
render(<Mark name={name} keyword={keyword} />);
10+
11+
expect(screen.getByText(keyword)).toBeInTheDocument();
12+
expect(screen.getByText(keyword)).toHaveStyle("color: #257AFD");
13+
expect(screen.getByText("物料")).not.toHaveStyle("color: #257AFD");
14+
});

Diff for: src/__tests__/project-list.tsx

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { ReactNode } from "react";
2+
import { setupServer } from "msw/node";
3+
import { rest } from "msw";
4+
import fakeData from "./fake.json";
5+
import { render, screen, waitFor } from "@testing-library/react";
6+
import { ProjectListScreen } from "screens/project-list";
7+
import { AppProviders } from "context";
8+
9+
const apiUrl = process.env.REACT_APP_API_URL;
10+
const fakeAuth = {
11+
id: 1,
12+
name: "jack",
13+
token: "123",
14+
};
15+
16+
const server = setupServer(
17+
rest.get(`${apiUrl}/me`, (req, res, ctx) => res(ctx.json(fakeAuth))),
18+
rest.get(`${apiUrl}/users`, (req, res, ctx) => res(ctx.json(fakeData.users))),
19+
rest.get(`${apiUrl}/projects`, (req, res, ctx) => {
20+
const { name = "", personId = undefined } = Object.fromEntries(
21+
req.url.searchParams
22+
);
23+
const result = fakeData?.projects?.filter((project) => {
24+
return (
25+
project.name.includes(name) &&
26+
(personId ? project.personId === +personId : true)
27+
);
28+
});
29+
return res(ctx.json(result));
30+
})
31+
);
32+
33+
// jest 是对react最友好的一个测试库
34+
// beforeAll 代表执行所有的测试之前,先来执行一下回调函数
35+
beforeAll(() => server.listen());
36+
37+
// 每一个测试跑完以后,都重置mock路由
38+
afterEach(() => server.resetHandlers());
39+
40+
// 所有的测试跑完后,关闭mock路由
41+
afterAll(() => server.close());
42+
43+
const waitTable = () =>
44+
waitFor(() => expect(screen.getByText("骑手管理")).toBeInTheDocument(), {
45+
timeout: 3000,
46+
});
47+
test("项目列表展示正常", async () => {
48+
renderScreen(<ProjectListScreen />, { route: "/projects" });
49+
await waitTable();
50+
expect(screen.getAllByRole("row").length).toBe(fakeData.projects.length + 1);
51+
});
52+
53+
test("搜索项目", async () => {
54+
renderScreen(<ProjectListScreen />, { route: "/projects?name=骑手" });
55+
await waitTable();
56+
expect(screen.getAllByRole("row").length).toBe(2);
57+
expect(screen.getByText("骑手管理")).toBeInTheDocument();
58+
});
59+
60+
export const renderScreen = (ui: ReactNode, { route = "/projects" } = {}) => {
61+
window.history.pushState({}, "Test page", route);
62+
63+
return render(<AppProviders>{ui}</AppProviders>);
64+
};

Diff for: src/__tests__/use-async.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useAsync } from "utils/use-async";
2+
import { act, renderHook } from "@testing-library/react-hooks";
3+
4+
const defaultState: ReturnType<typeof useAsync> = {
5+
stat: "idle",
6+
data: null,
7+
error: null,
8+
9+
isIdle: true,
10+
isLoading: false,
11+
isError: false,
12+
isSuccess: false,
13+
14+
run: expect.any(Function),
15+
setData: expect.any(Function),
16+
setError: expect.any(Function),
17+
retry: expect.any(Function),
18+
};
19+
20+
const loadingState: ReturnType<typeof useAsync> = {
21+
...defaultState,
22+
stat: "loading",
23+
isIdle: false,
24+
isLoading: true,
25+
};
26+
27+
const successState: ReturnType<typeof useAsync> = {
28+
...defaultState,
29+
stat: "success",
30+
isIdle: false,
31+
isSuccess: true,
32+
};
33+
34+
test("useAsync 可以异步处理", async () => {
35+
let resolve: any, reject;
36+
const promise = new Promise((res, rej) => {
37+
resolve = res;
38+
reject = rej;
39+
});
40+
41+
const { result } = renderHook(() => useAsync());
42+
expect(result.current).toEqual(defaultState);
43+
44+
let p: Promise<any>;
45+
act(() => {
46+
p = result.current.run(promise);
47+
});
48+
expect(result.current).toEqual(loadingState);
49+
const resolvedValue = { mockedValue: "resolved" };
50+
await act(async () => {
51+
resolve(resolvedValue);
52+
await p;
53+
});
54+
expect(result.current).toEqual({
55+
...successState,
56+
data: resolvedValue,
57+
});
58+
});

Diff for: src/authenticated-app.tsx

+11-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import styled from "@emotion/styled";
66
import { ButtonNoPadding, Row } from "components/lib";
77
import { Button, Dropdown, Menu } from "antd";
88
import { Navigate, Route, Routes } from "react-router";
9-
import { BrowserRouter as Router } from "react-router-dom";
109
import { ProjectScreen } from "screens/project";
1110
import { resetRoute } from "utils";
1211
import { ProjectModal } from "screens/project-list/project-modal";
@@ -27,26 +26,21 @@ import { UserPopover } from "components/user-popover";
2726

2827
// prop drilling
2928

30-
export const AuthenticatedApp = () => {
29+
export default function AuthenticatedApp() {
3130
return (
3231
<Container>
33-
<Router>
34-
<PageHeader />
35-
<Main>
36-
<Routes>
37-
<Route path={"/projects"} element={<ProjectListScreen />} />
38-
<Route
39-
path={"/projects/:projectId/*"}
40-
element={<ProjectScreen />}
41-
/>
42-
<Navigate to={"/projects"} />
43-
</Routes>
44-
</Main>
45-
<ProjectModal />
46-
</Router>
32+
<PageHeader />
33+
<Main>
34+
<Routes>
35+
<Route path={"/projects"} element={<ProjectListScreen />} />
36+
<Route path={"/projects/:projectId/*"} element={<ProjectScreen />} />
37+
<Navigate to={"/projects"} />
38+
</Routes>
39+
</Main>
40+
<ProjectModal />
4741
</Container>
4842
);
49-
};
43+
}
5044

5145
const PageHeader = () => {
5246
return (

Diff for: src/components/epic-select.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from "react";
2+
import { IdSelect } from "components/id-select";
3+
import { useEpics } from "utils/epic";
4+
5+
export const EpicSelect = (props: React.ComponentProps<typeof IdSelect>) => {
6+
const { data: epics } = useEpics();
7+
return <IdSelect options={epics || []} {...props} />;
8+
};

0 commit comments

Comments
 (0)