diff --git a/packages/happy-dom/src/match-media/IMediaQueryRange.ts b/packages/happy-dom/src/match-media/IMediaQueryRange.ts new file mode 100644 index 000000000..6989cd72c --- /dev/null +++ b/packages/happy-dom/src/match-media/IMediaQueryRange.ts @@ -0,0 +1,5 @@ +export default interface IMediaQueryRange { + before: { value: string; operator: string }; + type: string; + after: { value: string; operator: string }; +} diff --git a/packages/happy-dom/src/match-media/IMediaQueryRule.ts b/packages/happy-dom/src/match-media/IMediaQueryRule.ts new file mode 100644 index 000000000..ff9d2a860 --- /dev/null +++ b/packages/happy-dom/src/match-media/IMediaQueryRule.ts @@ -0,0 +1,4 @@ +export default interface IMediaQueryRule { + name: string; + value: string | null; +} diff --git a/packages/happy-dom/src/match-media/MediaQueryDeviceEnum.ts b/packages/happy-dom/src/match-media/MediaQueryDeviceEnum.ts deleted file mode 100644 index b95b59db4..000000000 --- a/packages/happy-dom/src/match-media/MediaQueryDeviceEnum.ts +++ /dev/null @@ -1,7 +0,0 @@ -enum MediaQueryDeviceEnum { - all = 'all', - print = 'print', - screen = 'screen' -} - -export default MediaQueryDeviceEnum; diff --git a/packages/happy-dom/src/match-media/MediaQueryItem.ts b/packages/happy-dom/src/match-media/MediaQueryItem.ts index d566347dd..4f2377b8c 100644 --- a/packages/happy-dom/src/match-media/MediaQueryItem.ts +++ b/packages/happy-dom/src/match-media/MediaQueryItem.ts @@ -1,13 +1,16 @@ import IWindow from '../window/IWindow'; -import MediaQueryDeviceEnum from './MediaQueryDeviceEnum'; +import IMediaQueryRange from './IMediaQueryRange'; +import IMediaQueryRule from './IMediaQueryRule'; +import MediaQueryTypeEnum from './MediaQueryTypeEnum'; /** * Media query this. */ export default class MediaQueryItem { - public devices: MediaQueryDeviceEnum[]; + public mediaTypes: MediaQueryTypeEnum[]; public not: boolean; - public rules: Array<{ name: string; value: string | null }>; + public rules: IMediaQueryRule[]; + public ranges: IMediaQueryRange[]; private ownerWindow: IWindow; /** @@ -15,30 +18,42 @@ export default class MediaQueryItem { * * @param ownerWindow Window. * @param [options] Options. - * @param [options.devices] Devices. + * @param [options.mediaTypes] Media types. * @param [options.not] Not. * @param [options.rules] Rules. + * @param options.ranges */ constructor( ownerWindow: IWindow, options?: { - devices?: MediaQueryDeviceEnum[]; + mediaTypes?: MediaQueryTypeEnum[]; not?: boolean; - rules?: Array<{ name: string; value: string | null }>; + rules?: IMediaQueryRule[]; + ranges?: IMediaQueryRange[]; } ) { this.ownerWindow = ownerWindow; - this.devices = (options && options.devices) || []; + this.mediaTypes = (options && options.mediaTypes) || []; this.not = (options && options.not) || false; this.rules = (options && options.rules) || []; + this.ranges = (options && options.ranges) || []; } /** * Returns media string. */ public toString(): string { - return `${this.not ? 'not ' : ''}${this.devices.join(', ')}${ - (this.not || this.devices.length > 0) && !!this.rules.length ? ' and ' : '' + return `${this.not ? 'not ' : ''}${this.mediaTypes.join(', ')}${ + (this.not || this.mediaTypes.length > 0) && !!this.ranges.length ? ' and ' : '' + }${this.ranges + .map( + (range) => + `(${range.before ? `${range.before.value} ${range.before.operator} ` : ''}${range.type}${ + range.after ? ` ${range.after.operator} ${range.after.value}` : '' + })` + ) + .join(' and ')}${ + (this.not || this.mediaTypes.length > 0) && !!this.rules.length ? ' and ' : '' }${this.rules .map((rule) => (rule.value ? `(${rule.name}: ${rule.value})` : `(${rule.name})`)) .join(' and ')}`; @@ -57,16 +72,16 @@ export default class MediaQueryItem { * @returns "true" if all matches. */ private matchesAll(): boolean { - if (!!this.devices.length) { - let isDeviceMatch = false; - for (const device of this.devices) { - if (this.matchesDevice(device)) { - isDeviceMatch = true; + if (!!this.mediaTypes.length) { + let isMediaTypeMatch = false; + for (const mediaType of this.mediaTypes) { + if (this.matchesMediaType(mediaType)) { + isMediaTypeMatch = true; break; } } - if (!isDeviceMatch) { + if (!isMediaTypeMatch) { return false; } } @@ -77,35 +92,107 @@ export default class MediaQueryItem { } } + for (const range of this.ranges) { + if (!this.matchesRange(range)) { + return false; + } + } + return true; } /** - * Returns "true" if the device matches. + * Returns "true" if the mediaType matches. * - * @param device Device. - * @returns "true" if the device matches. + * @param mediaType Media type. + * @returns "true" if the mediaType matches. */ - private matchesDevice(device: MediaQueryDeviceEnum): boolean { - switch (device) { - case MediaQueryDeviceEnum.all: - return true; - case MediaQueryDeviceEnum.screen: - return true; - case MediaQueryDeviceEnum.print: - return false; + private matchesMediaType(mediaType: MediaQueryTypeEnum): boolean { + if (mediaType === MediaQueryTypeEnum.all) { + return true; + } + return ( + mediaType === + (this.ownerWindow.happyDOM.settings.device.mediaType) + ); + } + + /** + * Returns "true" if the range matches. + * + * @param range Range. + * @returns "true" if the range matches. + */ + private matchesRange(range: IMediaQueryRange): boolean { + const size = + range.type === 'width' ? this.ownerWindow.innerWidth : this.ownerWindow.innerHeight; + + if (range.before) { + const beforeValue = parseInt(range.before.value, 10); + if (!isNaN(beforeValue)) { + switch (range.before.operator) { + case '<': + if (beforeValue >= size) { + return false; + } + break; + case '<=': + if (beforeValue > size) { + return false; + } + break; + case '>': + if (beforeValue <= size) { + return false; + } + break; + case '>=': + if (beforeValue < size) { + return false; + } + break; + } + } + } + + if (range.after) { + const afterValue = parseInt(range.after.value, 10); + if (!isNaN(afterValue)) { + switch (range.after.operator) { + case '<': + if (size >= afterValue) { + return false; + } + break; + case '<=': + if (size > afterValue) { + return false; + } + break; + case '>': + if (size <= afterValue) { + return false; + } + break; + case '>=': + if (size < afterValue) { + return false; + } + break; + } + } } + + return true; } /** * Returns "true" if the rule matches. * * @param rule Rule. - * @param rule.name Rule name. - * @param rule.value Rule value. * @returns "true" if the rule matches. */ - private matchesRule(rule: { name: string; value: string | null }): boolean { + private matchesRule(rule: IMediaQueryRule): boolean { if (!rule.value) { switch (rule.name) { case 'min-width': @@ -145,7 +232,7 @@ export default class MediaQueryItem { ? this.ownerWindow.innerWidth > this.ownerWindow.innerHeight : this.ownerWindow.innerWidth < this.ownerWindow.innerHeight; case 'prefers-color-scheme': - return rule.value === this.ownerWindow.happyDOM.settings.colorScheme; + return rule.value === this.ownerWindow.happyDOM.settings.device.prefersColorScheme; case 'any-hover': case 'hover': if (rule.value === 'none') { diff --git a/packages/happy-dom/src/match-media/MediaQueryParser.ts b/packages/happy-dom/src/match-media/MediaQueryParser.ts index fcd59666c..a73b46162 100644 --- a/packages/happy-dom/src/match-media/MediaQueryParser.ts +++ b/packages/happy-dom/src/match-media/MediaQueryParser.ts @@ -1,5 +1,5 @@ import MediaQueryItem from './MediaQueryItem'; -import MediaQueryDeviceEnum from './MediaQueryDeviceEnum'; +import MediaQueryTypeEnum from './MediaQueryTypeEnum'; import IWindow from '../window/IWindow'; /** @@ -21,16 +21,14 @@ const IS_RESOLUTION_REGEXP = /[<>]/; /** * Resolution RegExp. * - * Group 1: First resolution number. - * Group 2: First resolution entity. - * Group 3: First resolution operator. - * Group 4: Resolution type. - * Group 5: Second resolution operator. - * Group 6: Second resolution number. - * Group 7: Second resolution entity. + * Group 1: First resolution value. + * Group 2: First resolution operator. + * Group 3: Resolution type. + * Group 4: Second resolution operator. + * Group 5: Second resolution value. */ const RESOLUTION_REGEXP = - /(?:([0-9]+)([a-z]+) *(<|<=|>|=>)){0,1} *(width|height) *(?:(<|<=|>|=>) *([0-9]+)([a-z]+)){0,1}/; + /(?:([0-9]+[a-z]+) *(<|<=|>|=>)){0,1} *(width|height) *(?:(<|<=|>|=>) *([0-9]+[a-z]+)){0,1}/; /** * Utility for parsing a query string. @@ -54,49 +52,35 @@ export default class MediaQueryParser { currentMediaQueryItem = new MediaQueryItem(ownerWindow); mediaQueryItems.push(currentMediaQueryItem); } else if (match[1] === 'all' || match[1] === 'screen' || match[1] === 'print') { - currentMediaQueryItem.devices.push(match[1]); + currentMediaQueryItem.mediaTypes.push(match[1]); } else if (match[1] === 'not') { currentMediaQueryItem.not = true; } else if (match[2]) { const resolutionMatch = IS_RESOLUTION_REGEXP.test(match[2]) ? match[2].match(RESOLUTION_REGEXP) : null; - if (resolutionMatch && (resolutionMatch[1] || resolutionMatch[6])) { - if (resolutionMatch[1] && resolutionMatch[2] && resolutionMatch[3]) { - const value = parseInt(resolutionMatch[1], 10); - const parsedValue = - resolutionMatch[1] === '<' - ? value - 1 - : resolutionMatch[1] === '>' - ? value + 1 - : value; - currentMediaQueryItem.rules.push({ - name: `${resolutionMatch[3] === '<' || resolutionMatch[3] === '<=' ? 'max' : 'min'}-${ - resolutionMatch[3] - }`, - value: `${parsedValue}${resolutionMatch[2]}` - }); - } else if (resolutionMatch[4] && resolutionMatch[5] && resolutionMatch[6]) { - const value = parseInt(resolutionMatch[1], 10); - const parsedValue = - resolutionMatch[6] === '<' - ? value + 1 - : resolutionMatch[6] === '>' - ? value - 1 - : value; - currentMediaQueryItem.rules.push({ - name: `${resolutionMatch[6] === '<' || resolutionMatch[6] === '<=' ? 'min' : 'max'}-${ - resolutionMatch[5] - }`, - value: `${parsedValue}${resolutionMatch[5]}` - }); - } + if (resolutionMatch && (resolutionMatch[1] || resolutionMatch[5])) { + currentMediaQueryItem.ranges.push({ + before: resolutionMatch[1] + ? { + value: resolutionMatch[1], + operator: resolutionMatch[2] + } + : null, + type: resolutionMatch[3], + after: resolutionMatch[5] + ? { + value: resolutionMatch[5], + operator: resolutionMatch[4] + } + : null + }); } else { const [name, value] = match[2].split(':'); const trimmedValue = value ? value.trim() : null; if (!trimmedValue && !match[3]) { return [ - new MediaQueryItem(ownerWindow, { not: true, devices: [MediaQueryDeviceEnum.all] }) + new MediaQueryItem(ownerWindow, { not: true, mediaTypes: [MediaQueryTypeEnum.all] }) ]; } currentMediaQueryItem.rules.push({ diff --git a/packages/happy-dom/src/match-media/MediaQueryTypeEnum.ts b/packages/happy-dom/src/match-media/MediaQueryTypeEnum.ts new file mode 100644 index 000000000..87222fb3c --- /dev/null +++ b/packages/happy-dom/src/match-media/MediaQueryTypeEnum.ts @@ -0,0 +1,7 @@ +enum MediaQueryTypeEnum { + all = 'all', + print = 'print', + screen = 'screen' +} + +export default MediaQueryTypeEnum; diff --git a/packages/happy-dom/src/window/HappyDOMSettingsMediaTypeEnum.ts b/packages/happy-dom/src/window/HappyDOMSettingsMediaTypeEnum.ts new file mode 100644 index 000000000..07e6867ec --- /dev/null +++ b/packages/happy-dom/src/window/HappyDOMSettingsMediaTypeEnum.ts @@ -0,0 +1,6 @@ +enum HappyDOMSettingsMediaTypeEnum { + screen = 'screen', + print = 'print' +} + +export default HappyDOMSettingsMediaTypeEnum; diff --git a/packages/happy-dom/src/window/HappyDOMSettingsPrefersColorSchemeEnum.ts b/packages/happy-dom/src/window/HappyDOMSettingsPrefersColorSchemeEnum.ts new file mode 100644 index 000000000..dc545f432 --- /dev/null +++ b/packages/happy-dom/src/window/HappyDOMSettingsPrefersColorSchemeEnum.ts @@ -0,0 +1,6 @@ +enum HappyDOMSettingsPrefersColorSchemeEnum { + light = 'light', + dark = 'dark' +} + +export default HappyDOMSettingsPrefersColorSchemeEnum; diff --git a/packages/happy-dom/src/window/IHappyDOMOptions.ts b/packages/happy-dom/src/window/IHappyDOMOptions.ts index 5cf962447..70bc19429 100644 --- a/packages/happy-dom/src/window/IHappyDOMOptions.ts +++ b/packages/happy-dom/src/window/IHappyDOMOptions.ts @@ -1,3 +1,6 @@ +import HappyDOMSettingsMediaTypeEnum from './HappyDOMSettingsMediaTypeEnum'; +import HappyDOMSettingsPrefersColorSchemeEnum from './HappyDOMSettingsPrefersColorSchemeEnum'; + /** * Happy DOM options. */ @@ -11,5 +14,9 @@ export default interface IHappyDOMOptions { disableCSSFileLoading?: boolean; disableIframePageLoading?: boolean; enableFileSystemHttpRequests?: boolean; + device?: { + prefersColorScheme?: HappyDOMSettingsPrefersColorSchemeEnum; + mediaType?: HappyDOMSettingsMediaTypeEnum; + }; }; } diff --git a/packages/happy-dom/src/window/IHappyDOMSettings.ts b/packages/happy-dom/src/window/IHappyDOMSettings.ts index 2856a8b3a..0da49c748 100644 --- a/packages/happy-dom/src/window/IHappyDOMSettings.ts +++ b/packages/happy-dom/src/window/IHappyDOMSettings.ts @@ -1,3 +1,6 @@ +import HappyDOMSettingsMediaTypeEnum from './HappyDOMSettingsMediaTypeEnum'; +import HappyDOMSettingsPrefersColorSchemeEnum from './HappyDOMSettingsPrefersColorSchemeEnum'; + /** * Happy DOM settings. */ @@ -7,5 +10,8 @@ export default interface IHappyDOMSettings { disableCSSFileLoading: boolean; disableIframePageLoading: boolean; enableFileSystemHttpRequests: boolean; - colorScheme: string; + device: { + prefersColorScheme: HappyDOMSettingsPrefersColorSchemeEnum; + mediaType: HappyDOMSettingsMediaTypeEnum; + }; } diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 4767ad1f7..454415232 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -135,6 +135,8 @@ import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum'; import IHappyDOMOptions from './IHappyDOMOptions'; import RadioNodeList from '../nodes/html-form-element/RadioNodeList'; import ValidityState from '../validity-state/ValidityState'; +import HappyDOMSettingsPrefersColorSchemeEnum from './HappyDOMSettingsPrefersColorSchemeEnum'; +import HappyDOMSettingsMediaTypeEnum from './HappyDOMSettingsMediaTypeEnum'; const ORIGINAL_SET_TIMEOUT = setTimeout; const ORIGINAL_CLEAR_TIMEOUT = clearTimeout; @@ -178,7 +180,10 @@ export default class Window extends EventTarget implements IWindow { disableCSSFileLoading: false, disableIframePageLoading: false, enableFileSystemHttpRequests: false, - colorScheme: 'light' + device: { + prefersColorScheme: HappyDOMSettingsPrefersColorSchemeEnum.light, + mediaType: HappyDOMSettingsMediaTypeEnum.screen + } } }; @@ -434,7 +439,14 @@ export default class Window extends EventTarget implements IWindow { } if (options?.settings) { - this.happyDOM.settings = Object.assign(this.happyDOM.settings, options.settings); + this.happyDOM.settings = { + ...this.happyDOM.settings, + ...options.settings, + device: { + ...this.happyDOM.settings.device, + ...options.settings.device + } + }; } this._setTimeout = ORIGINAL_SET_TIMEOUT; diff --git a/packages/happy-dom/test/match-media/MediaQueryList.test.ts b/packages/happy-dom/test/match-media/MediaQueryList.test.ts index e57ac62d9..01c927ff2 100644 --- a/packages/happy-dom/test/match-media/MediaQueryList.test.ts +++ b/packages/happy-dom/test/match-media/MediaQueryList.test.ts @@ -2,6 +2,8 @@ import IWindow from '../../src/window/IWindow'; import Window from '../../src/window/Window'; import MediaQueryList from '../../src/match-media/MediaQueryList'; import MediaQueryListEvent from '../../src/event/events/MediaQueryListEvent'; +import HappyDOMSettingsPrefersColorSchemeEnum from '../../src/window/HappyDOMSettingsPrefersColorSchemeEnum'; +import HappyDOMSettingsMediaTypeEnum from '../../src/window/HappyDOMSettingsMediaTypeEnum'; describe('MediaQueryList', () => { let window: IWindow; @@ -22,6 +24,20 @@ describe('MediaQueryList', () => { expect(new MediaQueryList(window, 'all and (hover: none').media).toBe( 'all and (hover: none)' ); + expect( + new MediaQueryList( + window, + 'all and (400px <= height <= 2000px) and (400px <= width <= 2000px)' + ).media + ).toBe('all and (400px <= height <= 2000px) and (400px <= width <= 2000px)'); + expect( + new MediaQueryList( + window, + 'all and (400px <= height <= 2000px) and (400px <= width <= 2000px) and (min-width: 400px)' + ).media + ).toBe( + 'all and (400px <= height <= 2000px) and (400px <= width <= 2000px) and (min-width: 400px)' + ); expect(new MediaQueryList(window, 'prefers-color-scheme').media).toBe(''); expect(new MediaQueryList(window, '(prefers-color-scheme').media).toBe('not all'); expect(new MediaQueryList(window, '(prefers-color-scheme)').media).toBe( @@ -31,16 +47,21 @@ describe('MediaQueryList', () => { }); describe('get matches()', () => { - it('Handles device with name "all".', () => { + it('Handles media type with name "all".', () => { expect(new MediaQueryList(window, 'all and (min-width: 1024px)').matches).toBe(true); }); - it('Handles device with name "print".', () => { + it('Handles media type with name "print".', () => { expect(new MediaQueryList(window, 'print').matches).toBe(false); expect(new MediaQueryList(window, 'print and (min-width: 1024px)').matches).toBe(false); + + window.happyDOM.settings.device.mediaType = HappyDOMSettingsMediaTypeEnum.print; + + expect(new MediaQueryList(window, 'print').matches).toBe(true); + expect(new MediaQueryList(window, 'print and (min-width: 1024px)').matches).toBe(true); }); - it('Handles device with name "screen".', () => { + it('Handles media type with name "screen".', () => { expect(new MediaQueryList(window, 'screen').matches).toBe(true); expect(new MediaQueryList(window, 'screen and (min-width: 1024px)').matches).toBe(true); }); @@ -52,6 +73,12 @@ describe('MediaQueryList', () => { expect(new MediaQueryList(window, 'not (min-width: 1024px)').matches).toBe(false); }); + it('Handles "only" keyword.', () => { + expect(new MediaQueryList(window, 'only all').matches).toBe(true); + expect(new MediaQueryList(window, 'only print').matches).toBe(false); + expect(new MediaQueryList(window, 'only screen and (min-width: 1024px)').matches).toBe(true); + }); + it('Handles "min-width".', () => { expect(new MediaQueryList(window, '(min-width)').matches).toBe(true); expect(new MediaQueryList(window, '(min-width: 1025px)').matches).toBe(false); @@ -93,7 +120,8 @@ describe('MediaQueryList', () => { expect(new MediaQueryList(window, '(prefers-color-scheme: dark)').matches).toBe(false); expect(new MediaQueryList(window, '(prefers-color-scheme: light)').matches).toBe(true); - window.happyDOM.settings.colorScheme = 'dark'; + window.happyDOM.settings.device.prefersColorScheme = + HappyDOMSettingsPrefersColorSchemeEnum.dark; expect(new MediaQueryList(window, '(prefers-color-scheme: dark)').matches).toBe(true); expect(new MediaQueryList(window, '(prefers-color-scheme: light)').matches).toBe(false); @@ -121,6 +149,70 @@ describe('MediaQueryList', () => { expect(new MediaQueryList(window, '(any-pointer: coarse)').matches).toBe(false); expect(new MediaQueryList(window, '(any-pointer: fine)').matches).toBe(true); }); + + it('Handles "display-mode".', () => { + expect(new MediaQueryList(window, '(display-mode)').matches).toBe(true); + expect(new MediaQueryList(window, '(display-mode: invalid)').matches).toBe(false); + expect(new MediaQueryList(window, '(display-mode: browser)').matches).toBe(true); + }); + + it('Handles "min-aspect-ratio".', () => { + expect(new MediaQueryList(window, '(min-aspect-ratio)').matches).toBe(true); + expect(new MediaQueryList(window, '(min-aspect-ratio: 1024/770)').matches).toBe(true); + expect(new MediaQueryList(window, '(min-aspect-ratio: 1024/760)').matches).toBe(false); + }); + + it('Handles "max-aspect-ratio".', () => { + expect(new MediaQueryList(window, '(max-aspect-ratio)').matches).toBe(true); + expect(new MediaQueryList(window, '(max-aspect-ratio: 1024/760)').matches).toBe(true); + expect(new MediaQueryList(window, '(max-aspect-ratio: 1024/770)').matches).toBe(false); + }); + + it('Handles "aspect-ratio".', () => { + expect(new MediaQueryList(window, '(aspect-ratio)').matches).toBe(true); + expect(new MediaQueryList(window, '(aspect-ratio: 1024/768)').matches).toBe(true); + expect(new MediaQueryList(window, '(aspect-ratio: 1024/769)').matches).toBe(false); + expect(new MediaQueryList(window, '(aspect-ratio: 1024/767)').matches).toBe(false); + }); + + it('Handles defining a resolution range using the range syntax.', () => { + expect(new MediaQueryList(window, '(400px <= width)').matches).toBe(true); + expect(new MediaQueryList(window, '(400px < width)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px < width)').matches).toBe(false); + expect(new MediaQueryList(window, '(400px <= width <= 2000px)').matches).toBe(true); + expect(new MediaQueryList(window, '(400px <= width <= 1023px)').matches).toBe(false); + expect(new MediaQueryList(window, '(400px <= width <= 1024px)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px => width)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px > width)').matches).toBe(true); + expect(new MediaQueryList(window, '(700px > width)').matches).toBe(false); + + expect(new MediaQueryList(window, '(400px <= height)').matches).toBe(true); + expect(new MediaQueryList(window, '(400px < height)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px < height)').matches).toBe(false); + expect(new MediaQueryList(window, '(400px <= height <= 2000px)').matches).toBe(true); + expect(new MediaQueryList(window, '(400px <= height <= 767px)').matches).toBe(false); + expect(new MediaQueryList(window, '(400px <= height <= 768px)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px => height)').matches).toBe(true); + expect(new MediaQueryList(window, '(2000px > height)').matches).toBe(true); + expect(new MediaQueryList(window, '(700px > height)').matches).toBe(false); + + expect( + new MediaQueryList(window, '(400px <= height <= 2000px) and (400px <= width <= 2000px)') + .matches + ).toBe(true); + }); + + it('Handles multiple rules.', () => { + expect( + new MediaQueryList(window, '(min-width: 1024px) and (max-width: 2000px)').matches + ).toBe(true); + expect(new MediaQueryList(window, '(min-width: 768px) and (max-width: 1023px)').matches).toBe( + false + ); + expect( + new MediaQueryList(window, 'screen and (min-width: 1024px) and (max-width: 2000px)').matches + ).toBe(true); + }); }); describe('addEventListener()', () => {