From 27c9cc5730316123e1f9d3748ab5705304524d60 Mon Sep 17 00:00:00 2001 From: du Date: Mon, 12 Jul 2021 22:54:55 +0800 Subject: [PATCH 1/3] feat: add ts-jest --- jest.config.js | 16 +++--- package.json | 1 + yarn.lock | 138 +++++++++++++++++++++++++++++++++++++------------ 3 files changed, 116 insertions(+), 39 deletions(-) diff --git a/jest.config.js b/jest.config.js index f28f6db76..a963d5fff 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,17 +1,21 @@ +const { default: tsJestPreset } = require('ts-jest') + const defaults = { + ...tsJestPreset, coverageDirectory: './coverage/', collectCoverage: true, - testURL: 'http://localhost' + testURL: 'http://localhost', } -const testFolderPath = folderName => `/test/${folderName}/**/*.js` +const testFolderPath = (folderName) => + `/test/${folderName}/**/*.{js,ts,tsx}` const NORMAL_TEST_FOLDERS = ['components', 'hooks', 'integration', 'utils'] const standardConfig = { ...defaults, displayName: 'ReactDOM', - testMatch: NORMAL_TEST_FOLDERS.map(testFolderPath) + testMatch: NORMAL_TEST_FOLDERS.map(testFolderPath), } const rnConfig = { @@ -20,10 +24,10 @@ const rnConfig = { testMatch: [testFolderPath('react-native')], preset: 'react-native', transform: { - '^.+\\.js$': '/node_modules/react-native/jest/preprocessor.js' - } + '^.+\\.js$': '/node_modules/react-native/jest/preprocessor.js', + }, } module.exports = { - projects: [standardConfig, rnConfig] + projects: [standardConfig, rnConfig], } diff --git a/package.json b/package.json index ae298c88c..88d5c1ff1 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "rimraf": "^3.0.2", "rollup": "^2.32.1", "rollup-plugin-terser": "^7.0.2", + "ts-jest": "^27.0.3", "typescript": "^4.3.4" }, "browserify": { diff --git a/yarn.lock b/yarn.lock index f832e723b..8cfea03de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5228,6 +5228,15 @@ __metadata: languageName: node linkType: hard +"bs-logger@npm:0.x": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: 2.x + checksum: f5f2f1315d6ceac655c3945d149086a5f5a90b3c908780757e12e938aad0125a7aa563cae2f7153ccf43443adb1b88a44960a61063903c3973e1dfdda6fc2d8c + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -5244,7 +5253,7 @@ __metadata: languageName: node linkType: hard -"buffer-from@npm:^1.0.0": +"buffer-from@npm:1.x, buffer-from@npm:^1.0.0": version: 1.1.1 resolution: "buffer-from@npm:1.1.1" checksum: 540ceb79c4f5bfcadaabbc18324fa84c50dc52905084be7c03596a339cf5a88513bee6831ce9b36ddd046fab09257a7c80686e129d0559a0cfd141da196ad956 @@ -5597,7 +5606,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.0.0": +"ci-info@npm:^3.0.0, ci-info@npm:^3.1.1": version: 3.2.0 resolution: "ci-info@npm:3.2.0" checksum: d4a898d60111d00f2b7a06a349162971fe0603aefa208fe8d1343ce9e93c48e3d37311c47211d5c9040d25b43038c817588e5b7d8eab5d17b00aec49c7b5fade @@ -7985,7 +7994,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 7df3fabfe445d65953b2d9d9d3958bd895438b215a40fb87dae8b2165c5169a897785eb5d51e6cf0eb03523af756e3d82ea01083f6ac6341fe16db532fee3016 @@ -9685,6 +9694,17 @@ __metadata: languageName: node linkType: hard +"is-ci@npm:^3.0.0": + version: 3.0.0 + resolution: "is-ci@npm:3.0.0" + dependencies: + ci-info: ^3.1.1 + bin: + is-ci: bin.js + checksum: 1e26d3ba6634ebee83f9d22f260354c5d950eada4d609c30cc2642069f8ba52f3aeb4c9bbf8099aaf04a2f44a1ed7beef2a24485f988753c8c078a57e9b3a2fd + languageName: node + linkType: hard + "is-color-stop@npm:^1.1.0": version: 1.1.0 resolution: "is-color-stop@npm:1.1.0" @@ -10705,6 +10725,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:^27.0.0": + version: 27.0.6 + resolution: "jest-util@npm:27.0.6" + dependencies: + "@jest/types": ^27.0.6 + "@types/node": "*" + chalk: ^4.0.0 + graceful-fs: ^4.2.4 + is-ci: ^3.0.0 + picomatch: ^2.2.3 + checksum: a62ab3304ad58eb5fa130d66680d987890fca8c0505857a1b8bbcc8cf1de35eb3b82e19bdc5084dd10f68b3ce373234723f57f6e83781d4a4f66be1b647b488d + languageName: node + linkType: hard + "jest-validate@npm:^24.9.0": version: 24.9.0 resolution: "jest-validate@npm:24.9.0" @@ -10977,25 +11011,25 @@ __metadata: languageName: node linkType: hard -"json5@npm:^1.0.1": - version: 1.0.1 - resolution: "json5@npm:1.0.1" +"json5@npm:2.x, json5@npm:^2.1.2": + version: 2.2.0 + resolution: "json5@npm:2.2.0" dependencies: - minimist: ^1.2.0 + minimist: ^1.2.5 bin: json5: lib/cli.js - checksum: df41624f9f40bfacc546f779eef6d161a3312fbb6ec1dbd69f8c4388e9807af653b753371ab19b6d2bab22af2ca7dde62fe03c791596acf76915e1fc4ee6fd88 + checksum: 07b1f90c2801dc52df2b0ac8d606cc400a85cda79130e754780fa2ab9805d0fb85a0e61b6a5cdd68e88e5d0c8f9109ec415af08283175556cdccaa8563853908 languageName: node linkType: hard -"json5@npm:^2.1.2": - version: 2.2.0 - resolution: "json5@npm:2.2.0" +"json5@npm:^1.0.1": + version: 1.0.1 + resolution: "json5@npm:1.0.1" dependencies: - minimist: ^1.2.5 + minimist: ^1.2.0 bin: json5: lib/cli.js - checksum: 07b1f90c2801dc52df2b0ac8d606cc400a85cda79130e754780fa2ab9805d0fb85a0e61b6a5cdd68e88e5d0c8f9109ec415af08283175556cdccaa8563853908 + checksum: df41624f9f40bfacc546f779eef6d161a3312fbb6ec1dbd69f8c4388e9807af653b753371ab19b6d2bab22af2ca7dde62fe03c791596acf76915e1fc4ee6fd88 languageName: node linkType: hard @@ -11432,7 +11466,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.3.0, lodash@npm:^4.7.0, lodash@npm:~4.17.15": +"lodash@npm:4.x, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.3.0, lodash@npm:^4.7.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 4983720b9abca930a4a46f18db163d7dad8dd00dbed6db0cc7b499b33b717cce69f80928b27bbb1ff2cbd3b19d251ee90669a8b5ea466072ca81c2ebe91e7468 @@ -11558,6 +11592,13 @@ __metadata: languageName: node linkType: hard +"make-error@npm:1.x": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 2c780bab8409b865e8ee86697c599a2bf2765ec64d21eb67ccda27050e039f983feacad05a0d43aba3c966ea03d305d2612e94fec45474bcbc61181f57c5bb88 + languageName: node + linkType: hard + "make-fetch-happen@npm:^8.0.14": version: 8.0.14 resolution: "make-fetch-happen@npm:8.0.14" @@ -12244,6 +12285,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:1.x, mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 1aa3a6a2d7514f094a91329ec09994f5d32d2955a4985ecbb3d86f2aaeafc4aa11521f98d606144c1d49cd9835004d9a73342709b8c692c92e59eacf37412468 + languageName: node + linkType: hard + "mkdirp@npm:^0.5.0, mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1": version: 0.5.5 resolution: "mkdirp@npm:0.5.5" @@ -12255,15 +12305,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 1aa3a6a2d7514f094a91329ec09994f5d32d2955a4985ecbb3d86f2aaeafc4aa11521f98d606144c1d49cd9835004d9a73342709b8c692c92e59eacf37412468 - languageName: node - linkType: hard - "module-alias@npm:^2.2.2": version: 2.2.2 resolution: "module-alias@npm:2.2.2" @@ -14520,6 +14561,7 @@ __metadata: rimraf: ^3.0.2 rollup: ^2.32.1 rollup-plugin-terser: ^7.0.2 + ts-jest: ^27.0.3 typescript: ^4.3.4 peerDependencies: react: ^16.8.3 || ^17 @@ -15522,16 +15564,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" - bin: - semver: ./bin/semver.js - checksum: f0d155c06a67cc7e500c92d929339f1c6efd4ce9fe398aee6acc00a2333489cca0f5b4e76ee7292beba237fcca4b5a3d4a6153471f105f56299801bdab37289f - languageName: node - linkType: hard - -"semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:~7.3.0": +"semver@npm:7.x, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:~7.3.0": version: 7.3.5 resolution: "semver@npm:7.3.5" dependencies: @@ -15542,6 +15575,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0": + version: 6.3.0 + resolution: "semver@npm:6.3.0" + bin: + semver: ./bin/semver.js + checksum: f0d155c06a67cc7e500c92d929339f1c6efd4ce9fe398aee6acc00a2333489cca0f5b4e76ee7292beba237fcca4b5a3d4a6153471f105f56299801bdab37289f + languageName: node + linkType: hard + "send@npm:0.17.1": version: 0.17.1 resolution: "send@npm:0.17.1" @@ -16921,6 +16963,29 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:^27.0.3": + version: 27.0.3 + resolution: "ts-jest@npm:27.0.3" + dependencies: + bs-logger: 0.x + buffer-from: 1.x + fast-json-stable-stringify: 2.x + jest-util: ^27.0.0 + json5: 2.x + lodash: 4.x + make-error: 1.x + mkdirp: 1.x + semver: 7.x + yargs-parser: 20.x + peerDependencies: + jest: ^27.0.0 + typescript: ">=3.8 <5.0" + bin: + ts-jest: cli.js + checksum: a63f3a8620a16335d745f22377a9cc118129d28a5b122c609a7c6aabbb8048c85733c771a0dd39b136e8a75401473409452bdd3c5b9e3b85317c2e3f3ac03267 + languageName: node + linkType: hard + "tsconfig-paths@npm:^3.9.0": version: 3.9.0 resolution: "tsconfig-paths@npm:3.9.0" @@ -18247,6 +18312,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:20.x": + version: 20.2.9 + resolution: "yargs-parser@npm:20.2.9" + checksum: 3c58da6f6142f93c5207e309764bd90f723b9d7ed43f2e8aad0da1cefab83ee8ebf311dee2e81102646b74450c899e35b35053800b91fac23e6f433056f4c4cf + languageName: node + linkType: hard + "yargs-parser@npm:^13.1.2": version: 13.1.2 resolution: "yargs-parser@npm:13.1.2" From 09d8675f95354b5a6eb667e2299704bbb112832f Mon Sep 17 00:00:00 2001 From: du Date: Mon, 12 Jul 2021 22:56:30 +0800 Subject: [PATCH 2/3] Refactor: transform test\/hooks js files to tsx --- src/hooks/useReduxContext.ts | 3 +- ...eDispatch.spec.js => useDispatch.spec.tsx} | 14 +- ...ntext.spec.js => useReduxContext.spec.tsx} | 0 ...eSelector.spec.js => useSelector.spec.tsx} | 141 ++++++++++++------ 4 files changed, 105 insertions(+), 53 deletions(-) rename test/hooks/{useDispatch.spec.js => useDispatch.spec.tsx} (77%) rename test/hooks/{useReduxContext.spec.js => useReduxContext.spec.tsx} (100%) rename test/hooks/{useSelector.spec.js => useSelector.spec.tsx} (77%) diff --git a/src/hooks/useReduxContext.ts b/src/hooks/useReduxContext.ts index 70ea903df..bce0aa3e1 100644 --- a/src/hooks/useReduxContext.ts +++ b/src/hooks/useReduxContext.ts @@ -1,5 +1,6 @@ import { useContext } from 'react' import { ReactReduxContext } from '../components/Context' +import type { ReactReduxContextValue } from '../components/Context' /** * A hook to access the value of the `ReactReduxContext`. This is a low-level @@ -17,7 +18,7 @@ import { ReactReduxContext } from '../components/Context' * return
{store.getState()}
* } */ -export function useReduxContext() { +export function useReduxContext(): ReactReduxContextValue | null { const contextValue = useContext(ReactReduxContext) if (process.env.NODE_ENV !== 'production' && !contextValue) { diff --git a/test/hooks/useDispatch.spec.js b/test/hooks/useDispatch.spec.tsx similarity index 77% rename from test/hooks/useDispatch.spec.js rename to test/hooks/useDispatch.spec.tsx index 9473cc791..32143cb46 100644 --- a/test/hooks/useDispatch.spec.js +++ b/test/hooks/useDispatch.spec.tsx @@ -6,16 +6,20 @@ import { useDispatch, createDispatchHook, } from '../../src/index' +import type { ProviderProps } from '../../src/' -const store = createStore((c) => c + 1) -const store2 = createStore((c) => c + 2) +const store = createStore((c: number): number => c + 1) +const store2 = createStore((c: number): number => c + 2) describe('React', () => { describe('hooks', () => { describe('useDispatch', () => { it("returns the store's dispatch function", () => { + type PropsType = Omit const { result } = renderHook(() => useDispatch(), { - wrapper: (props) => , + wrapper: (props: PropsType) => ( + + ), }) expect(result.current).toBe(store.dispatch) @@ -27,7 +31,7 @@ describe('React', () => { const useCustomDispatch = createDispatchHook(nestedContext) const { result } = renderHook(() => useDispatch(), { // eslint-disable-next-line react/prop-types - wrapper: ({ children, ...props }) => ( + wrapper: ({ children, ...props }: ProviderProps) => ( {children} @@ -40,7 +44,7 @@ describe('React', () => { const { result: result2 } = renderHook(() => useCustomDispatch(), { // eslint-disable-next-line react/prop-types - wrapper: ({ children, ...props }) => ( + wrapper: ({ children, ...props }: ProviderProps) => ( {children} diff --git a/test/hooks/useReduxContext.spec.js b/test/hooks/useReduxContext.spec.tsx similarity index 100% rename from test/hooks/useReduxContext.spec.js rename to test/hooks/useReduxContext.spec.tsx diff --git a/test/hooks/useSelector.spec.js b/test/hooks/useSelector.spec.tsx similarity index 77% rename from test/hooks/useSelector.spec.js rename to test/hooks/useSelector.spec.tsx index ab2a29738..761b9e8f2 100644 --- a/test/hooks/useSelector.spec.js +++ b/test/hooks/useSelector.spec.tsx @@ -11,18 +11,31 @@ import { connect, createSelectorHook, } from '../../src/index' +import type { FunctionComponent } from 'react' import { useReduxContext } from '../../src/hooks/useReduxContext' - +import type { Store } from 'redux' +import type { ProviderProps } from '../../src/' +import type { Subscription } from '../../src/utils/Subscription' + +type PropsTypeDelStore = Omit +type DefaultRootState = { + count: number +} describe('React', () => { describe('hooks', () => { describe('useSelector', () => { - let store + let store: Store let renderedItems = [] beforeEach(() => { - store = createStore(({ count } = { count: -1 }) => ({ - count: count + 1, - })) + interface ReducerState { + count: number + } + store = createStore( + ({ count }: ReducerState = { count: -1 }): ReducerState => ({ + count: count + 1, + }) + ) renderedItems = [] }) @@ -30,9 +43,14 @@ describe('React', () => { describe('core subscription behavior', () => { it('selects the state on initial render', () => { - const { result } = renderHook(() => useSelector((s) => s.count), { - wrapper: (props) => , - }) + const { result } = renderHook( + () => useSelector((s) => s.count), + { + wrapper: (props: PropsTypeDelStore) => ( + + ), + } + ) expect(result.current).toEqual(0) }) @@ -41,7 +59,9 @@ describe('React', () => { const selector = jest.fn((s) => s.count) const { result } = renderHook(() => useSelector(selector), { - wrapper: (props) => , + wrapper: (props: PropsTypeDelStore) => ( + + ), }) expect(result.current).toEqual(0) @@ -58,7 +78,7 @@ describe('React', () => { describe('lifecycle interactions', () => { it('always uses the latest state', () => { - store = createStore((c) => c + 1, -1) + store = createStore((c: number): number => c + 1, -1) const Comp = () => { const selector = useCallback((c) => c + 1, []) @@ -81,17 +101,17 @@ describe('React', () => { }) it('subscribes to the store synchronously', () => { - let rootSubscription + let rootSubscription: Subscription const Parent = () => { const { subscription } = useReduxContext() rootSubscription = subscription - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return count === 1 ? : null } const Child = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return
{count}
} @@ -109,17 +129,17 @@ describe('React', () => { }) it('unsubscribes when the component is unmounted', () => { - let rootSubscription + let rootSubscription: Subscription const Parent = () => { const { subscription } = useReduxContext() rootSubscription = subscription - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return count === 0 ? : null } const Child = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return
{count}
} @@ -138,7 +158,7 @@ describe('React', () => { it('notices store updates between render and store subscription effect', () => { const Comp = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) renderedItems.push(count) // I don't know a better way to trigger a store update before the @@ -161,7 +181,7 @@ describe('React', () => { }) it('works properly with memoized selector with dispatch in Child useLayoutEffect', () => { - store = createStore((c) => c + 1, -1) + store = createStore((c: number) => c + 1, -1) const Comp = () => { const selector = useCallback((c) => c, []) @@ -221,8 +241,14 @@ describe('React', () => { }) it('allows other equality functions to prevent unnecessary updates', () => { + interface ReducerParamsType { + count: number + stable: any + } store = createStore( - ({ count, stable } = { count: -1, stable: {} }) => ({ + ( + { count, stable }: ReducerParamsType = { count: -1, stable: {} } + ) => ({ count: count + 1, stable, }) @@ -286,12 +312,12 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return } const Child = ({ parentCount }) => { - const result = useSelector(({ count }) => { + const result = useSelector(({ count }) => { if (count !== parentCount) { throw new Error() } @@ -328,7 +354,7 @@ describe('React', () => { return
{result}
} - const store = createStore((count = -1) => count + 1) + const store = createStore((count: number = -1) => count + 1) const App = () => ( @@ -349,12 +375,12 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return } const Child = ({ parentCount }) => { - const result = useSelector(({ count }) => { + const result = useSelector(({ count }) => { if (parentCount > 0) { throw new Error() } @@ -380,20 +406,30 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useSelector((s) => s.count) return } - - const ConnectedWrapper = connect(({ count }) => ({ count }))( - ({ parentCount }) => { - return - } - ) + interface ParentContentProps { + parentCount: number + } + const ConnectedWrapper = connect< + DefaultRootState, + undefined, + ParentContentProps, + DefaultRootState + >(({ count }) => ({ + count, + }))>(({ parentCount }) => { + return + }) let sawInconsistentState = false - const Child = ({ parentCount }) => { - const result = useSelector(({ count }) => { + interface ChildPropsType { + parentCount: number + } + const Child = ({ parentCount }: ChildPropsType) => { + const result = useSelector(({ count }) => { if (count !== parentCount) { sawInconsistentState = true } @@ -418,15 +454,17 @@ describe('React', () => { }) it('reuse latest selected state on selector re-run', () => { - store = createStore(({ count } = { count: -1 }) => ({ - count: count + 1, - })) + store = createStore( + ({ count }: DefaultRootState = { count: -1 }) => ({ + count: count + 1, + }) + ) const alwaysEqual = () => true const Comp = () => { // triggers render on store change - useSelector((s) => s.count) + useSelector((s) => s.count) const array = useSelector(() => [1, 2, 3], alwaysEqual) renderedItems.push(array) return
@@ -449,15 +487,20 @@ describe('React', () => { describe('error handling for invalid arguments', () => { it('throws if no selector is passed', () => { + //@ts-expect-error expect(() => useSelector()).toThrow() }) it('throws if selector is not a function', () => { + //@ts-expect-error expect(() => useSelector(1)).toThrow() }) it('throws if equality function is not a function', () => { - expect(() => useSelector((s) => s.count, 1)).toThrow() + expect(() => + //@ts-expect-error + useSelector((s) => s.count, 1) + ).toThrow() }) }) }) @@ -467,12 +510,16 @@ describe('React', () => { let customStore beforeEach(() => { - defaultStore = createStore(({ count } = { count: -1 }) => ({ - count: count + 1, - })) - customStore = createStore(({ count } = { count: 10 }) => ({ - count: count + 2, - })) + defaultStore = createStore( + ({ count }: DefaultRootState = { count: -1 }) => ({ + count: count + 1, + }) + ) + customStore = createStore( + ({ count }: DefaultRootState = { count: 10 }) => ({ + count: count + 2, + }) + ) }) it('subscribes to the correct store', () => { @@ -481,15 +528,15 @@ describe('React', () => { let defaultCount = null let customCount = null - const getCount = (s) => s.count + const getCount = (s: DefaultRootState) => s.count const DisplayDefaultCount = ({ children = null }) => { - const count = useSelector(getCount) + const count = useSelector(getCount) defaultCount = count return <>{children} } const DisplayCustomCount = ({ children = null }) => { - const count = useCustomSelector(getCount) + const count = useCustomSelector(getCount) customCount = count return <>{children} } From 8404c7ad9acf83a007d9e788910b23cb3827bf5b Mon Sep 17 00:00:00 2001 From: du Date: Tue, 13 Jul 2021 19:31:28 +0800 Subject: [PATCH 3/3] refactor: modify test/hooks/useSelector type use --- test/hooks/useSelector.spec.tsx | 184 ++++++++++++++++---------------- 1 file changed, 90 insertions(+), 94 deletions(-) diff --git a/test/hooks/useSelector.spec.tsx b/test/hooks/useSelector.spec.tsx index 761b9e8f2..587f215e0 100644 --- a/test/hooks/useSelector.spec.tsx +++ b/test/hooks/useSelector.spec.tsx @@ -13,26 +13,24 @@ import { } from '../../src/index' import type { FunctionComponent } from 'react' import { useReduxContext } from '../../src/hooks/useReduxContext' -import type { Store } from 'redux' -import type { ProviderProps } from '../../src/' +import type { Store, AnyAction } from 'redux' +import type { ProviderProps, TypedUseSelectorHook } from '../../src/' import type { Subscription } from '../../src/utils/Subscription' -type PropsTypeDelStore = Omit -type DefaultRootState = { - count: number -} describe('React', () => { describe('hooks', () => { describe('useSelector', () => { - let store: Store + type NormalStateType = { + count: number + } + let normalStore: Store let renderedItems = [] + type RootState = ReturnType + let useNormalSelector: TypedUseSelectorHook = useSelector beforeEach(() => { - interface ReducerState { - count: number - } - store = createStore( - ({ count }: ReducerState = { count: -1 }): ReducerState => ({ + normalStore = createStore( + ({ count }: NormalStateType = { count: -1 }): NormalStateType => ({ count: count + 1, }) ) @@ -42,12 +40,14 @@ describe('React', () => { afterEach(() => rtl.cleanup()) describe('core subscription behavior', () => { + type PropsTypeDelStore = Omit + it('selects the state on initial render', () => { const { result } = renderHook( - () => useSelector((s) => s.count), + () => useNormalSelector((s) => s.count), { wrapper: (props: PropsTypeDelStore) => ( - + ), } ) @@ -56,11 +56,13 @@ describe('React', () => { }) it('selects the state and renders the component when the store updates', () => { - const selector = jest.fn((s) => s.count) + const selector: jest.Mock = jest.fn( + (s) => s.count + ) - const { result } = renderHook(() => useSelector(selector), { + const { result } = renderHook(() => useNormalSelector(selector), { wrapper: (props: PropsTypeDelStore) => ( - + ), }) @@ -68,7 +70,7 @@ describe('React', () => { expect(selector).toHaveBeenCalledTimes(2) act(() => { - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) }) expect(result.current).toEqual(1) @@ -78,10 +80,10 @@ describe('React', () => { describe('lifecycle interactions', () => { it('always uses the latest state', () => { - store = createStore((c: number): number => c + 1, -1) + const store = createStore((c: number): number => c + 1, -1) const Comp = () => { - const selector = useCallback((c) => c + 1, []) + const selector = useCallback((c: number): number => c + 1, []) const value = useSelector(selector) renderedItems.push(value) return
@@ -106,24 +108,24 @@ describe('React', () => { const Parent = () => { const { subscription } = useReduxContext() rootSubscription = subscription - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return count === 1 ? : null } const Child = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return
{count}
} rtl.render( - + ) expect(rootSubscription.getListeners().get().length).toBe(1) - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) expect(rootSubscription.getListeners().get().length).toBe(2) }) @@ -134,44 +136,44 @@ describe('React', () => { const Parent = () => { const { subscription } = useReduxContext() rootSubscription = subscription - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return count === 0 ? : null } const Child = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return
{count}
} rtl.render( - + ) expect(rootSubscription.getListeners().get().length).toBe(2) - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) expect(rootSubscription.getListeners().get().length).toBe(1) }) it('notices store updates between render and store subscription effect', () => { const Comp = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) renderedItems.push(count) // I don't know a better way to trigger a store update before the // store subscription effect happens if (count === 0) { - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) } return
{count}
} rtl.render( - + ) @@ -181,16 +183,20 @@ describe('React', () => { }) it('works properly with memoized selector with dispatch in Child useLayoutEffect', () => { - store = createStore((c: number) => c + 1, -1) + const store = createStore((c: number): number => c + 1, -1) const Comp = () => { - const selector = useCallback((c) => c, []) + const selector = useCallback((c: number): number => c, []) const count = useSelector(selector) renderedItems.push(count) return } - const Child = ({ parentCount }) => { + interface ChildPropsType { + parentCount: number + } + + const Child = ({ parentCount }: ChildPropsType) => { useLayoutEffect(() => { if (parentCount === 1) { store.dispatch({ type: '' }) @@ -219,7 +225,7 @@ describe('React', () => { describe('performance optimizations and bail-outs', () => { it('defaults to ref-equality to prevent unnecessary updates', () => { const state = {} - store = createStore(() => state) + const store = createStore(() => state) const Comp = () => { const value = useSelector((s) => s) @@ -241,21 +247,21 @@ describe('React', () => { }) it('allows other equality functions to prevent unnecessary updates', () => { - interface ReducerParamsType { + interface StateType { count: number - stable: any + stable: {} } - store = createStore( - ( - { count, stable }: ReducerParamsType = { count: -1, stable: {} } - ) => ({ + const store = createStore( + ({ count, stable }: StateType = { count: -1, stable: {} }) => ({ count: count + 1, stable, }) ) const Comp = () => { - const value = useSelector((s) => Object.keys(s), shallowEqual) + const value = useSelector((s) => { + return Object.keys(s) + }, shallowEqual) renderedItems.push(value) return
} @@ -288,7 +294,7 @@ describe('React', () => { } rtl.render( - + ) @@ -299,7 +305,7 @@ describe('React', () => { expect(renderedItems).toEqual([0, 1]) rtl.act(() => { - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) }) expect(renderedItems).toEqual([0, 1]) @@ -308,16 +314,18 @@ describe('React', () => { }) describe('edge cases', () => { + interface ChildPropsType { + parentCount: number + } it('ignores transient errors in selector (e.g. due to stale props)', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return } - - const Child = ({ parentCount }) => { - const result = useSelector(({ count }) => { + const Child = ({ parentCount }: ChildPropsType) => { + const result = useNormalSelector(({ count }) => { if (count !== parentCount) { throw new Error() } @@ -329,12 +337,12 @@ describe('React', () => { } rtl.render( - + ) - expect(() => store.dispatch({ type: '' })).not.toThrowError() + expect(() => normalStore.dispatch({ type: '' })).not.toThrowError() spy.mockRestore() }) @@ -343,7 +351,7 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Comp = () => { - const result = useSelector((count) => { + const result = useSelector((count: number) => { if (count > 0) { throw new Error('foo') } @@ -354,7 +362,7 @@ describe('React', () => { return
{result}
} - const store = createStore((count: number = -1) => count + 1) + const store = createStore((count: number = -1): number => count + 1) const App = () => ( @@ -375,12 +383,12 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return } - const Child = ({ parentCount }) => { - const result = useSelector(({ count }) => { + const Child = ({ parentCount }: ChildPropsType) => { + const result = useNormalSelector(({ count }) => { if (parentCount > 0) { throw new Error() } @@ -392,12 +400,12 @@ describe('React', () => { } rtl.render( - + ) - expect(() => store.dispatch({ type: '' })).toThrowError() + expect(() => normalStore.dispatch({ type: '' })).toThrowError() spy.mockRestore() }) @@ -406,30 +414,25 @@ describe('React', () => { const spy = jest.spyOn(console, 'error').mockImplementation(() => {}) const Parent = () => { - const count = useSelector((s) => s.count) + const count = useNormalSelector((s) => s.count) return } - interface ParentContentProps { - parentCount: number - } + const ConnectedWrapper = connect< - DefaultRootState, + NormalStateType, undefined, - ParentContentProps, - DefaultRootState + ChildPropsType, + NormalStateType >(({ count }) => ({ count, - }))>(({ parentCount }) => { + }))>(({ parentCount }) => { return }) let sawInconsistentState = false - interface ChildPropsType { - parentCount: number - } const Child = ({ parentCount }: ChildPropsType) => { - const result = useSelector(({ count }) => { + const result = useNormalSelector(({ count }) => { if (count !== parentCount) { sawInconsistentState = true } @@ -441,12 +444,12 @@ describe('React', () => { } rtl.render( - + ) - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) expect(sawInconsistentState).toBe(false) @@ -454,31 +457,25 @@ describe('React', () => { }) it('reuse latest selected state on selector re-run', () => { - store = createStore( - ({ count }: DefaultRootState = { count: -1 }) => ({ - count: count + 1, - }) - ) - const alwaysEqual = () => true const Comp = () => { // triggers render on store change - useSelector((s) => s.count) + useNormalSelector((s) => s.count) const array = useSelector(() => [1, 2, 3], alwaysEqual) renderedItems.push(array) return
} rtl.render( - + ) expect(renderedItems.length).toBe(1) - store.dispatch({ type: '' }) + normalStore.dispatch({ type: '' }) expect(renderedItems.length).toBe(2) expect(renderedItems[0]).toBe(renderedItems[1]) @@ -499,7 +496,7 @@ describe('React', () => { it('throws if equality function is not a function', () => { expect(() => //@ts-expect-error - useSelector((s) => s.count, 1) + useNormalSelector((s) => s.count, 1) ).toThrow() }) }) @@ -508,18 +505,17 @@ describe('React', () => { describe('createSelectorHook', () => { let defaultStore let customStore + type StateType = { + count: number + } beforeEach(() => { - defaultStore = createStore( - ({ count }: DefaultRootState = { count: -1 }) => ({ - count: count + 1, - }) - ) - customStore = createStore( - ({ count }: DefaultRootState = { count: 10 }) => ({ - count: count + 2, - }) - ) + defaultStore = createStore(({ count }: StateType = { count: -1 }) => ({ + count: count + 1, + })) + customStore = createStore(({ count }: StateType = { count: 10 }) => ({ + count: count + 2, + })) }) it('subscribes to the correct store', () => { @@ -528,15 +524,15 @@ describe('React', () => { let defaultCount = null let customCount = null - const getCount = (s: DefaultRootState) => s.count + const getCount = (s: StateType) => s.count const DisplayDefaultCount = ({ children = null }) => { - const count = useSelector(getCount) + const count = useSelector(getCount) defaultCount = count return <>{children} } const DisplayCustomCount = ({ children = null }) => { - const count = useCustomSelector(getCount) + const count = useCustomSelector(getCount) customCount = count return <>{children} }