Skip to content

Commit 7c2acc2

Browse files
authored
Service Factory - Service Hooks (#141)
* remove cypress * update package dependencies * fix linting and build errors * configuration updates * add use-abort-signal hook * refactor service factories to use abortcontroller to cancel requests * add changesets * update gh actions * add mock service worker * add service hooks * add todo sample application using service hooks * add changeset * add tailwind to todo sample * update todorecord class name * update todo example * update package dependencies * msw init prepare script * Add mockServiceWorker.js to .gitignore * update todo sample README * Fix setupMockAPI function name in test files
1 parent 6a1bab5 commit 7c2acc2

48 files changed

Lines changed: 2722 additions & 655 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/sour-ties-peel.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rsm-hcd/javascript-react": minor
3+
"todo": minor
4+
---
5+
6+
Add service factory service hooks

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ yarn-error.log*
3737

3838
# HCD JavaScript specific
3939
docs/
40+
samples/todo/public/mockServiceWorker.js

.vscode/css/custom-data.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"version": 1.1,
3+
"atDirectives": [
4+
{
5+
"name": "@apply",
6+
"description": "Apply Tailwind Classes"
7+
}
8+
]
9+
}

.vscode/settings.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
{
2+
"files.associations": {
3+
"*.tsx": "typescriptreact",
4+
"*.css": "tailwindcss",
5+
"*.scss": "tailwindcss"
6+
},
27
"[typescript]": {
38
"editor.defaultFormatter": "esbenp.prettier-vscode"
49
},
510
"[typescriptreact]": {
611
"editor.defaultFormatter": "esbenp.prettier-vscode"
712
},
13+
"[scss]": {
14+
"editor.defaultFormatter": "esbenp.prettier-vscode"
15+
},
16+
"css.customData": ["./.vscode/css/custom-data.json"],
817
"editor.defaultFormatter": "esbenp.prettier-vscode",
918
"editor.formatOnSave": true,
1019
"editor.rulers": [100],
@@ -16,5 +25,12 @@
1625
{
1726
"mode": "auto"
1827
}
28+
],
29+
"[tailwindcss]": {
30+
"tailwindCSS.colorDecorators": true
31+
},
32+
"tailwindCSS.experimental.classRegex": [
33+
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
34+
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
1935
]
2036
}

packages/javascript-react/jest.config.js

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { Config } from "jest";
2+
3+
export default {
4+
rootDir: __dirname,
5+
/**
6+
* @note Include the polyfills in the "setupFiles"
7+
* to apply them BEFORE the test environment.
8+
*/
9+
setupFiles: ["<rootDir>/jest.polyfills.ts"],
10+
setupFilesAfterEnv: [
11+
"<rootDir>/src/setup-tests.ts",
12+
// polyfill window.resizeTo
13+
"window-resizeto/polyfill",
14+
],
15+
transform: {
16+
"^.+\\.tsx?$": "@swc/jest",
17+
},
18+
testEnvironment: "jsdom",
19+
testEnvironmentOptions: {
20+
/**
21+
* @note Opt-out from JSDOM using browser-style resolution
22+
* for dependencies. This is simply incorrect, as JSDOM is
23+
* not a browser, and loading browser-oriented bundles in
24+
* Node.js will break things.
25+
*
26+
* Consider migrating to a more modern test runner if you
27+
* don't want to deal with this.
28+
*/
29+
customExportConditions: [""],
30+
},
31+
} satisfies Config;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* eslint-disable import/order -- imports need to be arranged in a specific order */
2+
/**
3+
* @note The block below contains polyfills for Node.js globals
4+
* required for Jest to function when running JSDOM tests.
5+
* These HAVE to be require's and HAVE to be in this exact
6+
* order, since "undici" depends on the "TextEncoder" global API.
7+
*
8+
* Consider migrating to a more modern test runner if
9+
* you don't want to deal with this.
10+
*/
11+
12+
const { TextDecoder, TextEncoder } = require("node:util");
13+
14+
Object.defineProperties(globalThis, {
15+
TextDecoder: { value: TextDecoder },
16+
TextEncoder: { value: TextEncoder },
17+
});
18+
19+
const { Blob } = require("node:buffer");
20+
21+
// eslint-disable-next-line import/no-extraneous-dependencies -- Required for JSDOM tests
22+
const { fetch, Headers, FormData, Request, Response } = require("undici");
23+
24+
Object.defineProperties(globalThis, {
25+
fetch: { value: fetch, writable: true },
26+
Blob: { value: Blob },
27+
Headers: { value: Headers },
28+
FormData: { value: FormData },
29+
Request: { value: Request },
30+
Response: { value: Response },
31+
});

packages/javascript-react/package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"@rsm-hcd/javascript-core": "workspace:*",
2424
"@rsm-hcd/javascript-testing": "workspace:*",
2525
"@rsm-hcd/tsconfig": "workspace:*",
26+
"@swc/core": "^1.4.11",
27+
"@swc/jest": "^0.2.36",
2628
"@testing-library/jest-dom": "^6.4.2",
2729
"@testing-library/react": "^14.2.2",
2830
"@testing-library/react-hooks": "^8.0.1",
@@ -41,9 +43,8 @@
4143
"jest": "^29.7.0",
4244
"jest-environment-jsdom": "^29.7.0",
4345
"jest-extended": "^4.0.2",
44-
"jest-fetch-mock": "3.0.3",
45-
"jest-mock-axios": "^4.7.3",
4646
"lodash": "4.17.21",
47+
"msw": "^2.2.13",
4748
"prettier": "^3.2.5",
4849
"react": "^18.2.0",
4950
"react-dom": "^18.2.0",
@@ -52,10 +53,10 @@
5253
"react-test-renderer": "^18.2.0",
5354
"rimraf": "^5.0.5",
5455
"rosie": "^2.1.1",
55-
"ts-jest": "^29.1.2",
5656
"ts-node": "^10.9.2",
5757
"tsup": "^8.0.2",
5858
"typescript": "^5.4.3",
59+
"undici": "^5.28.3",
5960
"window-resizeto": "0.0.2"
6061
},
6162
"files": [
@@ -92,10 +93,10 @@
9293
"lint": "eslint \"src/**/*.ts\" --ext .ts --fix",
9394
"lint:fix": "pnpm run lint --fix",
9495
"prepublishOnly": "npm run build:prod",
95-
"test": "jest ./src",
96+
"test": "jest",
9697
"watch": "tsc --pretty --watch",
97-
"watch:coverage": "jest ./src --coverage --watch",
98-
"watch:test": "jest ./src --watch"
98+
"watch:coverage": "jest --coverage --watch",
99+
"watch:test": "jest --watch"
99100
},
100101
"lint-staged": {
101102
"*.{ts,tsx}": [

packages/javascript-react/src/__mocks__/axios.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { act, renderHook, waitFor } from "@testing-library/react";
2+
import { Record } from "immutable";
3+
import { ServiceFactory } from "../../services/service-factory";
4+
import { setupMockApi } from "../../tests/setup-mock-api";
5+
import { useCreateService } from "./use-create";
6+
7+
// ---------------------------------------------------------------------------------------------
8+
// #region Setup
9+
// ---------------------------------------------------------------------------------------------
10+
11+
const endpoint = "records";
12+
const baseEndpoint = `http://api.local/${endpoint}`;
13+
14+
const { server, mockPostSuccess } = setupMockApi({
15+
baseEndpoint,
16+
});
17+
18+
class TestRecord extends Record<{ id: number; value: string }>({
19+
id: 0,
20+
value: "",
21+
}) {}
22+
23+
const TestService = {
24+
create: ServiceFactory.create(TestRecord, baseEndpoint),
25+
};
26+
27+
// #endregion Setup
28+
29+
describe("useCreateService", () => {
30+
beforeAll(() => server.listen());
31+
afterEach(() => server.resetHandlers());
32+
afterAll(() => server.close());
33+
34+
it("should create a create service hook", () => {
35+
// Arrange & Act
36+
const { result } = renderHook(() =>
37+
useCreateService(TestService.create)
38+
);
39+
40+
// Assert
41+
expect(result.current.create).toBeDefined();
42+
});
43+
44+
it("should have a creating state", () => {
45+
// Arrange & Act
46+
const { result } = renderHook(() =>
47+
useCreateService(TestService.create)
48+
);
49+
50+
// Assert
51+
expect(result.current.isCreating).toBe(false);
52+
});
53+
54+
it("should not have an error", () => {
55+
// Arrange & Act
56+
const { result } = renderHook(() =>
57+
useCreateService(TestService.create)
58+
);
59+
60+
// Assert
61+
expect(result.current.error).toBeUndefined();
62+
});
63+
64+
describe("create", () => {
65+
it("should create a record", async () => {
66+
// Arrange
67+
const record = new TestRecord({ id: 1, value: "test" });
68+
mockPostSuccess(record, 100);
69+
70+
const { result } = renderHook(() =>
71+
useCreateService(TestService.create)
72+
);
73+
74+
// Act
75+
act(() => {
76+
result.current.create(record);
77+
});
78+
79+
// Assert
80+
await waitFor(() => {
81+
expect(result.current.isCreating).toBe(true);
82+
});
83+
84+
await waitFor(() => {
85+
expect(result.current.isCreating).toBe(false);
86+
expect(result.current.error).toBeUndefined();
87+
expect(result.current.created).toEqual([record]);
88+
});
89+
});
90+
91+
describe("when unmounted", () => {
92+
it("should not set state", async () => {
93+
// Arrange
94+
const record = new TestRecord({ id: 1, value: "test" });
95+
mockPostSuccess(record, 100);
96+
97+
const { result, unmount } = renderHook(() =>
98+
useCreateService(TestService.create)
99+
);
100+
101+
// Act
102+
act(() => {
103+
result.current.create(record);
104+
unmount();
105+
});
106+
107+
// Assert
108+
await waitFor(() => {
109+
expect(result.current.isCreating).toBe(false);
110+
expect(result.current.error).toBeUndefined();
111+
expect(result.current.created).toEqual([]);
112+
});
113+
});
114+
});
115+
});
116+
});

0 commit comments

Comments
 (0)