Skip to content

Commit

Permalink
Merge pull request #2199 from system-ui/match-media-breakpoints
Browse files Browse the repository at this point in the history
Support full media queries in `theme.breakpoints` in @theme-ui/match-media hooks
  • Loading branch information
hasparus authored Apr 21, 2022
2 parents 6c0b2a4 + de50e7d commit d2946ef
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 137 deletions.
2 changes: 1 addition & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@emotion/react": "^11",
"react": ">16",
"react-dom": ">16",
"theme-ui": "^0.11"
"theme-ui": "^0.14.2"
},
"devDependencies": {
"react": "^17.0.1",
Expand Down
10 changes: 7 additions & 3 deletions packages/match-media/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ export const useBreakpointIndex = (options: defaultOptions = {}) => {
const [value, setValue] = useState(defaultIndex)
useEffect(() => {
const getIndex = () =>
breakpoints.filter(
(bp) => window.matchMedia(`screen and (min-width: ${bp})`).matches
).length
breakpoints.filter((bp) => {
const query = bp.includes('@media')
? bp.replace('@media ', '')
: `screen and (min-width: ${bp})`

return window.matchMedia(query).matches
}).length

const onResize = () => {
const newValue = getIndex()
Expand Down
131 changes: 0 additions & 131 deletions packages/match-media/test/index.js

This file was deleted.

148 changes: 148 additions & 0 deletions packages/match-media/test/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* @jest-environment jsdom
* @jsx jsx
*/

import { jsx, Theme } from 'theme-ui'
import { cleanup, act, renderHook } from '@theme-ui/test-utils'
import { useResponsiveValue, useBreakpointIndex } from '../src'

const mockMediaQueries = (matches: string[]) =>
jest.fn().mockImplementation((query) => ({
matches: matches.includes(query),
}))

afterEach(cleanup)

describe('renders correct initial values and uses default breakpoints', () => {
test('no breakpoints matched', () => {
window.matchMedia = mockMediaQueries([])

const { result } = renderHook(() => useResponsiveValue(['a', 'b', 'c']))

expect(result).toEqual('a')
})

test('all breakpoints matched', () => {
window.matchMedia = mockMediaQueries([
'screen and (min-width: 40em)',
'screen and (min-width: 52em)',
'screen and (min-width: 64em)',
])

const { result } = renderHook(() =>
useResponsiveValue(['a', 'b', 'c', 'd'])
)

expect(result).toEqual('d')
})

test('uses the last value provided', () => {
window.matchMedia = mockMediaQueries([
'screen and (min-width: 40em)',
'screen and (min-width: 52em)',
])

const {
result: { value, index },
} = renderHook(() => {
return {
value: useResponsiveValue(['a', 'b']),
index: useBreakpointIndex(),
}
})

expect(value).toEqual('b')
expect(index).toEqual(2)
})
})

describe('reads breakpoints from theme', () => {
test('simple breakpoints', () => {
window.matchMedia = mockMediaQueries([
'screen and (min-width: 30em)',
'screen and (min-width: 45em)',
])

const { result } = renderHook(
() => {
return {
value: useResponsiveValue(['a', 'b']),
index: useBreakpointIndex(),
}
},
{
theme: {
breakpoints: ['30em', '45em', '55em'],
},
}
)

expect(result).toEqual({ value: 'b', index: 2 })
})

test('breakpoints containing media queries', () => {
const matchMedia = (window.matchMedia = mockMediaQueries([
'screen and (min-width: 300px)',
'screen and (min-width: 500px) and (max-width: 899px)',
]))

const theme: Theme = {
breakpoints: [
'300px',
'@media screen and (min-width: 500px) and (max-width: 899px)',
'@media screen and (min-width: 900px)',
],
}

const { result } = renderHook(
() => {
return {
value: useResponsiveValue(['a', 'b', 'c', 'd']),
index: useBreakpointIndex(),
}
},
{ theme }
)

expect(matchMedia).toHaveBeenCalledWith('screen and (min-width: 300px)')
expect(matchMedia).toHaveBeenCalledWith(
'screen and (min-width: 500px) and (max-width: 899px)'
)
expect(matchMedia).toHaveBeenCalledWith('screen and (min-width: 900px)')

expect(result).toEqual({ value: 'c', index: 2 })
})
})

test('responds to resize event', () => {
window.matchMedia = mockMediaQueries([
'screen and (min-width: 40em)',
'screen and (min-width: 52em)',
'screen and (min-width: 64em)',
])

let onResize: () => void
window.addEventListener = jest.fn().mockImplementation((event, cb) => {
if (event === 'resize') onResize = cb
})

const rendered = renderHook(() => useResponsiveValue(['a', 'b', 'c', 'd']))

expect(rendered.result).toEqual('d')

window.matchMedia = mockMediaQueries([
'screen and (min-width: 40em)',
'screen and (min-width: 52em)',
])
act(() => onResize())
expect(rendered.result).toEqual('c')

window.matchMedia = mockMediaQueries(['screen and (min-width: 40em)'])
act(() => onResize())
expect(rendered.result).toEqual('b')

window.matchMedia = mockMediaQueries([])
act(() => onResize())
expect(rendered.result).toEqual('a')
})
File renamed without changes.
8 changes: 8 additions & 0 deletions packages/test-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,13 @@
"conditional-type-checks": "^1.0.5",
"react-test-renderer": "17.0.2",
"ts-snippet": "^5.0.0"
},
"peerDependencies": {
"theme-ui": "0.14.2",
"react": ">16"
},
"devDependencies": {
"theme-ui": "0.14.2",
"react": ">16"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as React from 'react'
import { render } from '@testing-library/react'
import renderer from 'react-test-renderer'
import * as tsSnippet from 'ts-snippet'
import { Theme, ThemeProvider } from 'theme-ui'

export * from '@testing-library/react'

Expand All @@ -16,6 +19,31 @@ export type {
NotHas,
} from 'conditional-type-checks'

export const renderHook = <T,>(
useHook: () => T,
options: { theme?: Theme } = {}
) => {
let value: T | undefined = undefined

const Component = () => {
value = useHook()
return null
}

const { theme } = options
const { unmount, rerender } = render(<Component />, {
wrapper: theme && ((props) => <ThemeProvider theme={theme} {...props} />),
})

return {
unmount,
rerender,
get result() {
return value!
},
}
}

export const renderJSON = (
el: Parameters<typeof renderer.create>[0]
): renderer.ReactTestRendererJSON | null => {
Expand Down
2 changes: 1 addition & 1 deletion packages/theme-ui/test/color-modes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ test('dot notation works with color modes', () => {
sx={{
color: 'header.title',
}}
onClick={(e) => {
onClick={() => {
setMode('dark')
}}
children="test"
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.options.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"jsx": "react",
"noEmit": true,
"module": "es2015",
"isolatedModules": true
"isolatedModules": true,
"target": "es2018"
}
}

1 comment on commit d2946ef

@vercel
Copy link

@vercel vercel bot commented on d2946ef Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.