1 | function noop() {}
|
2 |
|
3 | export interface MediaMatching {
|
4 | (mediaQuery: string): Partial<MediaQueryList>;
|
5 | }
|
6 |
|
7 | export default class MatchMedia {
|
8 | originalMatchMedia: (mediaQuery: string) => MediaQueryList;
|
9 | private isUsingMockMatchMedia = false;
|
10 |
|
11 | mock(media: MediaMatching = defaultMatcher) {
|
12 | if (this.isUsingMockMatchMedia) {
|
13 | throw new Error(
|
14 | 'You tried to mock window.matchMedia when it was already mocked.',
|
15 | );
|
16 | }
|
17 |
|
18 | this.originalMatchMedia = window.matchMedia;
|
19 | this.isUsingMockMatchMedia = true;
|
20 |
|
21 | this.setMedia(media);
|
22 | }
|
23 |
|
24 | restore() {
|
25 | if (!this.isUsingMockMatchMedia) {
|
26 | throw new Error(
|
27 | 'You tried to restore window.matchMedia when it was already restored.',
|
28 | );
|
29 | }
|
30 |
|
31 | window.matchMedia = this.originalMatchMedia;
|
32 | this.isUsingMockMatchMedia = false;
|
33 | }
|
34 |
|
35 | isMocked() {
|
36 | return this.isUsingMockMatchMedia;
|
37 | }
|
38 |
|
39 | setMedia(media: MediaMatching = defaultMatcher) {
|
40 | this.ensureMatchMediaIsMocked();
|
41 | window.matchMedia = (query: string) => mediaQueryList(media(query));
|
42 | }
|
43 |
|
44 | private ensureMatchMediaIsMocked() {
|
45 | if (!this.isUsingMockMatchMedia) {
|
46 | throw new Error(
|
47 | 'You must call matchMedia.mock() before interacting with the mock matchMedia.',
|
48 | );
|
49 | }
|
50 | }
|
51 | }
|
52 |
|
53 | function defaultMatcher(): MediaQueryList {
|
54 | return {
|
55 | media: '',
|
56 | addListener: noop,
|
57 | addEventListener: noop,
|
58 | removeListener: noop,
|
59 | removeEventListener: noop,
|
60 | onchange: noop,
|
61 | dispatchEvent: () => false,
|
62 | matches: false,
|
63 | };
|
64 | }
|
65 |
|
66 | export function mediaQueryList(
|
67 | values: Partial<MediaQueryList>,
|
68 | ): MediaQueryList {
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | return {
|
77 | ...defaultMatcher(),
|
78 | ...values,
|
79 | };
|
80 | }
|