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
76 changes: 76 additions & 0 deletions web-app/src/hooks/__tests__/use-mobile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it, expect, vi, afterEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useIsMobile } from '../use-mobile'

afterEach(() => {
vi.restoreAllMocks()
vi.useRealTimers()
})

describe('useIsMobile', () => {
it('returns false on a wide screen', () => {
Object.defineProperty(window, 'innerWidth', { value: 1024, writable: true })
const mql = {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
}
vi.spyOn(window, 'matchMedia').mockReturnValue(mql as any)

const { result } = renderHook(() => useIsMobile())
expect(result.current).toBe(false)
})

it('returns true on a narrow screen', () => {
Object.defineProperty(window, 'innerWidth', { value: 400, writable: true })
const mql = {
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
}
vi.spyOn(window, 'matchMedia').mockReturnValue(mql as any)

const { result } = renderHook(() => useIsMobile())
expect(result.current).toBe(true)
})

it('falls back to deprecated addListener on older browsers that lack addEventListener on MediaQueryList', () => {
const consoleWarnSpy = vi
.spyOn(console, 'warn')
.mockImplementation(() => {})

const mql = {
addEventListener: vi.fn(() => {
throw new Error('addEventListener not supported on MediaQueryList')
}),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
}
vi.spyOn(window, 'matchMedia').mockReturnValue(mql as any)
Object.defineProperty(window, 'innerWidth', { value: 1024, writable: true })

renderHook(() => useIsMobile())

expect(mql.addListener).toHaveBeenCalledWith(expect.any(Function))
consoleWarnSpy.mockRestore()
})

it('removes the deprecated listener on unmount in older browsers', () => {
vi.spyOn(console, 'warn').mockImplementation(() => {})

const mql = {
addEventListener: vi.fn(() => {
throw new Error('addEventListener not supported')
}),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
}
vi.spyOn(window, 'matchMedia').mockReturnValue(mql as any)
Object.defineProperty(window, 'innerWidth', { value: 1024, writable: true })

const { unmount } = renderHook(() => useIsMobile())
unmount()

expect(mql.removeListener).toHaveBeenCalledWith(expect.any(Function))
})
})
5 changes: 3 additions & 2 deletions web-app/src/hooks/use-mobile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react"
import { attachMediaListener } from "./useMediaQuery"

const MOBILE_BREAKPOINT = 768

Expand All @@ -10,9 +11,9 @@ export function useIsMobile() {
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
const cleanup = attachMediaListener(mql, onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
return cleanup
}, [])

return !!isMobile
Expand Down
4 changes: 2 additions & 2 deletions web-app/src/hooks/useMediaQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface UseMediaQueryOptions {
getInitialValueInEffect: boolean
}

type MediaQueryCallback = (event: { matches: boolean; media: string }) => void
export type MediaQueryCallback = (event: { matches: boolean; media: string }) => void

// Zustand store for small screen state
type SmallScreenState = {
Expand All @@ -22,7 +22,7 @@ export const useSmallScreenStore = create<SmallScreenState>((set) => ({
* Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia
* https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent
* */
function attachMediaListener(
export function attachMediaListener(
query: MediaQueryList,
callback: MediaQueryCallback
) {
Expand Down