diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js
index 63449b633d4c35..88cbfb8f539ba7 100644
--- a/docs/src/modules/components/AppTableOfContents.js
+++ b/docs/src/modules/components/AppTableOfContents.js
@@ -122,7 +122,7 @@ function useThrottledOnScroll(callback, delay) {
React.useEffect(() => {
if (throttledCallback === noop) {
- return () => {};
+ return undefined;
}
window.addEventListener('scroll', throttledCallback);
diff --git a/packages/material-ui/src/useMediaQuery/useMediaQuery.js b/packages/material-ui/src/useMediaQuery/useMediaQuery.js
index 09daf44e3c2425..c48c73330f665b 100644
--- a/packages/material-ui/src/useMediaQuery/useMediaQuery.js
+++ b/packages/material-ui/src/useMediaQuery/useMediaQuery.js
@@ -12,10 +12,16 @@ function useMediaQuery(queryInput, options = {}) {
let queries = multiple ? queryInput : [queryInput];
queries = queries.map(query => query.replace('@media ', ''));
+ // We are waiting for JSDOM to support the match media feature.
+ // All the targetd browsers have the feature built-in.
+ // This defensive check is here for simplicity.
+ // Most of the time, the match media logic isn't central to people tests.
+ const supportMatchMedia = typeof window.matchMedia !== 'undefined';
+
const { defaultMatches = false, noSsr = false, ssrMatchMedia = null } = options;
const [matches, setMatches] = React.useState(() => {
- if (hydrationCompleted || noSsr) {
+ if ((hydrationCompleted || noSsr) && supportMatchMedia) {
return queries.map(query => window.matchMedia(query).matches);
}
if (ssrMatchMedia) {
@@ -30,6 +36,10 @@ function useMediaQuery(queryInput, options = {}) {
React.useEffect(() => {
hydrationCompleted = true;
+ if (!supportMatchMedia) {
+ return undefined;
+ }
+
const queryLists = queries.map(query => window.matchMedia(query));
setMatches(prev => {
const next = queryLists.map(queryList => queryList.matches);
diff --git a/packages/material-ui/src/useMediaQuery/useMediaQuery.test.js b/packages/material-ui/src/useMediaQuery/useMediaQuery.test.js
index eef9e394437452..b89fdcbcf26655 100644
--- a/packages/material-ui/src/useMediaQuery/useMediaQuery.test.js
+++ b/packages/material-ui/src/useMediaQuery/useMediaQuery.test.js
@@ -48,33 +48,117 @@ describe('useMediaQuery', () => {
mount = createMount({ strict: true });
});
- beforeEach(() => {
- testReset();
- values = spy();
- matchMediaInstances = [];
- window.matchMedia = createMatchMedia(1200, matchMediaInstances);
- });
-
after(() => {
mount.cleanUp();
});
- describe('option: defaultMatches', () => {
- it('should be false by default', () => {
+ describe('without feature', () => {
+ it('should work without window.matchMedia available', () => {
+ assert.strictEqual(typeof window.matchMedia, 'undefined');
const ref = React.createRef();
const text = () => ref.current.textContent;
const Test = () => {
- const matches = useMediaQuery('(min-width:2000px)');
- React.useEffect(() => values(matches));
+ const matches = useMediaQuery('(min-width:100px)');
return {`${matches}`};
};
mount();
assert.strictEqual(text(), 'false');
- assert.strictEqual(values.callCount, 1);
});
+ });
+
+ describe('with feature', () => {
+ beforeEach(() => {
+ testReset();
+ values = spy();
+ matchMediaInstances = [];
+ window.matchMedia = createMatchMedia(1200, matchMediaInstances);
+ });
+
+ describe('option: defaultMatches', () => {
+ it('should be false by default', () => {
+ const ref = React.createRef();
+ const text = () => ref.current.textContent;
+ const Test = () => {
+ const matches = useMediaQuery('(min-width:2000px)');
+ React.useEffect(() => values(matches));
+ return {`${matches}`};
+ };
+
+ mount();
+ assert.strictEqual(text(), 'false');
+ assert.strictEqual(values.callCount, 1);
+ });
+
+ it('should take the option into account', () => {
+ const ref = React.createRef();
+ const text = () => ref.current.textContent;
+ const Test = () => {
+ const matches = useMediaQuery('(min-width:2000px)', {
+ defaultMatches: true,
+ });
+ React.useEffect(() => values(matches));
+ return {`${matches}`};
+ };
+
+ mount();
+ assert.strictEqual(text(), 'false');
+ assert.strictEqual(values.callCount, 2);
+ });
+ });
+
+ describe('option: noSsr', () => {
+ it('should render once if the default value match the expectation', () => {
+ const ref = React.createRef();
+ const text = () => ref.current.textContent;
+ const Test = () => {
+ const matches = useMediaQuery('(min-width:2000px)', {
+ defaultMatches: false,
+ });
+ React.useEffect(() => values(matches));
+ return {`${matches}`};
+ };
+
+ mount();
+ assert.strictEqual(text(), 'false');
+ assert.strictEqual(values.callCount, 1);
+ });
+
+ it('should render twice if the default value does not match the expectation', () => {
+ const ref = React.createRef();
+ const text = () => ref.current.textContent;
+ const Test = () => {
+ const matches = useMediaQuery('(min-width:2000px)', {
+ defaultMatches: true,
+ });
+ React.useEffect(() => values(matches));
+ return {`${matches}`};
+ };
+
+ mount();
+ assert.strictEqual(text(), 'false');
+ assert.strictEqual(values.callCount, 2);
+ });
- it('should take the option into account', () => {
+ it('should render once if the default value does not match the expectation', () => {
+ const ref = React.createRef();
+ const text = () => ref.current.textContent;
+ const Test = () => {
+ const matches = useMediaQuery('(min-width:2000px)', {
+ defaultMatches: true,
+ noSsr: true,
+ });
+ React.useEffect(() => values(matches));
+ return {`${matches}`};
+ };
+
+ mount();
+ assert.strictEqual(text(), 'false');
+ assert.strictEqual(values.callCount, 1);
+ });
+ });
+
+ it('should try to reconcile only the first time', () => {
const ref = React.createRef();
const text = () => ref.current.textContent;
const Test = () => {
@@ -88,125 +172,58 @@ describe('useMediaQuery', () => {
mount();
assert.strictEqual(text(), 'false');
assert.strictEqual(values.callCount, 2);
- });
- });
- describe('option: noSsr', () => {
- it('should render once if the default value match the expectation', () => {
- const ref = React.createRef();
- const text = () => ref.current.textContent;
- const Test = () => {
- const matches = useMediaQuery('(min-width:2000px)', {
- defaultMatches: false,
- });
- React.useEffect(() => values(matches));
- return {`${matches}`};
- };
+ ReactDOM.unmountComponentAtNode(mount.attachTo);
mount();
assert.strictEqual(text(), 'false');
- assert.strictEqual(values.callCount, 1);
+ assert.strictEqual(values.callCount, 3);
});
- it('should render twice if the default value does not match the expectation', () => {
+ it('should be able to change the query dynamically', () => {
const ref = React.createRef();
const text = () => ref.current.textContent;
- const Test = () => {
- const matches = useMediaQuery('(min-width:2000px)', {
+ const Test = props => {
+ const matches = useMediaQuery(props.query, {
defaultMatches: true,
});
React.useEffect(() => values(matches));
return {`${matches}`};
};
+ Test.propTypes = {
+ query: PropTypes.string.isRequired,
+ };
- mount();
+ const wrapper = mount();
assert.strictEqual(text(), 'false');
assert.strictEqual(values.callCount, 2);
+ wrapper.setProps({ query: '(min-width:100px)' });
+ assert.strictEqual(text(), 'true');
+ assert.strictEqual(values.callCount, 4);
});
- it('should render once if the default value does not match the expectation', () => {
+ it('should observe the media query', () => {
const ref = React.createRef();
const text = () => ref.current.textContent;
- const Test = () => {
- const matches = useMediaQuery('(min-width:2000px)', {
- defaultMatches: true,
- noSsr: true,
- });
+ const Test = props => {
+ const matches = useMediaQuery(props.query);
React.useEffect(() => values(matches));
return {`${matches}`};
};
+ Test.propTypes = {
+ query: PropTypes.string.isRequired,
+ };
- mount();
- assert.strictEqual(text(), 'false');
+ mount();
assert.strictEqual(values.callCount, 1);
- });
- });
-
- it('should try to reconcile only the first time', () => {
- const ref = React.createRef();
- const text = () => ref.current.textContent;
- const Test = () => {
- const matches = useMediaQuery('(min-width:2000px)', {
- defaultMatches: true,
- });
- React.useEffect(() => values(matches));
- return {`${matches}`};
- };
-
- mount();
- assert.strictEqual(text(), 'false');
- assert.strictEqual(values.callCount, 2);
-
- ReactDOM.unmountComponentAtNode(mount.attachTo);
-
- mount();
- assert.strictEqual(text(), 'false');
- assert.strictEqual(values.callCount, 3);
- });
+ assert.strictEqual(text(), 'false');
- it('should be able to change the query dynamically', () => {
- const ref = React.createRef();
- const text = () => ref.current.textContent;
- const Test = props => {
- const matches = useMediaQuery(props.query, {
- defaultMatches: true,
+ act(() => {
+ matchMediaInstances[0].instance.matches = true;
+ matchMediaInstances[0].listeners[0]();
});
- React.useEffect(() => values(matches));
- return {`${matches}`};
- };
- Test.propTypes = {
- query: PropTypes.string.isRequired,
- };
-
- const wrapper = mount();
- assert.strictEqual(text(), 'false');
- assert.strictEqual(values.callCount, 2);
- wrapper.setProps({ query: '(min-width:100px)' });
- assert.strictEqual(text(), 'true');
- assert.strictEqual(values.callCount, 4);
- });
-
- it('should observe the media query', () => {
- const ref = React.createRef();
- const text = () => ref.current.textContent;
- const Test = props => {
- const matches = useMediaQuery(props.query);
- React.useEffect(() => values(matches));
- return {`${matches}`};
- };
- Test.propTypes = {
- query: PropTypes.string.isRequired,
- };
-
- mount();
- assert.strictEqual(values.callCount, 1);
- assert.strictEqual(text(), 'false');
-
- act(() => {
- matchMediaInstances[0].instance.matches = true;
- matchMediaInstances[0].listeners[0]();
+ assert.strictEqual(text(), 'true');
+ assert.strictEqual(values.callCount, 2);
});
- assert.strictEqual(text(), 'true');
- assert.strictEqual(values.callCount, 2);
});
});