├── .github └── workflows │ └── main.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── jest-setup.cjs ├── package.json ├── polyfill.js ├── polyfill.mjs ├── src └── index.ts ├── tests ├── import.mjs ├── listeners.cjs ├── matchers │ ├── aspect-ratio.cjs │ ├── device-aspect-ratio.cjs │ ├── device-height.cjs │ ├── device-width.cjs │ ├── height.cjs │ ├── media-type.cjs │ ├── prefers-color-scheme.cjs │ └── width.cjs ├── require.cjs └── simple-use.cjs └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [20.x, 22.x, 23.x, 24.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install packages 20 | run: yarn install 21 | - name: Build package 22 | run: yarn build 23 | - name: Run tests 24 | run: yarn test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | dist/ 5 | .cache/ 6 | .*_cache_*/ 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | src/ 5 | tests/ 6 | .github/ 7 | .vscode/ 8 | 9 | .cache/ 10 | .*_cache_*/ 11 | 12 | .prettierrc 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "tabWidth": 4 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "markdown.extension.toc.orderedList": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ayc0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple server-side compatible substitution for `window.matchMedia()` based on [media-query-fns](https://github.com/tbjgolden/media-query-fns). 2 | 3 | 1. [What is `mock-match-media`?](#what-is-mock-match-media) 4 | 2. [Usage](#usage) 5 | 1. [Listeners](#listeners) 6 | 2. [Cleanup](#cleanup) 7 | 1. [`cleanupListeners`](#cleanuplisteners) 8 | 2. [`cleanupMedia`](#cleanupmedia) 9 | 3. [`cleanup`](#cleanup-1) 10 | 3. [Polyfill](#polyfill) 11 | 4. [Other features](#other-features) 12 | 1. [`once` event listeners](#once-event-listeners) 13 | 2. [`.dispatchEvent` \& `MediaQueryListEvent`](#dispatchevent--mediaquerylistevent) 14 | 3. [`.onchange`](#onchange) 15 | 4. [Interactions between multiple listeners](#interactions-between-multiple-listeners) 16 | 5. [ESM](#esm) 17 | 3. [How to use with other libraries](#how-to-use-with-other-libraries) 18 | 1. [Jest](#jest) 19 | 2. [NextJS](#nextjs) 20 | 21 | # What is `mock-match-media`? 22 | 23 | `mock-match-media` is a [ponyfill](https://github.com/sindresorhus/ponyfill) for `window.matchMedia` but for Node. 24 | 25 | This mock is fully compliant with [the spec](https://www.w3.org/TR/mediaqueries-5/) (see doc on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) or [Other features](#other-features)). 26 | 27 | ![Node CI tests](https://github.com/Ayc0/mock-match-media/actions/workflows/main.yml/badge.svg) 28 | 29 | We currently requires at least Node v20.19.0 to be able to `require(ESM)` 30 | 31 | It's also coded in TypeScript. 32 | 33 | # Usage 34 | 35 | ```js 36 | const { matchMedia, setMedia } = require("mock-match-media"); 37 | 38 | // Define current media 39 | setMedia({ 40 | width: 50, 41 | type: "screen", 42 | orientation: "landscape", 43 | prefersColorScheme: "light", 44 | }); 45 | 46 | matchMedia("(min-width: 250px)").matches; 47 | // > false 48 | matchMedia("(width > 40px)").matches; 49 | // > true 50 | 51 | // Only redefine what changed 52 | setMedia({ 53 | width: 500, 54 | }); 55 | 56 | matchMedia("(width > 250px)").matches; 57 | // > true 58 | ``` 59 | 60 | ## Listeners 61 | 62 | `mock-match-media` also supports even listeners: 63 | 64 | ```js 65 | const { matchMedia, setMedia } = require("mock-match-media"); 66 | 67 | setMedia({ 68 | width: 50, 69 | }); 70 | 71 | const listener = (event) => console.log(event.matches); 72 | 73 | const matcher = matchMedia("(min-width: 250px)"); 74 | 75 | matcher.addEventListener("change", listener); 76 | // And also the deprecated version 77 | // matchMedia("(min-width: 250px)").addListener(event => console.log(event.matches)); 78 | 79 | setMedia({ 80 | width: 100, 81 | }); 82 | // outputs nothing because `matches` hasn't changed 83 | 84 | setMedia({ 85 | width: 1000, 86 | }); 87 | // outputs `true` 88 | 89 | matcher.removeEventListener("change", listener); 90 | 91 | setMedia({ 92 | width: 100, 93 | }); 94 | // outputs nothing because the listener is removed 95 | ``` 96 | 97 | ## Cleanup 98 | 99 | `mock-match-media` provides 3 cleanup functions: 100 | 101 | ```js 102 | const { cleanupListeners, cleanupMedia, cleanup } = require("mock-match-media"); 103 | ``` 104 | 105 | ### `cleanupListeners` 106 | 107 | `cleanupListeners` clears all listeners called via `matchMedia().addListener()` or `matchMedia().addEventListener()` (to avoid calling in side effects). 108 | 109 | ### `cleanupMedia` 110 | 111 | `cleanupMedia` resets the state of the window set via `setMedia()`. 112 | 113 | ### `cleanup` 114 | 115 | `cleanup` at the same time clears all listeners (like `cleanupListeners`), and clears the state of the window (like `cleanupMedia`). 116 | 117 | ## Polyfill 118 | 119 | If you don't want to change your code or to setup mocks with your testing library, you can do: 120 | 121 | ```js 122 | require("mock-match-media/polyfill"); 123 | ``` 124 | 125 | And then global variables `matchMedia` and `MediaQueryListEvent` will be set. 126 | And thus, you won't have to import those (you'll still have to import `setMedia`, and the `cleanup` functions). 127 | 128 | ## Other features 129 | 130 | This library covers most of the aspects of `matchMedia`. In addition to the API presented above, it also supports: 131 | 132 | ### `once` event listeners 133 | 134 | ```js 135 | const { matchMedia } = require("mock-match-media"); 136 | 137 | const mql = matchMedia("(min-width: 250px)"); 138 | mql.addEventListener("change", listener, { once: true }); // the listener will be removed after 1 received event 139 | ``` 140 | 141 | ### `.dispatchEvent` & `MediaQueryListEvent` 142 | 143 | Like every other [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget), you can `.dispatchEvent(event)` to manually dispatch event (it’s not that useful to be honest, but as it’s in the spec, we implemented it). 144 | 145 | ```js 146 | const { matchMedia, MediaQueryListEvent } = require("mock-match-media"); 147 | 148 | const mql = matchMedia("(min-width: 250px)"); 149 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 150 | // Works also with a regular event but it’s not recommended: 151 | mql.dispatchEvent(new Event("change")); 152 | ``` 153 | 154 | ### `.onchange` 155 | 156 | Like on any HTML element, you can attach a `.onchange` legacy event handler: 157 | 158 | ```js 159 | const { matchMedia } = require("mock-match-media"); 160 | 161 | const mql = matchMedia("(min-width: 250px)"); 162 | mql.onchange = listener; 163 | ``` 164 | 165 | ### Interactions between multiple listeners 166 | 167 | We follow how browsers implemented interactions like: 168 | 169 | ```js 170 | const { matchMedia } = require("mock-match-media"); 171 | 172 | const mql = matchMedia("(min-width: 250px)"); 173 | mql.onchange = listener; 174 | mql.addListener(listener); 175 | mql.addEventListener("change", listener); 176 | mql.addEventListener("change", listener, { once: true }); 177 | ``` 178 | 179 | And all of those are tested. 180 | 181 | ## ESM 182 | 183 | We also ship 2 versions of this library: 184 | 185 | - one in CJS 186 | - one in ESM 187 | 188 | This is also true for the polyfills, but the [`setup-jest`](#jest) file is only available in CJS (Jest doesn't work that well with ESM). 189 | 190 | # How to use with other libraries 191 | 192 | ## Jest 193 | 194 | In `jest.setup.js`, you only need to import `mock-match-media/jest-setup` (or `mock-match-media/jest-setup.cjs` depending on your config). It'll: 195 | 196 | - install the polyfill (for `matchMedia` and `MediaQueryListEvent`) 197 | - add a call to `cleanup` in `afterAll` to auto-cleanup your env in after each `test`/`it`. 198 | 199 | You can set import `jest-setup` in `setupFiles` or in `setupFilesAfterEnv` in your jest config. 200 | 201 | And then you can use `setMedia` in your tests. 202 | 203 | You can find an example [here](https://github.com/Ayc0/mock-match-media-examples/tree/master/create-react-app) that includes Jest, react testing library and react-scripts. 204 | 205 | ## NextJS 206 | 207 | You can find an example [here](https://github.com/Ayc0/mock-match-media-examples/tree/master/next) for how to use `mock-match-media` with NextJS. 208 | -------------------------------------------------------------------------------- /jest-setup.cjs: -------------------------------------------------------------------------------- 1 | const { cleanup } = require("./"); 2 | 3 | require("./polyfill"); 4 | 5 | if (typeof process === "undefined") { 6 | if (typeof afterEach === "function") { 7 | afterEach(() => { 8 | cleanup(); 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mock-match-media", 3 | "version": "1.0.0-alpha.1", 4 | "description": "mock window.matchMedia for tests or node", 5 | "source": "src/index.ts", 6 | "main": "dist/index.js", 7 | "module": "dist/index.mjs", 8 | "types": "dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/index.d.ts", 12 | "require": "./dist/index.js", 13 | "import": "./dist/index.mjs", 14 | "browser": "./dist/index.mjs" 15 | }, 16 | "./package.json": "./package.json", 17 | "./polyfill": { 18 | "require": "./polyfill.js", 19 | "import": "./polyfill.mjs" 20 | }, 21 | "./jest-setup": { 22 | "require": "./jest-setup.cjs" 23 | } 24 | }, 25 | "repository": "git@github.com:Ayc0/mock-match-media.git", 26 | "author": "Ayc0 ", 27 | "license": "MIT", 28 | "bugs": "https://github.com/Ayc0/mock-match-media/issues", 29 | "homepage": "https://github.com/Ayc0/mock-match-media#readme", 30 | "keywords": [ 31 | "match-media", 32 | "mock", 33 | "test", 34 | "jest", 35 | "ssr", 36 | "server-side-rendering" 37 | ], 38 | "scripts": { 39 | "build": "rm -rf dist && microbundle --target node -f esm,cjs", 40 | "test": "ava", 41 | "prepublishOnly": "yarn build" 42 | }, 43 | "dependencies": { 44 | "media-query-fns": "^2.0.0" 45 | }, 46 | "devDependencies": { 47 | "ava": "^6.3.0", 48 | "microbundle": "^0.15.1", 49 | "mock-match-media": "link:." 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | const mmm = require("./"); 2 | 3 | function inject(variable) { 4 | // Older versions of node don't have `globalThis` (was added in node 12) 5 | if (typeof globalThis !== "undefined" && !(variable in globalThis)) { 6 | globalThis[variable] = mmm[variable]; 7 | } 8 | 9 | // Only node has `global`, not browsers nor deno 10 | if (typeof global !== "undefined" && !(variable in global)) { 11 | global[variable] = mmm[variable]; 12 | } 13 | 14 | // If we want to set it up for window too. 15 | // Some implementation of window mock can have a different value for window and global 16 | if (typeof window !== "undefined" && !(variable in window)) { 17 | window[variable] = mmm[variable]; 18 | } 19 | } 20 | 21 | inject("matchMedia"); 22 | inject("MediaQueryListEvent"); 23 | -------------------------------------------------------------------------------- /polyfill.mjs: -------------------------------------------------------------------------------- 1 | import { matchMedia } from "./dist/index.mjs"; 2 | 3 | // Older versions of node don't have `globalThis` (was added in node 12) 4 | if (typeof globalThis !== "undefined" && !("matchMedia" in globalThis)) { 5 | globalThis.matchMedia = matchMedia; 6 | } 7 | 8 | // Only node has `global`, not browsers nor deno 9 | if (typeof global !== "undefined" && !("matchMedia" in global)) { 10 | global.matchMedia = matchMedia; 11 | } 12 | 13 | // If we want to set it up for window too. 14 | // Some implementation of window mock can have a different value for window and global 15 | if (typeof window !== "undefined" && !("matchMedia" in window)) { 16 | window.matchMedia = matchMedia; 17 | } 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { compileQuery, matches, type Environment, type EvaluateResult, type SimplePerm } from "media-query-fns"; 2 | 3 | type MediaState = { [key in keyof Environment as key extends `${infer Key}Px` ? Key : key]?: Environment[key] }; 4 | 5 | const DEFAULT_ENV: Parameters[1] = { 6 | widthPx: 0, 7 | deviceWidthPx: 0, 8 | heightPx: 0, 9 | deviceHeightPx: 0, 10 | dppx: 1, 11 | }; 12 | 13 | const PIXEL_FEATURES = ["width", "height", "deviceWidth", "deviceHeight"]; 14 | const convertStateToEnv = (state: MediaState): Parameters[1] => { 15 | const env = { ...DEFAULT_ENV }; 16 | 17 | for (const [key, value] of Object.entries(state)) { 18 | if (PIXEL_FEATURES.includes(key)) { 19 | env[key + "Px"] = value; 20 | } else { 21 | env[key] = value; 22 | } 23 | } 24 | 25 | return env; 26 | }; 27 | 28 | let state: MediaState = {}; 29 | 30 | type Feature = keyof MediaState; 31 | 32 | const now = Date.now(); 33 | 34 | // Event was added in node 15, so until we drop the support for versions before it, we need to use this 35 | class EventLegacy { 36 | type: "change"; 37 | timeStamp: number; 38 | 39 | bubbles = false; 40 | cancelBubble = false; 41 | cancelable = false; 42 | composed = false; 43 | target = null; 44 | currentTarget = null; 45 | defaultPrevented = false; 46 | eventPhase = 0; 47 | isTrusted = false; 48 | initEvent = () => {}; 49 | composedPath = () => []; 50 | preventDefault = () => {}; 51 | stopImmediatePropagation = () => {}; 52 | stopPropagation = () => {}; 53 | returnValue = true; 54 | srcElement = null; 55 | // See https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase 56 | NONE = 0; 57 | CAPTURING_PHASE = 1; 58 | AT_TARGET = 2; 59 | BUBBLING_PHASE = 3; 60 | constructor(type: "change") { 61 | this.type = type; 62 | this.timeStamp = Date.now() - now; // See https://developer.mozilla.org/en-US/docs/Web/API/Event/timeStamp#value 63 | } 64 | } 65 | 66 | // @ts-expect-error 67 | const EventCompat: typeof Event = typeof Event === "undefined" ? EventLegacy : Event; 68 | 69 | const getFeaturesFromQuery = (query: EvaluateResult): Set => { 70 | const features = new Set(); 71 | query.simplePerms.forEach((perm) => { 72 | Object.keys(perm).forEach((feature) => features.add(feature as Feature)); 73 | }); 74 | return features; 75 | }; 76 | 77 | type Callback = (event: MediaQueryListEvent) => void; 78 | type MQL = ReturnType; 79 | 80 | const MQLs = new Map void; previousMatched: boolean; features: Set }>(); 81 | 82 | export const matchMedia: typeof window.matchMedia = (query: string) => { 83 | let compiledQuery = compileQuery(query); 84 | let previousMatched; 85 | try { 86 | previousMatched = matches(compiledQuery, convertStateToEnv(state)); 87 | } catch (e) { 88 | compiledQuery = compileQuery("not all"); 89 | previousMatched = false; 90 | } 91 | const callbacks = new Set(); 92 | const onces = new WeakSet(); 93 | 94 | const clear = () => { 95 | for (const callback of callbacks) { 96 | onces.delete(callback); 97 | } 98 | callbacks.clear(); 99 | }; 100 | 101 | const removeListener = (callback: Callback) => { 102 | callbacks.delete(callback); 103 | onces.delete(callback); 104 | }; 105 | 106 | const mql: MQL = { 107 | get matches() { 108 | return matches(compiledQuery, convertStateToEnv(state)); 109 | }, 110 | media: query, 111 | onchange: null, 112 | addEventListener: (event, callback, options) => { 113 | if (event === "change" && callback) { 114 | const isAlreadyListed = callbacks.has(callback); 115 | callbacks.add(callback); 116 | 117 | const hasOnce = typeof options === "object" && options?.once; 118 | 119 | // If it doesn’t have `once: true`, but it was previously added with one, the `once` status should be lifted 120 | if (!hasOnce) { 121 | onces.delete(callback); 122 | return; 123 | } 124 | 125 | // If the callback is already listed in the list of callback to call, but not as a `once`, 126 | // it means that it was added without the flag and thus shouldn’t be treated as such. 127 | if (isAlreadyListed && !onces.has(callback)) { 128 | return; 129 | } 130 | 131 | // Otherwise, use the `once` flag 132 | onces.add(callback); 133 | } 134 | }, 135 | removeEventListener: (event, callback) => { 136 | if (event === "change") removeListener(callback); 137 | }, 138 | dispatchEvent: (event: MediaQueryListEvent) => { 139 | if (!event) { 140 | throw new TypeError( 141 | `Failed to execute 'dispatchEvent' on 'EventTarget': 1 argument required, but only 0 present.`, 142 | ); 143 | } 144 | if (!(event instanceof EventCompat)) { 145 | throw new TypeError( 146 | `Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.`, 147 | ); 148 | } 149 | if (event.type !== "change") { 150 | return true; 151 | } 152 | mql.onchange?.(event); 153 | callbacks.forEach((callback) => { 154 | callback(event); 155 | if (onces.has(callback)) { 156 | removeListener(callback); 157 | } 158 | }); 159 | // TODO: target and currentTarget 160 | // Object.defineProperty(event, "target", { value: mql }); 161 | return true; 162 | }, 163 | addListener: (callback) => { 164 | if (!callback) return; 165 | callbacks.add(callback); 166 | }, 167 | removeListener: (callback) => { 168 | if (!callback) return; 169 | removeListener(callback); 170 | }, 171 | }; 172 | 173 | MQLs.set(mql, { 174 | previousMatched, 175 | clear, 176 | features: getFeaturesFromQuery(compiledQuery), 177 | }); 178 | 179 | return mql; 180 | }; 181 | 182 | export class MediaQueryListEvent extends EventCompat { 183 | readonly media: string; 184 | readonly matches: boolean; 185 | constructor( 186 | type: "change", 187 | options: { 188 | media?: string; 189 | matches?: boolean; 190 | } = {}, 191 | ) { 192 | super(type); 193 | this.media = options.media || ""; 194 | this.matches = options.matches || false; 195 | } 196 | } 197 | 198 | // Cannot use MediaState here as setMedia is exposed in the API 199 | export const setMedia = (media: MediaState) => { 200 | const changedFeatures = new Set(); 201 | Object.keys(media).forEach((feature) => { 202 | changedFeatures.add(feature as Feature); 203 | state[feature] = media[feature]; 204 | }); 205 | 206 | // If we are trying to change the `width` but not the `deviceWidth` 207 | if ((changedFeatures.has("width") || changedFeatures.has("dppx")) && !changedFeatures.has("deviceWidth")) { 208 | state.deviceWidth = (state.width ?? DEFAULT_ENV.widthPx) * (state.dppx ?? DEFAULT_ENV.dppx); 209 | changedFeatures.add("deviceWidth"); 210 | } 211 | // If we are trying to change the `height` but not the `deviceHeight` 212 | if ((changedFeatures.has("height") || changedFeatures.has("dppx")) && !changedFeatures.has("deviceHeight")) { 213 | state.deviceHeight = (state.height ?? DEFAULT_ENV.heightPx) * (state.dppx ?? DEFAULT_ENV.dppx); 214 | changedFeatures.add("deviceHeight"); 215 | } 216 | 217 | for (const [MQL, cache] of MQLs) { 218 | let found = false; 219 | for (const feature of cache.features) { 220 | if (changedFeatures.has(feature)) { 221 | found = true; 222 | break; 223 | } 224 | } 225 | if (!found) { 226 | continue; 227 | } 228 | const doesMatch = matches(compileQuery(MQL.media), convertStateToEnv(state)); 229 | if (doesMatch === cache.previousMatched) { 230 | continue; 231 | } 232 | cache.previousMatched = doesMatch; 233 | MQL.dispatchEvent(new MediaQueryListEvent("change", { matches: doesMatch, media: MQL.media })); 234 | } 235 | }; 236 | 237 | export const cleanupListeners = () => { 238 | for (const { clear } of MQLs.values()) { 239 | clear(); 240 | } 241 | MQLs.clear(); 242 | }; 243 | 244 | export const cleanupMedia = () => { 245 | state = {}; 246 | }; 247 | 248 | export const cleanup = () => { 249 | cleanupListeners(); 250 | cleanupMedia(); 251 | }; 252 | -------------------------------------------------------------------------------- /tests/import.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import test from "ava"; 4 | 5 | test.serial("can import mock-match-media from ESM", async (t) => { 6 | const exportsDefault = await import("mock-match-media"); 7 | t.deepEqual(Object.keys(exportsDefault), [ 8 | "MediaQueryListEvent", 9 | "cleanup", 10 | "cleanupListeners", 11 | "cleanupMedia", 12 | "matchMedia", 13 | "setMedia", 14 | ]); 15 | t.pass(); 16 | }); 17 | 18 | test.serial("can import mock-match-media/polyfill from ESM", async (t) => { 19 | t.is(global.matchMedia, undefined); 20 | const { matchMedia } = await import("mock-match-media"); 21 | 22 | await import("mock-match-media/polyfill"); 23 | 24 | t.is(global.matchMedia, matchMedia); 25 | t.pass(); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/listeners.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupListeners, MediaQueryListEvent, cleanup } = require("mock-match-media"); 5 | 6 | test.afterEach(() => { 7 | // cleanup listeners and state after each test 8 | cleanup(); 9 | }); 10 | 11 | /** 12 | * @type {() => [(event: MediaQueryListEvent) => void, MediaQueryListEvent[]]} 13 | */ 14 | const mock = () => { 15 | const calls = []; 16 | return [ 17 | (event) => { 18 | calls.push(event); 19 | }, 20 | calls, 21 | ]; 22 | }; 23 | 24 | test.serial("`.addListener()`", (t) => { 25 | const mql = matchMedia("(min-width: 500px)"); 26 | 27 | const [cb, calls] = mock(); 28 | 29 | mql.addListener(cb); 30 | 31 | setMedia({ 32 | width: 600, 33 | }); 34 | t.is(calls.length, 1); 35 | t.is(calls[0].matches, true); 36 | 37 | setMedia({ 38 | width: 300, 39 | }); 40 | t.is(calls.length, 2); 41 | t.is(calls[1].matches, false); 42 | 43 | // No new call 44 | setMedia({ 45 | width: 200, 46 | }); 47 | t.is(calls.length, 2); 48 | 49 | // cleanup make it so that new changes in the media won’t trigger listeners 50 | cleanupListeners(); 51 | setMedia({ 52 | width: 600, 53 | }); 54 | t.is(calls.length, 2); 55 | 56 | // same thing for cleanup 57 | mql.addListener(cb); 58 | cleanup(); 59 | 60 | setMedia({ 61 | width: 300, 62 | }); 63 | t.is(calls.length, 2); 64 | 65 | t.pass(); 66 | }); 67 | 68 | test.serial("`.addEventListener()`", (t) => { 69 | const mql = matchMedia("(min-width: 500px)"); 70 | 71 | const [cb, calls] = mock(); 72 | 73 | mql.addEventListener("change", cb); 74 | 75 | setMedia({ 76 | width: 600, 77 | }); 78 | t.is(calls.length, 1); 79 | t.is(calls[0].matches, true); 80 | 81 | setMedia({ 82 | width: 300, 83 | }); 84 | t.is(calls.length, 2); 85 | t.is(calls[1].matches, false); 86 | 87 | // No new call 88 | setMedia({ 89 | width: 200, 90 | }); 91 | t.is(calls.length, 2); 92 | 93 | // cleanup make it so that new changes in the media won’t trigger listeners 94 | cleanupListeners(); 95 | setMedia({ 96 | width: 600, 97 | }); 98 | t.is(calls.length, 2); 99 | 100 | // same thing for cleanup 101 | mql.addListener(cb); 102 | cleanup(); 103 | 104 | setMedia({ 105 | width: 300, 106 | }); 107 | t.is(calls.length, 2); 108 | 109 | t.pass(); 110 | }); 111 | 112 | test.serial("listeners get only called once when multiple features change", (t) => { 113 | const mql = matchMedia("(min-width: 500px) and (min-height: 200px)"); 114 | 115 | const [cb, calls] = mock(); 116 | 117 | t.is(mql.matches, false); 118 | 119 | mql.addEventListener("change", cb); 120 | t.is(calls.length, 0); 121 | 122 | setMedia({ 123 | width: 300, 124 | height: 300, 125 | }); 126 | t.is(calls.length, 0); 127 | 128 | // here it checks that it won’t first apply width, see if it matches, then apply height, see if it matches 129 | setMedia({ 130 | width: 600, 131 | height: 100, 132 | }); 133 | t.is(calls.length, 0); 134 | 135 | setMedia({ 136 | width: 600, 137 | height: 300, 138 | }); 139 | t.is(calls.length, 1); 140 | t.is(calls[0].matches, true); 141 | 142 | t.pass(); 143 | }); 144 | 145 | test.serial( 146 | "ensure that when the same fn is used in multiple listeners, it won’t be called twice on each change", 147 | (t) => { 148 | const mql = matchMedia("(min-width: 500px)"); 149 | 150 | const [cb, calls] = mock(); 151 | 152 | mql.addEventListener("change", cb); 153 | mql.addEventListener("change", cb); 154 | mql.addListener(cb); 155 | mql.addListener(cb); 156 | 157 | setMedia({ 158 | width: 600, 159 | }); 160 | t.is(calls.length, 1); 161 | 162 | t.pass(); 163 | }, 164 | ); 165 | 166 | test.serial("ensure that when the same fn is used in 2 different MQL, it will be called twice", (t) => { 167 | const mql1 = matchMedia("(min-width: 500px)"); 168 | const mql2 = matchMedia("(min-width: 500px)"); 169 | const mql3 = matchMedia("(min-width: 500px)"); 170 | const mql4 = matchMedia("(min-width: 500px)"); 171 | 172 | const [cb, calls] = mock(); 173 | 174 | mql1.addEventListener("change", cb); 175 | mql2.addEventListener("change", cb); 176 | mql3.addListener(cb); 177 | mql4.addListener(cb); 178 | 179 | setMedia({ 180 | width: 600, 181 | }); 182 | t.is(calls.length, 4); 183 | 184 | t.pass(); 185 | }); 186 | 187 | test.serial("`.dispatchEvent()` always calls listeners", (t) => { 188 | const mql1 = matchMedia("(min-width: 500px)"); 189 | const mql2 = matchMedia("(min-width: 500px)"); 190 | 191 | const [cb, calls] = mock(); 192 | 193 | mql1.addEventListener("change", cb); 194 | mql2.addListener(cb); 195 | 196 | mql1.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 197 | mql2.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 198 | t.is(calls.length, 2); 199 | t.is(calls[0].matches, false); 200 | t.is(calls[0].media, "(custom-non-valid)"); 201 | t.is(calls[1].matches, false); 202 | t.is(calls[1].media, "(custom-non-valid)"); 203 | 204 | t.pass(); 205 | }); 206 | 207 | test.serial("`.dispatchEvent()` is only dispatched once", (t) => { 208 | const mql = matchMedia("(min-width: 500px)"); 209 | 210 | const [cb, calls] = mock(); 211 | 212 | mql.addEventListener("change", cb); 213 | mql.addListener(cb); 214 | 215 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 216 | 217 | t.is(calls.length, 1); 218 | 219 | t.pass(); 220 | }); 221 | 222 | test.serial("`.dispatchEvent()` doesn’t call no listener when the event isn’t `change`", (t) => { 223 | const mql = matchMedia("(min-width: 500px)"); 224 | 225 | const [cb1, calls1] = mock(); 226 | const [cb2, calls2] = mock(); 227 | const [cb3, calls3] = mock(); 228 | 229 | mql.addEventListener("change", cb1); 230 | mql.addListener(cb2); 231 | mql.onchange = cb3; 232 | 233 | // @ts-expect-error 234 | mql.dispatchEvent(new MediaQueryListEvent("not-change", { matches: false, media: "(custom-non-valid)" })); 235 | 236 | t.is(calls1.length, 0); 237 | t.is(calls2.length, 0); 238 | t.is(calls3.length, 0); 239 | 240 | t.pass(); 241 | }); 242 | 243 | test.serial("the 2 kinds of listeners can reset each other", (t) => { 244 | const mql1 = matchMedia("(min-width: 500px)"); 245 | const mql2 = matchMedia("(min-width: 500px)"); 246 | 247 | const [cb1, calls1] = mock(); 248 | const [cb2, calls2] = mock(); 249 | 250 | mql1.addEventListener("change", cb1); 251 | mql1.removeListener(cb1); 252 | 253 | mql2.addListener(cb2); 254 | mql2.removeEventListener("change", cb2); 255 | 256 | mql1.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 257 | mql2.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 258 | 259 | t.is(calls1.length, 0); 260 | t.is(calls2.length, 0); 261 | 262 | t.pass(); 263 | }); 264 | 265 | // Note: the concept of "loose" state disappeared from the codebase, but this test is kept to avoid regressions 266 | test.serial("the loose state should disappear after a removeListener", (t) => { 267 | const mql1 = matchMedia("(min-width: 500px)"); 268 | const [cb1, calls1] = mock(); 269 | mql1.addListener(cb1); 270 | mql1.removeListener(cb1); 271 | mql1.addEventListener("change", cb1); 272 | // @ts-expect-error 273 | mql1.dispatchEvent(new MediaQueryListEvent("not-change", { matches: false, media: "(custom-non-valid)" })); 274 | t.is(calls1.length, 0); 275 | 276 | const mql2 = matchMedia("(min-width: 500px)"); 277 | const [cb2, calls2] = mock(); 278 | mql2.addListener(cb2); 279 | mql2.removeEventListener("change", cb2); 280 | mql2.addEventListener("change", cb2); 281 | // @ts-expect-error 282 | mql2.dispatchEvent(new MediaQueryListEvent("not-change", { matches: false, media: "(custom-non-valid)" })); 283 | t.is(calls2.length, 0); 284 | 285 | t.pass(); 286 | }); 287 | 288 | test.serial("the listeners and onchange are fired twice when set together", (t) => { 289 | const mql1 = matchMedia("(min-width: 500px)"); 290 | const [cb1, calls1] = mock(); 291 | mql1.addEventListener("change", cb1); 292 | mql1.onchange = cb1; 293 | mql1.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 294 | t.is(calls1.length, 2); 295 | 296 | const mql2 = matchMedia("(min-width: 500px)"); 297 | const [cb2, calls2] = mock(); 298 | mql2.addListener(cb2); 299 | mql2.onchange = cb2; 300 | mql2.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 301 | t.is(calls2.length, 2); 302 | }); 303 | 304 | test.serial("the listeners can’t disable `onchange`", (t) => { 305 | const mql1 = matchMedia("(min-width: 500px)"); 306 | const [cb1, calls1] = mock(); 307 | mql1.onchange = cb1; 308 | mql1.removeEventListener("change", cb1); 309 | mql1.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 310 | t.is(calls1.length, 1); 311 | 312 | const mql2 = matchMedia("(min-width: 500px)"); 313 | const [cb2, calls2] = mock(); 314 | mql2.onchange = cb2; 315 | mql2.removeListener(cb2); 316 | mql2.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 317 | t.is(calls2.length, 1); 318 | 319 | const mql3 = matchMedia("(min-width: 500px)"); 320 | const [cb3, calls3] = mock(); 321 | mql3.onchange = cb3; 322 | mql3.addEventListener("change", cb3, { once: true }); 323 | mql3.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 324 | t.is(calls3.length, 2); // 2 because of the .onchange + the .addEventListener 325 | mql3.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 326 | t.is(calls3.length, 3); // `once` didn’t prevent it from receiving the 2nd event 327 | }); 328 | 329 | test.serial("`once: true` doesn’t clear any listener after 1 call", (t) => { 330 | const mql = matchMedia("(min-width: 500px)"); 331 | const [cb, calls] = mock(); 332 | mql.addListener(cb); 333 | mql.addEventListener("change", cb); 334 | mql.addEventListener("change", cb, { once: true }); 335 | 336 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 337 | t.is(calls.length, 1); 338 | 339 | // The other listeners are still here 340 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 341 | t.is(calls.length, 2); 342 | 343 | // It only clear once too 344 | mql.addEventListener("change", cb); 345 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 346 | t.is(calls.length, 3); 347 | }); 348 | 349 | test.serial("`once: true` should be cleared after a regular `removeEventListener`", (t) => { 350 | const mql = matchMedia("(min-width: 500px)"); 351 | const [cb, calls] = mock(); 352 | mql.addEventListener("change", cb, { once: true }); 353 | mql.removeEventListener("change", cb); 354 | mql.addEventListener("change", cb); 355 | 356 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 357 | t.is(calls.length, 1); 358 | // The listener works as expected 359 | mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })); 360 | t.is(calls.length, 2); 361 | }); 362 | 363 | test.serial("`.dispatchEvent` can only receive events", (t) => { 364 | const mql = matchMedia("(min-width: 500px)"); 365 | if (typeof Event !== "undefined") { 366 | t.is(mql.dispatchEvent(new Event("hello")), true); 367 | } 368 | t.is(mql.dispatchEvent(new MediaQueryListEvent("change", { matches: false, media: "(custom-non-valid)" })), true); 369 | 370 | // @ts-expect-error 371 | const err1 = t.throws(() => mql.dispatchEvent()); 372 | t.is(err1.message, `Failed to execute 'dispatchEvent' on 'EventTarget': 1 argument required, but only 0 present.`); 373 | 374 | // @ts-expect-error 375 | const err2 = t.throws(() => mql.dispatchEvent("hello")); 376 | t.is(err2.message, `Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.`); 377 | }); 378 | -------------------------------------------------------------------------------- /tests/matchers/aspect-ratio.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | // TODO: Seems to be a bug? To investigate. Maybe because the default aspect ratio is 0/0 11 | test.serial.skip("unset", (t) => { 12 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, true); 13 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, false); 14 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, false); 15 | 16 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, false); 17 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, false); 18 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, false); 19 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, false); 20 | 21 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, false); 22 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, false); 23 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, false); 24 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, false); 25 | 26 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, false); 27 | t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, false); 28 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, false); 29 | t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, false); 30 | 31 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, false); 32 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, false); 33 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, false); 34 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, false); 35 | 36 | t.pass(); 37 | }); 38 | 39 | test.serial("10/16", (t) => { 40 | setMedia({ 41 | width: 10, 42 | height: 16, 43 | }); 44 | 45 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, true); 46 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, false); 47 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, false); 48 | 49 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, true); 50 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, true); 51 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, false); 52 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, false); 53 | 54 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, false); 55 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, false); 56 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, true); 57 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, true); 58 | 59 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, false); 60 | t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, false); 61 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, false); 62 | t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, false); 63 | 64 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, false); 65 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, false); 66 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, false); 67 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, false); 68 | 69 | t.pass(); 70 | }); 71 | 72 | test.serial("9/16", (t) => { 73 | setMedia({ 74 | width: 9, 75 | height: 16, 76 | }); 77 | 78 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, true); 79 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, true); 80 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, true); 81 | 82 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, false); 83 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, true); 84 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, false); 85 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, true); 86 | 87 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, false); 88 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, true); 89 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, false); 90 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, true); 91 | 92 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, false); 93 | // t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 94 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, false); 95 | // t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 96 | 97 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, false); 98 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, true); 99 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, true); 100 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, false); 101 | 102 | t.pass(); 103 | }); 104 | 105 | test.serial("6/16", (t) => { 106 | setMedia({ 107 | width: 6, 108 | height: 16, 109 | }); 110 | 111 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, false); 112 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, false); 113 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, true); 114 | 115 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, false); 116 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, false); 117 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, true); 118 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, true); 119 | 120 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, true); 121 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, true); 122 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, false); 123 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, false); 124 | 125 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, true); 126 | t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, true); 127 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, true); 128 | t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, true); 129 | 130 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, true); 131 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, true); 132 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, true); 133 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, true); 134 | 135 | t.pass(); 136 | }); 137 | 138 | test.serial("5/16", (t) => { 139 | setMedia({ 140 | width: 5, 141 | height: 16, 142 | }); 143 | 144 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, false); 145 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, false); 146 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, true); 147 | 148 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, false); 149 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, false); 150 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, true); 151 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, true); 152 | 153 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, true); 154 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, true); 155 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, false); 156 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, false); 157 | 158 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, false); 159 | t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, true); 160 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, true); 161 | t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, false); 162 | 163 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, false); 164 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, true); 165 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, false); 166 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, true); 167 | 168 | t.pass(); 169 | }); 170 | 171 | test.serial("4/16", (t) => { 172 | setMedia({ 173 | width: 4, 174 | height: 16, 175 | }); 176 | 177 | t.is(matchMedia("(min-aspect-ratio: 9/16)").matches, false); 178 | t.is(matchMedia("(aspect-ratio: 9/16)").matches, false); 179 | t.is(matchMedia("(max-aspect-ratio: 9/16)").matches, true); 180 | 181 | t.is(matchMedia("(aspect-ratio > 9/16)").matches, false); 182 | t.is(matchMedia("(aspect-ratio >= 9/16)").matches, false); 183 | t.is(matchMedia("(aspect-ratio < 9/16)").matches, true); 184 | t.is(matchMedia("(aspect-ratio <= 9/16)").matches, true); 185 | 186 | t.is(matchMedia("(9/16 > aspect-ratio)").matches, true); 187 | t.is(matchMedia("(9/16 >= aspect-ratio)").matches, true); 188 | t.is(matchMedia("(9/16 < aspect-ratio)").matches, false); 189 | t.is(matchMedia("(9/16 <= aspect-ratio)").matches, false); 190 | 191 | t.is(matchMedia("(9/16 > aspect-ratio > 5/16)").matches, false); 192 | t.is(matchMedia("(9/16 >= aspect-ratio >= 5/16)").matches, false); 193 | t.is(matchMedia("(9/16 > aspect-ratio >= 5/16)").matches, false); 194 | t.is(matchMedia("(9/16 >= aspect-ratio > 5/16)").matches, false); 195 | 196 | t.is(matchMedia("(5/16 < aspect-ratio < 9/16)").matches, false); 197 | t.is(matchMedia("(5/16 <= aspect-ratio <= 9/16)").matches, false); 198 | t.is(matchMedia("(5/16 < aspect-ratio <= 9/16)").matches, false); 199 | t.is(matchMedia("(5/16 <= aspect-ratio < 9/16)").matches, false); 200 | 201 | t.pass(); 202 | }); 203 | 204 | test.serial("other syntax", (t) => { 205 | setMedia({ 206 | width: 16, 207 | height: 16, 208 | }); 209 | 210 | t.is(matchMedia("(aspect-ratio: 16/16)").matches, true); 211 | t.is(matchMedia("(aspect-ratio: 8/8)").matches, true); 212 | // optional second denominator 213 | t.is(matchMedia("(aspect-ratio: 1)").matches, true); 214 | // floats 215 | t.is(matchMedia("(aspect-ratio: 1.0)").matches, true); 216 | // only support floats when only 1 number – TO CHECK 217 | // t.is(matchMedia("(aspect-ratio: 16.0/16.0)").matches, true); 218 | // can have spaces 219 | t.is(matchMedia("(aspect-ratio: 16/ 16)").matches, true); 220 | t.is(matchMedia("(aspect-ratio: 16 /16)").matches, true); 221 | t.is(matchMedia("(aspect-ratio: 16 / 16)").matches, true); 222 | 223 | setMedia({ 224 | width: 2, 225 | height: 1, 226 | }); 227 | t.is(matchMedia("(aspect-ratio: 32/16)").matches, true); 228 | 229 | setMedia({ 230 | width: 3, 231 | height: 2, 232 | }); 233 | t.is(matchMedia("(aspect-ratio: 3/2)").matches, true); 234 | 235 | setMedia({ 236 | width: 1, 237 | height: 1, 238 | }); 239 | t.is(matchMedia("(aspect-ratio: 1)").matches, true); 240 | 241 | setMedia({ 242 | width: 2, 243 | height: 1, 244 | }); 245 | t.is(matchMedia("(aspect-ratio: 2)").matches, true); 246 | 247 | setMedia({ 248 | width: 6, 249 | height: 2, 250 | }); 251 | t.is(matchMedia("(aspect-ratio: 3)").matches, true); 252 | 253 | t.pass(); 254 | }); 255 | -------------------------------------------------------------------------------- /tests/matchers/device-aspect-ratio.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | // TODO: Seems to be a bug? To investigate. Maybe because the default aspect ratio is 0/0 11 | test.serial.skip("unset", (t) => { 12 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, false); 13 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, false); 14 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, false); 15 | 16 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, false); 17 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, false); 18 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, false); 19 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, false); 20 | 21 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, false); 22 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, false); 23 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, false); 24 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, false); 25 | 26 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, false); 27 | t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, false); 28 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, false); 29 | t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, false); 30 | 31 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, false); 32 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, false); 33 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, false); 34 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, false); 35 | 36 | t.pass(); 37 | }); 38 | 39 | test.serial("10/16", (t) => { 40 | setMedia({ 41 | deviceWidth: 10, 42 | deviceHeight: 16, 43 | }); 44 | 45 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, true); 46 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, false); 47 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, false); 48 | 49 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, true); 50 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, true); 51 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, false); 52 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, false); 53 | 54 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, false); 55 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, false); 56 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, true); 57 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, true); 58 | 59 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, false); 60 | t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, false); 61 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, false); 62 | t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, false); 63 | 64 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, false); 65 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, false); 66 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, false); 67 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, false); 68 | 69 | t.pass(); 70 | }); 71 | 72 | test.serial("9/16", (t) => { 73 | setMedia({ 74 | deviceWidth: 9, 75 | deviceHeight: 16, 76 | }); 77 | 78 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, true); 79 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, true); 80 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, true); 81 | 82 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, false); 83 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, true); 84 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, false); 85 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, true); 86 | 87 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, false); 88 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, true); 89 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, false); 90 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, true); 91 | 92 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, false); 93 | // t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 94 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, false); 95 | // t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 96 | 97 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, false); 98 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, true); 99 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, true); 100 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, false); 101 | 102 | t.pass(); 103 | }); 104 | 105 | test.serial("6/16", (t) => { 106 | setMedia({ 107 | deviceWidth: 6, 108 | deviceHeight: 16, 109 | }); 110 | 111 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, false); 112 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, false); 113 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, true); 114 | 115 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, false); 116 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, false); 117 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, true); 118 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, true); 119 | 120 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, true); 121 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, true); 122 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, false); 123 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, false); 124 | 125 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, true); 126 | t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, true); 127 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, true); 128 | t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, true); 129 | 130 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, true); 131 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, true); 132 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, true); 133 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, true); 134 | 135 | t.pass(); 136 | }); 137 | 138 | test.serial("5/16", (t) => { 139 | setMedia({ 140 | deviceWidth: 5, 141 | deviceHeight: 16, 142 | }); 143 | 144 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, false); 145 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, false); 146 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, true); 147 | 148 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, false); 149 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, false); 150 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, true); 151 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, true); 152 | 153 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, true); 154 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, true); 155 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, false); 156 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, false); 157 | 158 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, false); 159 | t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, true); 160 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, true); 161 | t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, false); 162 | 163 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, false); 164 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, true); 165 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, false); 166 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, true); 167 | 168 | t.pass(); 169 | }); 170 | 171 | test.serial("4/16", (t) => { 172 | setMedia({ 173 | deviceWidth: 4, 174 | deviceHeight: 16, 175 | }); 176 | 177 | t.is(matchMedia("(min-device-aspect-ratio: 9/16)").matches, false); 178 | t.is(matchMedia("(device-aspect-ratio: 9/16)").matches, false); 179 | t.is(matchMedia("(max-device-aspect-ratio: 9/16)").matches, true); 180 | 181 | t.is(matchMedia("(device-aspect-ratio > 9/16)").matches, false); 182 | t.is(matchMedia("(device-aspect-ratio >= 9/16)").matches, false); 183 | t.is(matchMedia("(device-aspect-ratio < 9/16)").matches, true); 184 | t.is(matchMedia("(device-aspect-ratio <= 9/16)").matches, true); 185 | 186 | t.is(matchMedia("(9/16 > device-aspect-ratio)").matches, true); 187 | t.is(matchMedia("(9/16 >= device-aspect-ratio)").matches, true); 188 | t.is(matchMedia("(9/16 < device-aspect-ratio)").matches, false); 189 | t.is(matchMedia("(9/16 <= device-aspect-ratio)").matches, false); 190 | 191 | t.is(matchMedia("(9/16 > device-aspect-ratio > 5/16)").matches, false); 192 | t.is(matchMedia("(9/16 >= device-aspect-ratio >= 5/16)").matches, false); 193 | t.is(matchMedia("(9/16 > device-aspect-ratio >= 5/16)").matches, false); 194 | t.is(matchMedia("(9/16 >= device-aspect-ratio > 5/16)").matches, false); 195 | 196 | t.is(matchMedia("(5/16 < device-aspect-ratio < 9/16)").matches, false); 197 | t.is(matchMedia("(5/16 <= device-aspect-ratio <= 9/16)").matches, false); 198 | t.is(matchMedia("(5/16 < device-aspect-ratio <= 9/16)").matches, false); 199 | t.is(matchMedia("(5/16 <= device-aspect-ratio < 9/16)").matches, false); 200 | 201 | t.pass(); 202 | }); 203 | 204 | test.serial("other syntax", (t) => { 205 | setMedia({ 206 | deviceWidth: 16, 207 | deviceHeight: 16, 208 | }); 209 | 210 | t.is(matchMedia("(device-aspect-ratio: 16/16)").matches, true); 211 | t.is(matchMedia("(device-aspect-ratio: 8/8)").matches, true); 212 | // optional second denominator 213 | t.is(matchMedia("(device-aspect-ratio: 1)").matches, true); 214 | // floats 215 | t.is(matchMedia("(device-aspect-ratio: 1.0)").matches, true); 216 | // only support floats when only 1 number – to check 217 | // t.is(matchMedia("(device-aspect-ratio: 16.0/16.0)").matches, false); 218 | // can have spaces 219 | t.is(matchMedia("(device-aspect-ratio: 16 / 16)").matches, true); 220 | t.is(matchMedia("(device-aspect-ratio: 16 / 16)").matches, true); 221 | t.is(matchMedia("(device-aspect-ratio: 16 / 16)").matches, true); 222 | 223 | t.pass(); 224 | }); 225 | -------------------------------------------------------------------------------- /tests/matchers/device-height.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("(min-device-height: 500px)").matches, false); 12 | t.is(matchMedia("(device-height: 500px)").matches, false); 13 | t.is(matchMedia("(max-device-height: 500px)").matches, true); 14 | 15 | t.is(matchMedia("(device-height > 500px)").matches, false); 16 | t.is(matchMedia("(device-height >= 500px)").matches, false); 17 | t.is(matchMedia("(device-height < 500px)").matches, true); 18 | t.is(matchMedia("(device-height <= 500px)").matches, true); 19 | 20 | t.is(matchMedia("(500px > device-height)").matches, true); 21 | t.is(matchMedia("(500px >= device-height)").matches, true); 22 | t.is(matchMedia("(500px < device-height)").matches, false); 23 | t.is(matchMedia("(500px <= device-height)").matches, false); 24 | 25 | t.is(matchMedia("(500px > device-height > 300px)").matches, false); 26 | t.is(matchMedia("(500px >= device-height >= 300px)").matches, false); 27 | t.is(matchMedia("(500px > device-height >= 300px)").matches, false); 28 | t.is(matchMedia("(500px >= device-height > 300px)").matches, false); 29 | 30 | t.is(matchMedia("(300px < device-height < 500px)").matches, false); 31 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, false); 32 | t.is(matchMedia("(300px < device-height <= 500px)").matches, false); 33 | t.is(matchMedia("(300px <= device-height < 500px)").matches, false); 34 | 35 | t.pass(); 36 | }); 37 | 38 | test.serial("deviceHeight 600px", (t) => { 39 | setMedia({ 40 | deviceHeight: 600, 41 | }); 42 | 43 | t.is(matchMedia("(min-device-height: 500px)").matches, true); 44 | t.is(matchMedia("(device-height: 500px)").matches, false); 45 | t.is(matchMedia("(max-device-height: 500px)").matches, false); 46 | 47 | t.is(matchMedia("(device-height > 500px)").matches, true); 48 | t.is(matchMedia("(device-height >= 500px)").matches, true); 49 | t.is(matchMedia("(device-height < 500px)").matches, false); 50 | t.is(matchMedia("(device-height <= 500px)").matches, false); 51 | 52 | t.is(matchMedia("(500px > device-height)").matches, false); 53 | t.is(matchMedia("(500px >= device-height)").matches, false); 54 | t.is(matchMedia("(500px < device-height)").matches, true); 55 | t.is(matchMedia("(500px <= device-height)").matches, true); 56 | 57 | t.is(matchMedia("(500px > device-height > 300px)").matches, false); 58 | t.is(matchMedia("(500px >= device-height >= 300px)").matches, false); 59 | t.is(matchMedia("(500px > device-height >= 300px)").matches, false); 60 | t.is(matchMedia("(500px >= device-height > 300px)").matches, false); 61 | 62 | t.is(matchMedia("(300px < device-height < 500px)").matches, false); 63 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, false); 64 | t.is(matchMedia("(300px < device-height <= 500px)").matches, false); 65 | t.is(matchMedia("(300px <= device-height < 500px)").matches, false); 66 | 67 | t.pass(); 68 | }); 69 | 70 | test.serial("deviceHeight 500px", (t) => { 71 | setMedia({ 72 | deviceHeight: 500, 73 | }); 74 | 75 | t.is(matchMedia("(min-device-height: 500px)").matches, true); 76 | t.is(matchMedia("(device-height: 500px)").matches, true); 77 | t.is(matchMedia("(max-device-height: 500px)").matches, true); 78 | 79 | t.is(matchMedia("(device-height > 500px)").matches, false); 80 | t.is(matchMedia("(device-height >= 500px)").matches, true); 81 | t.is(matchMedia("(device-height < 500px)").matches, false); 82 | t.is(matchMedia("(device-height <= 500px)").matches, true); 83 | 84 | t.is(matchMedia("(500px > device-height)").matches, false); 85 | t.is(matchMedia("(500px >= device-height)").matches, true); 86 | t.is(matchMedia("(500px < device-height)").matches, false); 87 | t.is(matchMedia("(500px <= device-height)").matches, true); 88 | 89 | t.is(matchMedia("(500px > device-height > 300px)").matches, false); 90 | // t.is(matchMedia("(500px >= device-height >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 91 | t.is(matchMedia("(500px > device-height >= 300px)").matches, false); 92 | // t.is(matchMedia("(500px >= device-height > 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 93 | 94 | t.is(matchMedia("(300px < device-height < 500px)").matches, false); 95 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, true); 96 | t.is(matchMedia("(300px < device-height <= 500px)").matches, true); 97 | t.is(matchMedia("(300px <= device-height < 500px)").matches, false); 98 | 99 | t.pass(); 100 | }); 101 | 102 | test.serial("deviceHeight 400px", (t) => { 103 | setMedia({ 104 | deviceHeight: 400, 105 | }); 106 | 107 | t.is(matchMedia("(min-device-height: 500px)").matches, false); 108 | t.is(matchMedia("(device-height: 500px)").matches, false); 109 | t.is(matchMedia("(max-device-height: 500px)").matches, true); 110 | 111 | t.is(matchMedia("(device-height > 500px)").matches, false); 112 | t.is(matchMedia("(device-height >= 500px)").matches, false); 113 | t.is(matchMedia("(device-height < 500px)").matches, true); 114 | t.is(matchMedia("(device-height <= 500px)").matches, true); 115 | 116 | t.is(matchMedia("(500px > device-height)").matches, true); 117 | t.is(matchMedia("(500px >= device-height)").matches, true); 118 | t.is(matchMedia("(500px < device-height)").matches, false); 119 | t.is(matchMedia("(500px <= device-height)").matches, false); 120 | 121 | // t.is(matchMedia("(500px > device-height > 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 122 | t.is(matchMedia("(500px >= device-height >= 300px)").matches, true); 123 | t.is(matchMedia("(500px > device-height >= 300px)").matches, true); 124 | t.is(matchMedia("(500px >= device-height > 300px)").matches, true); 125 | 126 | t.is(matchMedia("(300px < device-height < 500px)").matches, true); 127 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, true); 128 | t.is(matchMedia("(300px < device-height <= 500px)").matches, true); 129 | t.is(matchMedia("(300px <= device-height < 500px)").matches, true); 130 | 131 | t.pass(); 132 | }); 133 | 134 | test.serial("deviceHeight 300px", (t) => { 135 | setMedia({ 136 | deviceHeight: 300, 137 | }); 138 | 139 | t.is(matchMedia("(min-device-height: 500px)").matches, false); 140 | t.is(matchMedia("(device-height: 500px)").matches, false); 141 | t.is(matchMedia("(max-device-height: 500px)").matches, true); 142 | 143 | t.is(matchMedia("(device-height > 500px)").matches, false); 144 | t.is(matchMedia("(device-height >= 500px)").matches, false); 145 | t.is(matchMedia("(device-height < 500px)").matches, true); 146 | t.is(matchMedia("(device-height <= 500px)").matches, true); 147 | 148 | t.is(matchMedia("(500px > device-height)").matches, true); 149 | t.is(matchMedia("(500px >= device-height)").matches, true); 150 | t.is(matchMedia("(500px < device-height)").matches, false); 151 | t.is(matchMedia("(500px <= device-height)").matches, false); 152 | 153 | t.is(matchMedia("(500px > device-height > 300px)").matches, false); 154 | // t.is(matchMedia("(500px >= device-height >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 155 | t.is(matchMedia("(500px > device-height >= 300px)").matches, true); 156 | t.is(matchMedia("(500px >= device-height > 300px)").matches, false); 157 | 158 | t.is(matchMedia("(300px < device-height < 500px)").matches, false); 159 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, true); 160 | t.is(matchMedia("(300px < device-height <= 500px)").matches, false); 161 | t.is(matchMedia("(300px <= device-height < 500px)").matches, true); 162 | 163 | t.pass(); 164 | }); 165 | 166 | test.serial("deviceHeight 200px", (t) => { 167 | setMedia({ 168 | deviceHeight: 200, 169 | }); 170 | 171 | t.is(matchMedia("(min-device-height: 500px)").matches, false); 172 | t.is(matchMedia("(device-height: 500px)").matches, false); 173 | t.is(matchMedia("(max-device-height: 500px)").matches, true); 174 | 175 | t.is(matchMedia("(device-height > 500px)").matches, false); 176 | t.is(matchMedia("(device-height >= 500px)").matches, false); 177 | t.is(matchMedia("(device-height < 500px)").matches, true); 178 | t.is(matchMedia("(device-height <= 500px)").matches, true); 179 | 180 | t.is(matchMedia("(500px > device-height)").matches, true); 181 | t.is(matchMedia("(500px >= device-height)").matches, true); 182 | t.is(matchMedia("(500px < device-height)").matches, false); 183 | t.is(matchMedia("(500px <= device-height)").matches, false); 184 | 185 | t.is(matchMedia("(500px > device-height > 300px)").matches, false); 186 | t.is(matchMedia("(500px >= device-height >= 300px)").matches, false); 187 | t.is(matchMedia("(500px > device-height >= 300px)").matches, false); 188 | t.is(matchMedia("(500px >= device-height > 300px)").matches, false); 189 | 190 | t.is(matchMedia("(300px < device-height < 500px)").matches, false); 191 | t.is(matchMedia("(300px <= device-height <= 500px)").matches, false); 192 | t.is(matchMedia("(300px < device-height <= 500px)").matches, false); 193 | t.is(matchMedia("(300px <= device-height < 500px)").matches, false); 194 | 195 | t.pass(); 196 | }); 197 | 198 | test.serial("height & dppx", (t) => { 199 | setMedia({ 200 | height: 600, 201 | }); 202 | 203 | t.is(matchMedia("(device-height > 500px)").matches, true); 204 | t.is(matchMedia("(device-height < 700px)").matches, true); 205 | t.is(matchMedia("(device-height: 600px)").matches, true); 206 | 207 | setMedia({ 208 | // Keep the: `height: 600` 209 | dppx: 2, 210 | }); 211 | 212 | t.is(matchMedia("(device-height > 500px)").matches, true); 213 | t.is(matchMedia("(device-height < 700px)").matches, false); 214 | t.is(matchMedia("(device-height: 600px)").matches, false); 215 | t.is(matchMedia("(device-height: 1200px)").matches, true); 216 | 217 | setMedia({ 218 | // Keep the `dppx: 2` 219 | height: 300, 220 | }); 221 | 222 | t.is(matchMedia("(device-height > 500px)").matches, true); 223 | t.is(matchMedia("(device-height < 700px)").matches, true); 224 | t.is(matchMedia("(device-height: 600px)").matches, true); 225 | t.is(matchMedia("(device-height: 300px)").matches, false); 226 | 227 | t.pass(); 228 | }); 229 | -------------------------------------------------------------------------------- /tests/matchers/device-width.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("(min-device-width: 500px)").matches, false); 12 | t.is(matchMedia("(device-width: 500px)").matches, false); 13 | t.is(matchMedia("(max-device-width: 500px)").matches, true); 14 | 15 | t.is(matchMedia("(device-width > 500px)").matches, false); 16 | t.is(matchMedia("(device-width >= 500px)").matches, false); 17 | t.is(matchMedia("(device-width < 500px)").matches, true); 18 | t.is(matchMedia("(device-width <= 500px)").matches, true); 19 | 20 | t.is(matchMedia("(500px > device-width)").matches, true); 21 | t.is(matchMedia("(500px >= device-width)").matches, true); 22 | t.is(matchMedia("(500px < device-width)").matches, false); 23 | t.is(matchMedia("(500px <= device-width)").matches, false); 24 | 25 | t.is(matchMedia("(500px > device-width > 300px)").matches, false); 26 | t.is(matchMedia("(500px >= device-width >= 300px)").matches, false); 27 | t.is(matchMedia("(500px > device-width >= 300px)").matches, false); 28 | t.is(matchMedia("(500px >= device-width > 300px)").matches, false); 29 | 30 | t.is(matchMedia("(300px < device-width < 500px)").matches, false); 31 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, false); 32 | t.is(matchMedia("(300px < device-width <= 500px)").matches, false); 33 | t.is(matchMedia("(300px <= device-width < 500px)").matches, false); 34 | 35 | t.pass(); 36 | }); 37 | 38 | test.serial("device 600px", (t) => { 39 | setMedia({ 40 | deviceWidth: 600, 41 | }); 42 | 43 | t.is(matchMedia("(min-device-width: 500px)").matches, true); 44 | t.is(matchMedia("(device-width: 500px)").matches, false); 45 | t.is(matchMedia("(max-device-width: 500px)").matches, false); 46 | 47 | t.is(matchMedia("(device-width > 500px)").matches, true); 48 | t.is(matchMedia("(device-width >= 500px)").matches, true); 49 | t.is(matchMedia("(device-width < 500px)").matches, false); 50 | t.is(matchMedia("(device-width <= 500px)").matches, false); 51 | 52 | t.is(matchMedia("(500px > device-width)").matches, false); 53 | t.is(matchMedia("(500px >= device-width)").matches, false); 54 | t.is(matchMedia("(500px < device-width)").matches, true); 55 | t.is(matchMedia("(500px <= device-width)").matches, true); 56 | 57 | t.is(matchMedia("(500px > device-width > 300px)").matches, false); 58 | t.is(matchMedia("(500px >= device-width >= 300px)").matches, false); 59 | t.is(matchMedia("(500px > device-width >= 300px)").matches, false); 60 | t.is(matchMedia("(500px >= device-width > 300px)").matches, false); 61 | 62 | t.is(matchMedia("(300px < device-width < 500px)").matches, false); 63 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, false); 64 | t.is(matchMedia("(300px < device-width <= 500px)").matches, false); 65 | t.is(matchMedia("(300px <= device-width < 500px)").matches, false); 66 | 67 | t.pass(); 68 | }); 69 | 70 | test.serial("device 500px", (t) => { 71 | setMedia({ 72 | deviceWidth: 500, 73 | }); 74 | 75 | t.is(matchMedia("(min-device-width: 500px)").matches, true); 76 | t.is(matchMedia("(device-width: 500px)").matches, true); 77 | t.is(matchMedia("(max-device-width: 500px)").matches, true); 78 | 79 | t.is(matchMedia("(device-width > 500px)").matches, false); 80 | t.is(matchMedia("(device-width >= 500px)").matches, true); 81 | t.is(matchMedia("(device-width < 500px)").matches, false); 82 | t.is(matchMedia("(device-width <= 500px)").matches, true); 83 | 84 | t.is(matchMedia("(500px > device-width)").matches, false); 85 | t.is(matchMedia("(500px >= device-width)").matches, true); 86 | t.is(matchMedia("(500px < device-width)").matches, false); 87 | t.is(matchMedia("(500px <= device-width)").matches, true); 88 | 89 | t.is(matchMedia("(500px > device-width > 300px)").matches, false); 90 | // t.is(matchMedia("(500px >= device-width >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 91 | t.is(matchMedia("(500px > device-width >= 300px)").matches, false); 92 | // t.is(matchMedia("(500px >= device-width > 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 93 | 94 | t.is(matchMedia("(300px < device-width < 500px)").matches, false); 95 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, true); 96 | t.is(matchMedia("(300px < device-width <= 500px)").matches, true); 97 | t.is(matchMedia("(300px <= device-width < 500px)").matches, false); 98 | 99 | t.pass(); 100 | }); 101 | 102 | test.serial("device 400px", (t) => { 103 | setMedia({ 104 | deviceWidth: 400, 105 | }); 106 | 107 | t.is(matchMedia("(min-device-width: 500px)").matches, false); 108 | t.is(matchMedia("(device-width: 500px)").matches, false); 109 | t.is(matchMedia("(max-device-width: 500px)").matches, true); 110 | 111 | t.is(matchMedia("(device-width > 500px)").matches, false); 112 | t.is(matchMedia("(device-width >= 500px)").matches, false); 113 | t.is(matchMedia("(device-width < 500px)").matches, true); 114 | t.is(matchMedia("(device-width <= 500px)").matches, true); 115 | 116 | t.is(matchMedia("(500px > device-width)").matches, true); 117 | t.is(matchMedia("(500px >= device-width)").matches, true); 118 | t.is(matchMedia("(500px < device-width)").matches, false); 119 | t.is(matchMedia("(500px <= device-width)").matches, false); 120 | 121 | t.is(matchMedia("(500px > device-width > 300px)").matches, true); 122 | t.is(matchMedia("(500px >= device-width >= 300px)").matches, true); 123 | t.is(matchMedia("(500px > device-width >= 300px)").matches, true); 124 | t.is(matchMedia("(500px >= device-width > 300px)").matches, true); 125 | 126 | t.is(matchMedia("(300px < device-width < 500px)").matches, true); 127 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, true); 128 | t.is(matchMedia("(300px < device-width <= 500px)").matches, true); 129 | t.is(matchMedia("(300px <= device-width < 500px)").matches, true); 130 | 131 | t.pass(); 132 | }); 133 | 134 | test.serial("device 300px", (t) => { 135 | setMedia({ 136 | deviceWidth: 300, 137 | }); 138 | 139 | t.is(matchMedia("(min-device-width: 500px)").matches, false); 140 | t.is(matchMedia("(device-width: 500px)").matches, false); 141 | t.is(matchMedia("(max-device-width: 500px)").matches, true); 142 | 143 | t.is(matchMedia("(device-width > 500px)").matches, false); 144 | t.is(matchMedia("(device-width >= 500px)").matches, false); 145 | t.is(matchMedia("(device-width < 500px)").matches, true); 146 | t.is(matchMedia("(device-width <= 500px)").matches, true); 147 | 148 | t.is(matchMedia("(500px > device-width)").matches, true); 149 | t.is(matchMedia("(500px >= device-width)").matches, true); 150 | t.is(matchMedia("(500px < device-width)").matches, false); 151 | t.is(matchMedia("(500px <= device-width)").matches, false); 152 | 153 | t.is(matchMedia("(500px > device-width > 300px)").matches, false); 154 | // t.is(matchMedia("(500px >= device-width >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 155 | t.is(matchMedia("(500px > device-width >= 300px)").matches, true); 156 | t.is(matchMedia("(500px >= device-width > 300px)").matches, false); 157 | 158 | t.is(matchMedia("(300px < device-width < 500px)").matches, false); 159 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, true); 160 | t.is(matchMedia("(300px < device-width <= 500px)").matches, false); 161 | t.is(matchMedia("(300px <= device-width < 500px)").matches, true); 162 | 163 | t.pass(); 164 | }); 165 | 166 | test.serial("device 200px", (t) => { 167 | setMedia({ 168 | deviceWidth: 200, 169 | }); 170 | 171 | t.is(matchMedia("(min-device-width: 500px)").matches, false); 172 | t.is(matchMedia("(device-width: 500px)").matches, false); 173 | t.is(matchMedia("(max-device-width: 500px)").matches, true); 174 | 175 | t.is(matchMedia("(device-width > 500px)").matches, false); 176 | t.is(matchMedia("(device-width >= 500px)").matches, false); 177 | t.is(matchMedia("(device-width < 500px)").matches, true); 178 | t.is(matchMedia("(device-width <= 500px)").matches, true); 179 | 180 | t.is(matchMedia("(500px > device-width)").matches, true); 181 | t.is(matchMedia("(500px >= device-width)").matches, true); 182 | t.is(matchMedia("(500px < device-width)").matches, false); 183 | t.is(matchMedia("(500px <= device-width)").matches, false); 184 | 185 | t.is(matchMedia("(500px > device-width > 300px)").matches, false); 186 | t.is(matchMedia("(500px >= device-width >= 300px)").matches, false); 187 | t.is(matchMedia("(500px > device-width >= 300px)").matches, false); 188 | t.is(matchMedia("(500px >= device-width > 300px)").matches, false); 189 | 190 | t.is(matchMedia("(300px < device-width < 500px)").matches, false); 191 | t.is(matchMedia("(300px <= device-width <= 500px)").matches, false); 192 | t.is(matchMedia("(300px < device-width <= 500px)").matches, false); 193 | t.is(matchMedia("(300px <= device-width < 500px)").matches, false); 194 | 195 | t.pass(); 196 | }); 197 | 198 | test.serial("width & dppx", (t) => { 199 | setMedia({ 200 | width: 600, 201 | }); 202 | 203 | t.is(matchMedia("(device-width > 500px)").matches, true); 204 | t.is(matchMedia("(device-width < 700px)").matches, true); 205 | t.is(matchMedia("(device-width: 600px)").matches, true); 206 | 207 | setMedia({ 208 | // Keep the: `width: 600` 209 | dppx: 2, 210 | }); 211 | 212 | t.is(matchMedia("(device-width > 500px)").matches, true); 213 | t.is(matchMedia("(device-width < 700px)").matches, false); 214 | t.is(matchMedia("(device-width: 600px)").matches, false); 215 | t.is(matchMedia("(device-width: 1200px)").matches, true); 216 | 217 | setMedia({ 218 | // Keep the `dppx: 2` 219 | width: 300, 220 | }); 221 | 222 | t.is(matchMedia("(device-width > 500px)").matches, true); 223 | t.is(matchMedia("(device-width < 700px)").matches, true); 224 | t.is(matchMedia("(device-width: 600px)").matches, true); 225 | t.is(matchMedia("(device-width: 300px)").matches, false); 226 | 227 | t.pass(); 228 | }); 229 | -------------------------------------------------------------------------------- /tests/matchers/height.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("(min-height: 500px)").matches, false); 12 | t.is(matchMedia("(height: 500px)").matches, false); 13 | t.is(matchMedia("(max-height: 500px)").matches, true); 14 | 15 | t.is(matchMedia("(height > 500px)").matches, false); 16 | t.is(matchMedia("(height >= 500px)").matches, false); 17 | t.is(matchMedia("(height < 500px)").matches, true); 18 | t.is(matchMedia("(height <= 500px)").matches, true); 19 | 20 | t.is(matchMedia("(500px > height)").matches, true); 21 | t.is(matchMedia("(500px >= height)").matches, true); 22 | t.is(matchMedia("(500px < height)").matches, false); 23 | t.is(matchMedia("(500px <= height)").matches, false); 24 | 25 | t.is(matchMedia("(500px > height > 300px)").matches, false); 26 | t.is(matchMedia("(500px >= height >= 300px)").matches, false); 27 | t.is(matchMedia("(500px > height >= 300px)").matches, false); 28 | t.is(matchMedia("(500px >= height > 300px)").matches, false); 29 | 30 | t.is(matchMedia("(300px < height < 500px)").matches, false); 31 | t.is(matchMedia("(300px <= height <= 500px)").matches, false); 32 | t.is(matchMedia("(300px < height <= 500px)").matches, false); 33 | t.is(matchMedia("(300px <= height < 500px)").matches, false); 34 | 35 | t.pass(); 36 | }); 37 | 38 | test.serial("600px", (t) => { 39 | setMedia({ 40 | height: 600, 41 | }); 42 | 43 | t.is(matchMedia("(min-height: 500px)").matches, true); 44 | t.is(matchMedia("(height: 500px)").matches, false); 45 | t.is(matchMedia("(max-height: 500px)").matches, false); 46 | 47 | t.is(matchMedia("(height > 500px)").matches, true); 48 | t.is(matchMedia("(height >= 500px)").matches, true); 49 | t.is(matchMedia("(height < 500px)").matches, false); 50 | t.is(matchMedia("(height <= 500px)").matches, false); 51 | 52 | t.is(matchMedia("(500px > height)").matches, false); 53 | t.is(matchMedia("(500px >= height)").matches, false); 54 | t.is(matchMedia("(500px < height)").matches, true); 55 | t.is(matchMedia("(500px <= height)").matches, true); 56 | 57 | t.is(matchMedia("(500px > height > 300px)").matches, false); 58 | t.is(matchMedia("(500px >= height >= 300px)").matches, false); 59 | t.is(matchMedia("(500px > height >= 300px)").matches, false); 60 | t.is(matchMedia("(500px >= height > 300px)").matches, false); 61 | 62 | t.is(matchMedia("(300px < height < 500px)").matches, false); 63 | t.is(matchMedia("(300px <= height <= 500px)").matches, false); 64 | t.is(matchMedia("(300px < height <= 500px)").matches, false); 65 | t.is(matchMedia("(300px <= height < 500px)").matches, false); 66 | 67 | t.pass(); 68 | }); 69 | 70 | test.serial("500px", (t) => { 71 | setMedia({ 72 | height: 500, 73 | }); 74 | 75 | t.is(matchMedia("(min-height: 500px)").matches, true); 76 | t.is(matchMedia("(height: 500px)").matches, true); 77 | t.is(matchMedia("(max-height: 500px)").matches, true); 78 | 79 | t.is(matchMedia("(height > 500px)").matches, false); 80 | t.is(matchMedia("(height >= 500px)").matches, true); 81 | t.is(matchMedia("(height < 500px)").matches, false); 82 | t.is(matchMedia("(height <= 500px)").matches, true); 83 | 84 | t.is(matchMedia("(500px > height)").matches, false); 85 | t.is(matchMedia("(500px >= height)").matches, true); 86 | t.is(matchMedia("(500px < height)").matches, false); 87 | t.is(matchMedia("(500px <= height)").matches, true); 88 | 89 | t.is(matchMedia("(500px > height > 300px)").matches, false); 90 | // t.is(matchMedia("(500px >= height >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 91 | t.is(matchMedia("(500px > height >= 300px)").matches, false); 92 | // t.is(matchMedia("(500px >= height > 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 93 | 94 | t.is(matchMedia("(300px < height < 500px)").matches, false); 95 | t.is(matchMedia("(300px <= height <= 500px)").matches, true); 96 | t.is(matchMedia("(300px < height <= 500px)").matches, true); 97 | t.is(matchMedia("(300px <= height < 500px)").matches, false); 98 | 99 | t.pass(); 100 | }); 101 | 102 | test.serial("400px", (t) => { 103 | setMedia({ 104 | height: 400, 105 | }); 106 | 107 | t.is(matchMedia("(min-height: 500px)").matches, false); 108 | t.is(matchMedia("(height: 500px)").matches, false); 109 | t.is(matchMedia("(max-height: 500px)").matches, true); 110 | 111 | t.is(matchMedia("(height > 500px)").matches, false); 112 | t.is(matchMedia("(height >= 500px)").matches, false); 113 | t.is(matchMedia("(height < 500px)").matches, true); 114 | t.is(matchMedia("(height <= 500px)").matches, true); 115 | 116 | t.is(matchMedia("(500px > height)").matches, true); 117 | t.is(matchMedia("(500px >= height)").matches, true); 118 | t.is(matchMedia("(500px < height)").matches, false); 119 | t.is(matchMedia("(500px <= height)").matches, false); 120 | 121 | t.is(matchMedia("(500px > height > 300px)").matches, true); 122 | t.is(matchMedia("(500px >= height >= 300px)").matches, true); 123 | t.is(matchMedia("(500px > height >= 300px)").matches, true); 124 | t.is(matchMedia("(500px >= height > 300px)").matches, true); 125 | 126 | t.is(matchMedia("(300px < height < 500px)").matches, true); 127 | t.is(matchMedia("(300px <= height <= 500px)").matches, true); 128 | t.is(matchMedia("(300px < height <= 500px)").matches, true); 129 | t.is(matchMedia("(300px <= height < 500px)").matches, true); 130 | 131 | t.pass(); 132 | }); 133 | 134 | test.serial("300px", (t) => { 135 | setMedia({ 136 | height: 300, 137 | }); 138 | 139 | t.is(matchMedia("(min-height: 500px)").matches, false); 140 | t.is(matchMedia("(height: 500px)").matches, false); 141 | t.is(matchMedia("(max-height: 500px)").matches, true); 142 | 143 | t.is(matchMedia("(height > 500px)").matches, false); 144 | t.is(matchMedia("(height >= 500px)").matches, false); 145 | t.is(matchMedia("(height < 500px)").matches, true); 146 | t.is(matchMedia("(height <= 500px)").matches, true); 147 | 148 | t.is(matchMedia("(500px > height)").matches, true); 149 | t.is(matchMedia("(500px >= height)").matches, true); 150 | t.is(matchMedia("(500px < height)").matches, false); 151 | t.is(matchMedia("(500px <= height)").matches, false); 152 | 153 | t.is(matchMedia("(500px > height > 300px)").matches, false); 154 | t.is(matchMedia("(500px >= height >= 300px)").matches, true); 155 | t.is(matchMedia("(500px > height >= 300px)").matches, true); 156 | t.is(matchMedia("(500px >= height > 300px)").matches, false); 157 | 158 | t.is(matchMedia("(300px < height < 500px)").matches, false); 159 | t.is(matchMedia("(300px <= height <= 500px)").matches, true); 160 | t.is(matchMedia("(300px < height <= 500px)").matches, false); 161 | t.is(matchMedia("(300px <= height < 500px)").matches, true); 162 | 163 | t.pass(); 164 | }); 165 | 166 | test.serial("200px", (t) => { 167 | setMedia({ 168 | height: 200, 169 | }); 170 | 171 | t.is(matchMedia("(min-height: 500px)").matches, false); 172 | t.is(matchMedia("(height: 500px)").matches, false); 173 | t.is(matchMedia("(max-height: 500px)").matches, true); 174 | 175 | t.is(matchMedia("(height > 500px)").matches, false); 176 | t.is(matchMedia("(height >= 500px)").matches, false); 177 | t.is(matchMedia("(height < 500px)").matches, true); 178 | t.is(matchMedia("(height <= 500px)").matches, true); 179 | 180 | t.is(matchMedia("(500px > height)").matches, true); 181 | t.is(matchMedia("(500px >= height)").matches, true); 182 | t.is(matchMedia("(500px < height)").matches, false); 183 | t.is(matchMedia("(500px <= height)").matches, false); 184 | 185 | t.is(matchMedia("(500px > height > 300px)").matches, false); 186 | t.is(matchMedia("(500px >= height >= 300px)").matches, false); 187 | t.is(matchMedia("(500px > height >= 300px)").matches, false); 188 | t.is(matchMedia("(500px >= height > 300px)").matches, false); 189 | 190 | t.is(matchMedia("(300px < height < 500px)").matches, false); 191 | t.is(matchMedia("(300px <= height <= 500px)").matches, false); 192 | t.is(matchMedia("(300px < height <= 500px)").matches, false); 193 | t.is(matchMedia("(300px <= height < 500px)").matches, false); 194 | 195 | t.pass(); 196 | }); 197 | -------------------------------------------------------------------------------- /tests/matchers/media-type.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("all").matches, true); 12 | t.is(matchMedia("screen").matches, true); 13 | t.is(matchMedia("print").matches, false); 14 | 15 | // To note: other media types are deprecated since Media Query 4 16 | // See: 17 | // - https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_types 18 | // - https://drafts.csswg.org/mediaqueries/#media-types 19 | 20 | t.pass(); 21 | }); 22 | 23 | test.serial("screen", (t) => { 24 | setMedia({ 25 | mediaType: "screen", 26 | }); 27 | 28 | t.is(matchMedia("all").matches, true); 29 | t.is(matchMedia("screen").matches, true); 30 | t.is(matchMedia("print").matches, false); 31 | 32 | t.pass(); 33 | }); 34 | 35 | test.serial("print", (t) => { 36 | setMedia({ 37 | mediaType: "print", 38 | }); 39 | 40 | t.is(matchMedia("all").matches, true); 41 | t.is(matchMedia("screen").matches, false); 42 | t.is(matchMedia("print").matches, true); 43 | 44 | t.pass(); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/matchers/prefers-color-scheme.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("(prefers-color-scheme: light)").matches, false); 12 | t.is(matchMedia("(prefers-color-scheme: dark)").matches, false); 13 | 14 | t.pass(); 15 | }); 16 | 17 | test.serial("light", (t) => { 18 | setMedia({ 19 | prefersColorScheme: "light", 20 | }); 21 | 22 | t.is(matchMedia("(prefers-color-scheme: light)").matches, true); 23 | t.is(matchMedia("(prefers-color-scheme: dark)").matches, false); 24 | 25 | t.pass(); 26 | }); 27 | 28 | test.serial("dark", (t) => { 29 | setMedia({ 30 | prefersColorScheme: "dark", 31 | }); 32 | 33 | t.is(matchMedia("(prefers-color-scheme: light)").matches, false); 34 | t.is(matchMedia("(prefers-color-scheme: dark)").matches, true); 35 | 36 | t.pass(); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/matchers/width.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupMedia } = require("mock-match-media"); 5 | 6 | test.beforeEach(() => { 7 | cleanupMedia(); 8 | }); 9 | 10 | test.serial("unset", (t) => { 11 | t.is(matchMedia("(min-width: 500px)").matches, false); 12 | t.is(matchMedia("(width: 500px)").matches, false); 13 | t.is(matchMedia("(max-width: 500px)").matches, true); 14 | 15 | t.is(matchMedia("(width > 500px)").matches, false); 16 | t.is(matchMedia("(width >= 500px)").matches, false); 17 | t.is(matchMedia("(width < 500px)").matches, true); 18 | t.is(matchMedia("(width <= 500px)").matches, true); 19 | 20 | t.is(matchMedia("(500px > width)").matches, true); 21 | t.is(matchMedia("(500px >= width)").matches, true); 22 | t.is(matchMedia("(500px < width)").matches, false); 23 | t.is(matchMedia("(500px <= width)").matches, false); 24 | 25 | t.is(matchMedia("(500px > width > 300px)").matches, false); 26 | t.is(matchMedia("(500px >= width >= 300px)").matches, false); 27 | t.is(matchMedia("(500px > width >= 300px)").matches, false); 28 | t.is(matchMedia("(500px >= width > 300px)").matches, false); 29 | 30 | t.is(matchMedia("(300px < width < 500px)").matches, false); 31 | t.is(matchMedia("(300px <= width <= 500px)").matches, false); 32 | t.is(matchMedia("(300px < width <= 500px)").matches, false); 33 | t.is(matchMedia("(300px <= width < 500px)").matches, false); 34 | 35 | t.pass(); 36 | }); 37 | 38 | test.serial("600px", (t) => { 39 | setMedia({ 40 | width: 600, 41 | }); 42 | 43 | t.is(matchMedia("(min-width: 500px)").matches, true); 44 | t.is(matchMedia("(width: 500px)").matches, false); 45 | t.is(matchMedia("(max-width: 500px)").matches, false); 46 | 47 | t.is(matchMedia("(width > 500px)").matches, true); 48 | t.is(matchMedia("(width >= 500px)").matches, true); 49 | t.is(matchMedia("(width < 500px)").matches, false); 50 | t.is(matchMedia("(width <= 500px)").matches, false); 51 | 52 | t.is(matchMedia("(500px > width)").matches, false); 53 | t.is(matchMedia("(500px >= width)").matches, false); 54 | t.is(matchMedia("(500px < width)").matches, true); 55 | t.is(matchMedia("(500px <= width)").matches, true); 56 | 57 | t.is(matchMedia("(500px > width > 300px)").matches, false); 58 | t.is(matchMedia("(500px >= width >= 300px)").matches, false); 59 | t.is(matchMedia("(500px > width >= 300px)").matches, false); 60 | t.is(matchMedia("(500px >= width > 300px)").matches, false); 61 | 62 | t.is(matchMedia("(300px < width < 500px)").matches, false); 63 | t.is(matchMedia("(300px <= width <= 500px)").matches, false); 64 | t.is(matchMedia("(300px < width <= 500px)").matches, false); 65 | t.is(matchMedia("(300px <= width < 500px)").matches, false); 66 | 67 | t.pass(); 68 | }); 69 | 70 | test.serial("500px", (t) => { 71 | setMedia({ 72 | width: 500, 73 | }); 74 | 75 | t.is(matchMedia("(min-width: 500px)").matches, true); 76 | t.is(matchMedia("(width: 500px)").matches, true); 77 | t.is(matchMedia("(max-width: 500px)").matches, true); 78 | 79 | t.is(matchMedia("(width > 500px)").matches, false); 80 | t.is(matchMedia("(width >= 500px)").matches, true); 81 | t.is(matchMedia("(width < 500px)").matches, false); 82 | t.is(matchMedia("(width <= 500px)").matches, true); 83 | 84 | t.is(matchMedia("(500px > width)").matches, false); 85 | t.is(matchMedia("(500px >= width)").matches, true); 86 | t.is(matchMedia("(500px < width)").matches, false); 87 | t.is(matchMedia("(500px <= width)").matches, true); 88 | 89 | t.is(matchMedia("(500px > width > 300px)").matches, false); 90 | // t.is(matchMedia("(500px >= width >= 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 91 | t.is(matchMedia("(500px > width >= 300px)").matches, false); 92 | // t.is(matchMedia("(500px >= width > 300px)").matches, true); // Bug in media-query-fns https://github.com/tbjgolden/media-query-fns/issues/7 93 | 94 | t.is(matchMedia("(300px < width < 500px)").matches, false); 95 | t.is(matchMedia("(300px <= width <= 500px)").matches, true); 96 | t.is(matchMedia("(300px < width <= 500px)").matches, true); 97 | t.is(matchMedia("(300px <= width < 500px)").matches, false); 98 | 99 | t.pass(); 100 | }); 101 | 102 | test.serial("400px", (t) => { 103 | setMedia({ 104 | width: 400, 105 | }); 106 | 107 | t.is(matchMedia("(min-width: 500px)").matches, false); 108 | t.is(matchMedia("(width: 500px)").matches, false); 109 | t.is(matchMedia("(max-width: 500px)").matches, true); 110 | 111 | t.is(matchMedia("(width > 500px)").matches, false); 112 | t.is(matchMedia("(width >= 500px)").matches, false); 113 | t.is(matchMedia("(width < 500px)").matches, true); 114 | t.is(matchMedia("(width <= 500px)").matches, true); 115 | 116 | t.is(matchMedia("(500px > width)").matches, true); 117 | t.is(matchMedia("(500px >= width)").matches, true); 118 | t.is(matchMedia("(500px < width)").matches, false); 119 | t.is(matchMedia("(500px <= width)").matches, false); 120 | 121 | t.is(matchMedia("(500px > width > 300px)").matches, true); 122 | t.is(matchMedia("(500px >= width >= 300px)").matches, true); 123 | t.is(matchMedia("(500px > width >= 300px)").matches, true); 124 | t.is(matchMedia("(500px >= width > 300px)").matches, true); 125 | 126 | t.is(matchMedia("(300px < width < 500px)").matches, true); 127 | t.is(matchMedia("(300px <= width <= 500px)").matches, true); 128 | t.is(matchMedia("(300px < width <= 500px)").matches, true); 129 | t.is(matchMedia("(300px <= width < 500px)").matches, true); 130 | 131 | t.pass(); 132 | }); 133 | 134 | test.serial("300px", (t) => { 135 | setMedia({ 136 | width: 300, 137 | }); 138 | 139 | t.is(matchMedia("(min-width: 500px)").matches, false); 140 | t.is(matchMedia("(width: 500px)").matches, false); 141 | t.is(matchMedia("(max-width: 500px)").matches, true); 142 | 143 | t.is(matchMedia("(width > 500px)").matches, false); 144 | t.is(matchMedia("(width >= 500px)").matches, false); 145 | t.is(matchMedia("(width < 500px)").matches, true); 146 | t.is(matchMedia("(width <= 500px)").matches, true); 147 | 148 | t.is(matchMedia("(500px > width)").matches, true); 149 | t.is(matchMedia("(500px >= width)").matches, true); 150 | t.is(matchMedia("(500px < width)").matches, false); 151 | t.is(matchMedia("(500px <= width)").matches, false); 152 | 153 | t.is(matchMedia("(500px > width > 300px)").matches, false); 154 | t.is(matchMedia("(500px >= width >= 300px)").matches, true); 155 | t.is(matchMedia("(500px > width >= 300px)").matches, true); 156 | t.is(matchMedia("(500px >= width > 300px)").matches, false); 157 | 158 | t.is(matchMedia("(300px < width < 500px)").matches, false); 159 | t.is(matchMedia("(300px <= width <= 500px)").matches, true); 160 | t.is(matchMedia("(300px < width <= 500px)").matches, false); 161 | t.is(matchMedia("(300px <= width < 500px)").matches, true); 162 | 163 | t.pass(); 164 | }); 165 | 166 | test.serial("200px", (t) => { 167 | setMedia({ 168 | width: 200, 169 | }); 170 | 171 | t.is(matchMedia("(min-width: 500px)").matches, false); 172 | t.is(matchMedia("(width: 500px)").matches, false); 173 | t.is(matchMedia("(max-width: 500px)").matches, true); 174 | 175 | t.is(matchMedia("(width > 500px)").matches, false); 176 | t.is(matchMedia("(width >= 500px)").matches, false); 177 | t.is(matchMedia("(width < 500px)").matches, true); 178 | t.is(matchMedia("(width <= 500px)").matches, true); 179 | 180 | t.is(matchMedia("(500px > width)").matches, true); 181 | t.is(matchMedia("(500px >= width)").matches, true); 182 | t.is(matchMedia("(500px < width)").matches, false); 183 | t.is(matchMedia("(500px <= width)").matches, false); 184 | 185 | t.is(matchMedia("(500px > width > 300px)").matches, false); 186 | t.is(matchMedia("(500px >= width >= 300px)").matches, false); 187 | t.is(matchMedia("(500px > width >= 300px)").matches, false); 188 | t.is(matchMedia("(500px >= width > 300px)").matches, false); 189 | 190 | t.is(matchMedia("(300px < width < 500px)").matches, false); 191 | t.is(matchMedia("(300px <= width <= 500px)").matches, false); 192 | t.is(matchMedia("(300px < width <= 500px)").matches, false); 193 | t.is(matchMedia("(300px <= width < 500px)").matches, false); 194 | 195 | t.pass(); 196 | }); 197 | -------------------------------------------------------------------------------- /tests/require.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | 5 | test.serial("can import mock-match-media from CJS", (t) => { 6 | const exportsDefault = require("mock-match-media"); 7 | t.deepEqual(Object.keys(exportsDefault), [ 8 | "MediaQueryListEvent", 9 | "cleanup", 10 | "cleanupListeners", 11 | "cleanupMedia", 12 | "matchMedia", 13 | "setMedia", 14 | ]); 15 | t.pass(); 16 | }); 17 | 18 | test.serial("can import mock-match-media/polyfill from CJS", (t) => { 19 | t.is(global.matchMedia, undefined); 20 | t.is(global.MediaQueryListEvent, undefined); 21 | const { matchMedia, MediaQueryListEvent } = require("mock-match-media"); 22 | require("mock-match-media/polyfill"); 23 | t.is(global.matchMedia, matchMedia); 24 | t.is(global.MediaQueryListEvent, MediaQueryListEvent); 25 | delete require.cache[require.resolve("mock-match-media/polyfill")]; 26 | // @ts-expect-error 27 | delete global.matchMedia; 28 | t.pass(); 29 | }); 30 | 31 | test.serial("can import mock-match-media/jest-setup from CJS", (t) => { 32 | t.is(global.matchMedia, undefined); 33 | const { matchMedia } = require("mock-match-media"); 34 | require("mock-match-media/jest-setup"); 35 | t.is(global.matchMedia, matchMedia); 36 | delete require.cache[require.resolve("mock-match-media/jest-setup")]; 37 | // @ts-expect-error 38 | delete global.matchMedia; 39 | t.pass(); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/simple-use.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const test = require("ava"); 4 | const { matchMedia, setMedia, cleanupListeners, cleanupMedia, cleanup } = require("mock-match-media"); 5 | 6 | test.serial(".matches", (t) => { 7 | const mql = matchMedia("(min-width: 500px)"); 8 | 9 | t.is(mql.matches, false); 10 | 11 | setMedia({ 12 | width: 600, 13 | }); 14 | 15 | t.is(mql.matches, true); 16 | 17 | setMedia({ 18 | width: 300, 19 | }); 20 | 21 | t.is(mql.matches, false); 22 | 23 | t.pass(); 24 | }); 25 | 26 | test.serial("cleanupMedia", (t) => { 27 | const doesMatch = () => matchMedia("(min-width: 500px)").matches; 28 | 29 | setMedia({ 30 | width: 600, 31 | }); 32 | t.is(doesMatch(), true); 33 | 34 | cleanupMedia(); 35 | t.is(doesMatch(), false); 36 | }); 37 | 38 | test.serial("cleanup", (t) => { 39 | const doesMatch = () => matchMedia("(min-width: 500px)").matches; 40 | 41 | setMedia({ 42 | width: 600, 43 | }); 44 | t.is(doesMatch(), true); 45 | 46 | cleanup(); 47 | t.is(doesMatch(), false); 48 | }); 49 | --------------------------------------------------------------------------------