Skip to content
Open
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
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: CI

on:
push:
branches: ['main']
pull_request:
branches: ['main']

jobs:
test:
name: Typecheck, lint and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v2
- name: Install deps
run: bun install --frozen-lockfile
- name: Typecheck
run: bun run typecheck
- name: Lint
run: bun run lint
- name: Test
run: bun run test
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
}
279 changes: 260 additions & 19 deletions bun.lock

Large diffs are not rendered by default.

30 changes: 19 additions & 11 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ jest.mock("react-native/Libraries/Utilities/Platform", () => ({
}))

jest.mock("react-native", () => {
const RN = jest.requireActual("react-native/Libraries/ReactNative/oss/ReactNativeRenderer-prod")

return {
Platform: {
select: jest.fn((platform) => platform.default),
Expand All @@ -27,12 +25,25 @@ jest.mock("react-native", () => {
remove: jest.fn(),
})),
},
Keyboard: {
addListener: jest.fn(() => ({
remove: jest.fn(),
})),
emit: jest.fn(),
},
Keyboard: (() => {
const listeners = {}

return {
addListener: jest.fn((eventType, listener) => {
listeners[eventType] = listeners[eventType] || []
listeners[eventType].push(listener)

return {
remove: jest.fn(() => {
listeners[eventType] = (listeners[eventType] || []).filter((l) => l !== listener)
}),
}
}),
emit: jest.fn((eventType, event) => {
;(listeners[eventType] || []).forEach((listener) => listener(event))
}),
}
})(),
AccessibilityInfo: {
addEventListener: jest.fn(() => ({
remove: jest.fn(),
Expand All @@ -59,6 +70,3 @@ jest.mock("react-native", () => {
},
}
})

global.window = {}
global.window.addEventListener = () => {}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@
"devDependencies": {
"@changesets/cli": "^2.28.1",
"@jest/globals": "^29.7.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/react-native": "^13.0.1",
"@types/jest": "^29.5.14",
"@types/react": "18.3.18",
"eslint": "9.20.1",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.0",
"jest": "^29.7.0",
"prettier": "3.5.1",
"react": "18.3.1",
"react-native": "0.78.0",
"react-test-renderer": "^19.0.0",
"react-test-renderer": "18.3.1",
"ts-jest": "^29.2.5",
"typescript": "5.7.3",
"typescript-eslint": "^8.24.1"
Expand Down
84 changes: 27 additions & 57 deletions src/useAccessibilityInfo.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAccessibilityInfo } from "./useAccessibilityInfo"
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook, waitFor } from "@testing-library/react-native"
import { AccessibilityChangeEventName, AccessibilityInfo } from "react-native"

Comment on lines 1 to 4
describe("useAccessibilityInfo", () => {
Expand All @@ -19,6 +19,8 @@ describe("useAccessibilityInfo", () => {
if (eventName === event) {
handler = fn
}

return { remove: jest.fn() }
})

return (value: boolean) => handler(value)
Expand All @@ -45,22 +47,16 @@ describe("useAccessibilityInfo", () => {

mockIsScreenReaderEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().screenReaderEnabled,
)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().screenReaderEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("screenReaderChanged")
const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().screenReaderEnabled,
)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().screenReaderEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand All @@ -87,22 +83,16 @@ describe("useAccessibilityInfo", () => {

mockIsGrayscaleEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().grayscaleEnabled,
)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().grayscaleEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("grayscaleChanged")
const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().grayscaleEnabled,
)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().grayscaleEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand All @@ -129,22 +119,16 @@ describe("useAccessibilityInfo", () => {

mockIsInvertColorsEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().invertColorsEnabled,
)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().invertColorsEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("invertColorsChanged")
const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().invertColorsEnabled,
)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().invertColorsEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand All @@ -171,22 +155,16 @@ describe("useAccessibilityInfo", () => {

mockIsReduceMotionEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().reduceMotionEnabled,
)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().reduceMotionEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("reduceMotionChanged")
const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().reduceMotionEnabled,
)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().reduceMotionEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand All @@ -213,22 +191,16 @@ describe("useAccessibilityInfo", () => {

mockIsReduceTransparencyEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().reduceTransparencyEnabled,
)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().reduceTransparencyEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("reduceTransparencyChanged")
const { result, waitForNextUpdate } = renderHook(
() => useAccessibilityInfo().reduceTransparencyEnabled,
)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().reduceTransparencyEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand All @@ -255,18 +227,16 @@ describe("useAccessibilityInfo", () => {

mockIsBoldTextEnabled.mockResolvedValueOnce(defaultValue)

const { result, waitForNextUpdate } = renderHook(() => useAccessibilityInfo().boldTextEnabled)

await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().boldTextEnabled)

expect(result.current).toBe(defaultValue)
await waitFor(() => expect(result.current).toBe(defaultValue))
})

it("should update value when it change", async () => {
const newValue = true
const emit = createEmitChangeEvent("boldTextChanged")
const { result, waitForNextUpdate } = renderHook(() => useAccessibilityInfo().boldTextEnabled)
await waitForNextUpdate() // wait when promise will be resolved
const { result } = renderHook(() => useAccessibilityInfo().boldTextEnabled)
await waitFor(() => expect(result.current).toBe(false))

const { current: initial } = result

Expand Down
6 changes: 4 additions & 2 deletions src/useAppState.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook } from "@testing-library/react-native"
import { AppState, AppStateStatus } from "react-native"
import { useAppState } from "./useAppState"

jest.mock("react-native", () => ({
AppState: {
currentState: "mock-currentState",
addEventListener: jest.fn(),
addEventListener: jest.fn(() => ({ remove: jest.fn() })),
removeEventListener: jest.fn(),
},
}))
Expand All @@ -17,6 +17,8 @@ describe("useAppState", () => {

addEventListenerMock.mockImplementationOnce((_, fn) => {
listener = fn

return { remove: jest.fn() }
})

return (newStatus: AppStateStatus) => listener(newStatus)
Expand Down
2 changes: 1 addition & 1 deletion src/useBackHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useBackHandler } from "./useBackHandler"
import { renderHook } from "@testing-library/react-hooks"
import { renderHook } from "@testing-library/react-native"
import { BackHandler } from "react-native"

const removeEventListenerMock = jest.fn()
Expand Down
2 changes: 1 addition & 1 deletion src/useImageDimensions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useImageDimensions } from "./useImageDimensions"
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook } from "@testing-library/react-native"
import { Image } from "react-native"

jest.mock("react-native", () => ({
Expand Down
6 changes: 4 additions & 2 deletions src/useInteractionManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useInteractionManager } from "./useInteractionManager"
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook } from "@testing-library/react-native"
import { InteractionManager } from "react-native"

jest.mock("react-native", () => ({
InteractionManager: {
runAfterInteractions: jest.fn(),
runAfterInteractions: jest.fn(() => ({ cancel: jest.fn() })),
},
}))

Expand All @@ -22,6 +22,8 @@ describe("useInteractionManager", () => {

runAfterInteractionsMock.mockImplementationOnce((cb) => {
emitAfterInteractions = cb

return { cancel: jest.fn() }
})

const { result } = renderHook(() => useInteractionManager())
Expand Down
2 changes: 1 addition & 1 deletion src/useKeyboard.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useKeyboard } from "./useKeyboard"
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook } from "@testing-library/react-native"
import { Keyboard } from "react-native"

describe("useKeyboard", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/useLayout.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderHook, act } from "@testing-library/react-hooks"
import { renderHook, act } from "@testing-library/react-native"
import { LayoutRectangle, NativeSyntheticEvent } from "react-native"
import { useLayout } from "./useLayout"

Expand Down
6 changes: 3 additions & 3 deletions src/useRefresh.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { act, renderHook } from "@testing-library/react-hooks"
import { act, renderHook } from "@testing-library/react-native"
import { useRefresh } from "./useRefresh"

const DELAY_IN_MS = 300
jest.useFakeTimers()
describe("useRefresh", () => {
it("should invoke refresh and return correct refreshing state", () => {
it("should invoke refresh and return correct refreshing state", async () => {
const wait = () => {
return new Promise((resolve) => setTimeout(resolve, DELAY_IN_MS))
}
Expand All @@ -18,7 +18,7 @@ describe("useRefresh", () => {

expect(result.current.isRefreshing).toBe(true)
expect(spy).toHaveBeenCalledTimes(1)
act(() => {
await act(async () => {
jest.advanceTimersByTime(DELAY_IN_MS)
})

Expand Down