From 76a760fb897ec875fc3b9398a73c1405d603d6d1 Mon Sep 17 00:00:00 2001 From: Emma Simon Date: Wed, 21 Mar 2018 08:14:06 -0400 Subject: [PATCH] fix: React 16 issues (fixes #269, #200, #259) * Check if both element and props are frozen or not extensible (#200) Fixes #200 * Only check extensiblity of props Also run preventExtensions on it after modifications * Check if just the props are frozen (#266) * Recursively map element arrays Fixes #259 * Add tests For element arrays and frozen elements/props --- src/linkClass.js | 23 ++++++++++++--- tests/linkClass.js | 72 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/linkClass.js b/src/linkClass.js index 48142e1..4618446 100644 --- a/src/linkClass.js +++ b/src/linkClass.js @@ -22,17 +22,26 @@ const linkArray = (array: Array, styles: Object, configuration: Object) => { const linkElement = (element: ReactElement, styles: Object, configuration: Object): ReactElement => { let appendClassName; - let elementIsFrozen; let elementShallowCopy; elementShallowCopy = element; - if (Object.isFrozen && Object.isFrozen(elementShallowCopy)) { - elementIsFrozen = true; + if (Array.isArray(elementShallowCopy)) { + return elementShallowCopy.map((arrayElement) => { + return linkElement(arrayElement, styles, configuration); + }); + } - // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131 + const elementIsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy); + const propsFrozen = Object.isFrozen && Object.isFrozen(elementShallowCopy.props); + const propsNotExtensible = Object.isExtensible && !Object.isExtensible(elementShallowCopy.props); + + if (elementIsFrozen) { + // https://github.com/facebook/react/blob/v0.13.3/src/classic/element/ReactElement.js#L131 elementShallowCopy = objectUnfreeze(elementShallowCopy); elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props); + } else if (propsFrozen || propsNotExtensible) { + elementShallowCopy.props = objectUnfreeze(elementShallowCopy.props); } const styleNames = parseStyleName(elementShallowCopy.props.styleName || '', configuration.allowMultiple); @@ -76,6 +85,12 @@ const linkElement = (element: ReactElement, styles: Object, configuration: Objec if (elementIsFrozen) { Object.freeze(elementShallowCopy.props); Object.freeze(elementShallowCopy); + } else if (propsFrozen) { + Object.freeze(elementShallowCopy.props); + } + + if (propsNotExtensible) { + Object.preventExtensions(elementShallowCopy.props); } return elementShallowCopy; diff --git a/tests/linkClass.js b/tests/linkClass.js index fdda06b..6a13f21 100644 --- a/tests/linkClass.js +++ b/tests/linkClass.js @@ -1,4 +1,4 @@ -/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this, no-console */ +/* eslint-disable max-nested-callbacks, react/prefer-stateless-function, class-methods-use-this, no-console, no-unused-expressions */ import chai, { expect @@ -239,6 +239,76 @@ describe('linkClass', () => { }); }); + context('can\'t write to properties', () => { + context('when the element is frozen', () => { + it('adds className but is still frozen', () => { + let subject; + + subject =
; + + Object.freeze(subject); + subject = linkClass(subject, { + foo: 'foo-1' + }); + + expect(subject).to.be.frozen; + expect(subject.props.className).to.equal('foo-1'); + }); + }); + context('when the element\'s props are frozen', () => { + it('adds className and only props are still frozen', () => { + let subject; + + subject =
; + + Object.freeze(subject.props); + subject = linkClass(subject, { + foo: 'foo-1' + }); + + expect(subject.props).to.be.frozen; + expect(subject.props.className).to.equal('foo-1'); + }); + }); + context('when the element\'s props are not extensible', () => { + it('adds className and props are still not extensible', () => { + let subject; + + subject =
; + + Object.preventExtensions(subject.props); + subject = linkClass(subject, { + foo: 'foo-1' + }); + + expect(subject.props).to.not.be.extensible; + expect(subject.props.className).to.equal('foo-1'); + }); + }); + }); + + context('when element is an array', () => { + it('handles each element individually', () => { + let subject; + + subject = [ +
, +
+

+

+ ]; + + subject = linkClass(subject, { + bar: 'bar-1', + foo: 'foo-1' + }); + + expect(subject).to.be.an('array'); + expect(subject[0].props.className).to.equal('foo-1'); + expect(subject[1].props.children.props.className).to.equal('bar-1'); + }); + }); + describe('options.allowMultiple', () => { context('when multiple module names are used', () => { context('when false', () => {