Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
"modules": false
}
]

]
},
"test": {
"presets": [["@4c", { "development": true }]]
"presets": [["@4c", { "development": true }], "@babel/typescript"]
}
}
}
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 18.x
- name: Install Dependencies
run: yarn bootstrap
- name: Run Tests
Expand Down
49 changes: 24 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
},
"jest": {
"preset": "@4c",
"rootDir": "./test",
"testEnvironment": "jsdom",
"setupFilesAfterEnv": [
"./setup.js"
"./test/setup.js"
]
},
"prettier": {
Expand All @@ -62,38 +62,37 @@
"react": ">=16.8.0"
},
"devDependencies": {
"@4c/babel-preset": "^7.3.3",
"@4c/cli": "^2.1.12",
"@4c/jest-preset": "^1.5.4",
"@4c/rollout": "^2.1.11",
"@4c/tsconfig": "^0.3.1",
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@4c/babel-preset": "^10.2.1",
"@4c/cli": "^4.0.4",
"@4c/jest-preset": "^1.8.1",
"@4c/rollout": "^4.0.2",
"@4c/tsconfig": "^0.4.1",
"@babel/cli": "^7.22.9",
"@babel/core": "^7.22.9",
"@babel/preset-typescript": "^7.22.5",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^7.0.0",
"@types/enzyme": "^3.10.8",
"@types/jest": "^26.0.19",
"@types/lodash": "^4.14.167",
"@types/react": "^17.0.0",
"babel-jest": "^26.6.3",
"@types/jest": "^29.5.3",
"@types/lodash": "^4.14.195",
"@types/react": "^18.2.15",
"babel-jest": "^29.6.1",
"babel-plugin-transform-rename-import": "^2.3.0",
"cherry-pick": "^0.5.0",
"codecov": "^3.8.1",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint": "^7.17.0",
"codecov": "^3.8.3",
"eslint": "^8.44.0",
"gh-pages": "^3.1.0",
"husky": "^4.3.6",
"jest": "^26.6.3",
"lint-staged": "^10.5.3",
"jest": "^29.6.1",
"jest-environment-jsdom": "^29.6.1",
"lint-staged": "^13.2.3",
"mq-polyfill": "^1.1.8",
"prettier": "^2.2.1",
"prettier": "^3.0.0",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"rimraf": "^3.0.2",
"typescript": "^4.1.3"
"rimraf": "^5.0.1",
"typescript": "^5.1.6"
},
"dependencies": {
"dequal": "^2.0.2"
"dequal": "^2.0.3"
}
}
2 changes: 1 addition & 1 deletion src/useForceUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useReducer } from 'react'
*/
export default function useForceUpdate(): () => void {
// The toggling state value is designed to defeat React optimizations for skipping
// updates when they are stricting equal to the last state value
// updates when they are strictly equal to the last state value
const [, dispatch] = useReducer((state: boolean) => !state, false)
return dispatch as () => void
}
23 changes: 23 additions & 0 deletions src/useIsInitialRenderRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useLayoutEffect, useRef } from 'react'

/**
* Returns ref that is `true` on the initial render and `false` on subsequent renders. It
* is StrictMode safe, so will reset correctly if the component is unmounted and remounted
*/
export default function useIsInitialRenderRef() {
const isInitialRenderRef = useRef(true)

useLayoutEffect(() => {
isInitialRenderRef.current = false
})

// Strict mode handling in React 18
useEffect(
() => () => {
isInitialRenderRef.current = true
},
[],
)

return isInitialRenderRef
}
2 changes: 1 addition & 1 deletion src/useMergeStateFromProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type Mapper<TProps, TState> = (
state: TState,
) => null | Partial<TState>

export default function useMergeStateFromProps<TProps, TState>(
export default function useMergeStateFromProps<TProps, TState extends {}>(
props: TProps,
gDSFP: Mapper<TProps, TState>,
initialState: TState,
Expand Down
6 changes: 3 additions & 3 deletions src/useRefWithInitialValueFactory.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useRef } from 'react'

const dft: any = Symbol('default value sigil')
const dft: unique symbol = Symbol('default value sigil')

/**
* Exactly the same as `useRef` except that the initial value is set via a
* factroy function. Useful when the default is relatively costly to construct.
* factory function. Useful when the default is relatively costly to construct.
*
* ```ts
* const ref = useRefWithInitialValueFactory<ExpensiveValue>(() => constructExpensiveValue())
Expand All @@ -17,7 +17,7 @@ const dft: any = Symbol('default value sigil')
export default function useRefWithInitialValueFactory<T>(
initialValueFactory: () => T,
) {
const ref = useRef<T>(dft)
const ref = useRef<T>(dft as T)
if (ref.current === dft) {
ref.current = initialValueFactory()
}
Expand Down
6 changes: 3 additions & 3 deletions src/useThrottledEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export type ThrottledHandler<TEvent> = ((event: TEvent) => void) & {
* @returns The event handler with a `clear` method attached for clearing any in-flight handler calls
*
*/
export default function useThrottledEventHandler<TEvent = SyntheticEvent>(
handler: (event: TEvent) => void,
): ThrottledHandler<TEvent> {
export default function useThrottledEventHandler<
TEvent extends object = SyntheticEvent
>(handler: (event: TEvent) => void): ThrottledHandler<TEvent> {
const isMounted = useMounted()
const eventHandler = useEventCallback(handler)

Expand Down
26 changes: 16 additions & 10 deletions test/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { ReactWrapper, mount } from 'enzyme'
import { renderHook as baseRenderHook } from '@testing-library/react-hooks'
import React from 'react'

type ReactWrapper<P> = {
setProps(props: Partial<P>): void
unmount(): void
}

export function renderHook<T extends (props: P) => any, P = any>(
fn: T,
initialProps?: P,
): [ReturnType<T>, ReactWrapper<P>] {
const result = Array(2) as any

function Wrapper(props: any) {
result[0] = fn(props)
return <span />
}

result[1] = mount(<Wrapper {...initialProps} />)
const { rerender, result, unmount } = baseRenderHook(fn, { initialProps })

return result
return [
result.current,
{
unmount,
setProps(props: P) {
rerender({ ...initialProps, ...props })
},
},
]
}
6 changes: 1 addition & 5 deletions test/setup.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import matchMediaPolyfill from 'mq-polyfill'

Enzyme.configure({ adapter: new Adapter() })

// https://github.com/bigslycat/mq-polyfill

if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -32,7 +28,7 @@ function onError(e) {
actualErrors += 1
}

expect.errors = num => {
expect.errors = (num) => {
expectedErrors = num
}

Expand Down
3 changes: 1 addition & 2 deletions test/useBreakpoint.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import useBreakpoint, {
createBreakpointHook,
} from '../src/useBreakpoint'

import React from 'react'
import { renderHook, act } from '@testing-library/react-hooks'
import { renderHook } from '@testing-library/react-hooks'

interface Props {
breakpoint: DefaultBreakpointMap
Expand Down
11 changes: 5 additions & 6 deletions test/useCallbackRef.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react'
import { act } from 'react-dom/test-utils'
import { mount } from 'enzyme'
import { render, act } from '@testing-library/react'

import useCallbackRef from '../src/useCallbackRef'

Expand All @@ -20,18 +19,18 @@ describe('useCallbackRef', () => {
return toggle ? <div ref={attachRef} /> : <span ref={attachRef} />
}

const wrapper = mount(<Wrapper toggle={false} />)
const wrapper = render(<Wrapper toggle={false} />)

expect(wrapper.children().type()).toEqual('span')
expect(wrapper.container.getElementsByTagName('span')).toHaveLength(1)
expect(effectSpy).toHaveBeenLastCalledWith(
expect.objectContaining({ tagName: 'SPAN' }),
)

act(() => {
wrapper.setProps({ toggle: true })
wrapper.rerender(<Wrapper toggle={true} />)
})

expect(wrapper.children().type()).toEqual('div')
expect(wrapper.container.getElementsByTagName('div')).toHaveLength(1)
expect(effectSpy).toHaveBeenLastCalledWith(
expect.objectContaining({ tagName: 'DIV' }),
)
Expand Down
21 changes: 8 additions & 13 deletions test/useDebouncedCallback.test.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import React from 'react'
import { mount } from 'enzyme'
import useDebouncedCallback from '../src/useDebouncedCallback'
import { renderHook, act } from '@testing-library/react-hooks'

describe('useDebouncedCallback', () => {
it('should return a function that debounces input callback', () => {
jest.useFakeTimers()
const spy = jest.fn()

let debouncedFn;
const { result } = renderHook(() => useDebouncedCallback(spy, 500))

function Wrapper() {
debouncedFn = useDebouncedCallback(spy, 500)
return <span />
}
act(() => {
result.current(1)
result.current(2)
result.current(3)
})

mount(<Wrapper />)

debouncedFn(1)
debouncedFn(2)
debouncedFn(3)
expect(spy).not.toHaveBeenCalled()

jest.runOnlyPendingTimers()

expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith(3)
})
Expand Down
12 changes: 5 additions & 7 deletions test/useDebouncedState.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import { mount } from 'enzyme'
import { act } from 'react-dom/test-utils'
import { render, act } from '@testing-library/react'
import useDebouncedState from '../src/useDebouncedState'

describe('useDebouncedState', () => {
Expand All @@ -15,21 +14,20 @@ describe('useDebouncedState', () => {
return <span>{value}</span>
}

const wrapper = mount(<Wrapper />)
expect(wrapper.text()).toBe('0')
const wrapper = render(<Wrapper />)
expect(wrapper.getByText('0')).toBeTruthy()

outerSetValue((cur: number) => cur + 1)
outerSetValue((cur: number) => cur + 1)
outerSetValue((cur: number) => cur + 1)
outerSetValue((cur: number) => cur + 1)
outerSetValue((cur: number) => cur + 1)

expect(wrapper.text()).toBe('0')
expect(wrapper.getByText('0')).toBeTruthy()

act(() => {
jest.runOnlyPendingTimers()
})

expect(wrapper.text()).toBe('1')
expect(wrapper.getByText('1')).toBeTruthy()
})
})
23 changes: 12 additions & 11 deletions test/useDebouncedValue.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import { mount } from 'enzyme'
import { act } from 'react-dom/test-utils'

import useDebouncedValue from '../src/useDebouncedValue'
import { act, render } from '@testing-library/react'

describe('useDebouncedValue', () => {
it('should return a function that debounces input callback', () => {
Expand All @@ -18,21 +18,22 @@ describe('useDebouncedValue', () => {
return <span>{debouncedValue}</span>
}

const { rerender, getByText } = render(<Wrapper value={0} />)

act(() => {
const wrapper = mount(<Wrapper value={0} />)
expect(wrapper.text()).toBe('0')
expect(getByText('0')).toBeTruthy()

wrapper.setProps({ value: 1 })
wrapper.setProps({ value: 2 })
wrapper.setProps({ value: 3 })
wrapper.setProps({ value: 4 })
wrapper.setProps({ value: 5 })
rerender(<Wrapper value={1} />)
rerender(<Wrapper value={2} />)
rerender(<Wrapper value={3} />)
rerender(<Wrapper value={4} />)
rerender(<Wrapper value={5} />)

expect(wrapper.text()).toBe('0')
expect(getByText('0')).toBeTruthy()

jest.runAllTimers()

expect(wrapper.text()).toBe('5')
expect(getByText('5')).toBeTruthy()
expect(count).toBe(2)
})
})
Expand Down
Loading