Add a test for bad Map polyfill, and work around Rollup bug (#11745)
* Add a test for bad Map polyfill * Add a workaround for the Rollup bug * Add a link to the bug URL
Dan Abramov committed
Dec 2, 2017 at 00:00 UTC
31ea0aa6d701df811fb586fa7545a376e5135440
2 files changed
+80
-4
packages/react-reconciler/src/ReactFiber.js
+7
-4
@@ -41,10 +41,13 @@ if (__DEV__) {
41
var hasBadMapPolyfill = false;
42
try {
43
var nonExtensibleObject = Object.preventExtensions({});
44
- /* eslint-disable no-new */
45
- new Map([[nonExtensibleObject, null]]);
46
- new Set([nonExtensibleObject]);
47
- /* eslint-enable no-new */
44
+ var testMap = new Map([[nonExtensibleObject, null]]);
45
+ var testSet = new Set([nonExtensibleObject]);
46
+ // This is necessary for Rollup to not consider these unused.
47
+ // https://github.com/rollup/rollup/issues/1771
48
+ // TODO: we can remove these if Rollup fixes the bug.
49
+ testMap.set(0, 0);
50
+ testSet.add(0);
51
} catch (e) {
52
// TODO: Consider warning about bad polyfills
53
hasBadMapPolyfill = true;
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+73
@@ -2715,4 +2715,77 @@ describe('ReactIncremental', () => {
2715
2716
expect(ReactNoop.flush()).toEqual([]);
2717
});
2718
+
2719
+ it('does not break with a bad Map polyfill', () => {
2720
+ const realMapSet = Map.prototype.set;
2721
+
2722
+ function triggerCodePathThatUsesFibersAsMapKeys() {
2723
+ function Thing() {
2724
+ throw new Error('No.');
2725
+ }
2726
+ class Boundary extends React.Component {
2727
+ state = {didError: false};
2728
+ componentDidCatch() {
2729
+ this.setState({didError: true});
2730
+ }
2731
+ render() {
2732
+ return this.state.didError ? null : <Thing />;
2733
+ }
2734
+ }
2735
+ ReactNoop.render(<Boundary />);
2736
+ ReactNoop.flush();
2737
+ }
2738
+
2739
+ // First, verify that this code path normally receives Fibers as keys,
2740
+ // and that they're not extensible.
2741
+ jest.resetModules();
2742
+ let receivedNonExtensibleObjects;
2743
+ // eslint-disable-next-line no-extend-native
2744
+ Map.prototype.set = function(key) {
2745
+ if (typeof key === 'object' && key !== null) {
2746
+ if (!Object.isExtensible(key)) {
2747
+ receivedNonExtensibleObjects = true;
2748
+ }
2749
+ }
2750
+ return realMapSet.apply(this, arguments);
2751
+ };
2752
+ React = require('react');
2753
+ ReactNoop = require('react-noop-renderer');
2754
+ try {
2755
+ receivedNonExtensibleObjects = false;
2756
+ triggerCodePathThatUsesFibersAsMapKeys();
2757
+ } finally {
2758
+ // eslint-disable-next-line no-extend-native
2759
+ Map.prototype.set = realMapSet;
2760
+ }
2761
+ // If this fails, find another code path in Fiber
2762
+ // that passes Fibers as keys to Maps.
2763
+ // Note that we only expect them to be non-extensible
2764
+ // in development.
2765
+ expect(receivedNonExtensibleObjects).toBe(__DEV__);
2766
+
2767
+ // Next, verify that a Map polyfill that "writes" to keys
2768
+ // doesn't cause a failure.
2769
+ jest.resetModules();
2770
+ // eslint-disable-next-line no-extend-native
2771
+ Map.prototype.set = function(key, value) {
2772
+ if (typeof key === 'object' && key !== null) {
2773
+ // A polyfill could do something like this.
2774
+ // It would throw if an object is not extensible.
2775
+ key.__internalValueSlot = value;
2776
+ }
2777
+ return realMapSet.apply(this, arguments);
2778
+ };
2779
+ React = require('react');
2780
+ ReactNoop = require('react-noop-renderer');
2781
+ try {
2782
+ triggerCodePathThatUsesFibersAsMapKeys();
2783
+ } finally {
2784
+ // eslint-disable-next-line no-extend-native
2785
+ Map.prototype.set = realMapSet;
2786
+ }
2787
+ // If we got this far, our feature detection worked.
2788
+ // We knew that Map#set() throws for non-extensible objects,
2789
+ // so we didn't set them as non-extensible for that reason.
2790
+ });
2791
});