From d87f71ebc11d4ac986ec39a039ec4f7c96916e7d Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Sat, 31 Dec 2016 17:11:22 -0800 Subject: [PATCH] [change] StyleSheet performance rewrite Improves StyleSheet benchmark performance by 13x. Removes undocumented `StyleSheet.resolve` API. Typical mean (and first) task duration. [benchmark] DeepTree: depth=3, breadth=10, wrap=4) -master 2809.76ms (3117.64ms) -patch 211.2ms (364.28ms) [benchmark] DeepTree: depth=5, breadth=3, wrap=1) -master 421.25ms (428.15ms) -patch 32.46ms (47.36ms) This patch adds memoization of DOM prop resolution (~3-4x faster), and re-introduces a `className`-based styling strategy (~3-4x faster). Styles map to "atomic css" rules. Fix #307 --- docs/apis/StyleSheet.md | 4 +- examples/.storybook/webpack.config.js | 5 +- package.json | 1 + .../renderApplication-test.js.snap | 45 + .../__tests__/renderApplication-test.js | 2 +- src/apis/AppRegistry/renderApplication.js | 2 +- .../createReactDOMStyle-test.js.snap | 15 + .../__snapshots__/generateCss-test.js.snap | 1 + .../__snapshots__/index-test.js.snap | 22 +- .../__snapshots__/registry-test.js.snap | 179 +++ .../__tests__/createReactDOMStyle-test.js | 22 +- .../StyleSheet/__tests__/generateCss-test.js | 16 + src/apis/StyleSheet/__tests__/index-test.js | 32 +- src/apis/StyleSheet/__tests__/injector.js | 22 + .../__tests__/prefixeInlineStyles-test.js | 13 + .../StyleSheet/__tests__/registry-test.js | 54 + .../__tests__/resolveVendorPrefixes-test.js | 13 - src/apis/StyleSheet/createReactDOMStyle.js | 6 +- src/apis/StyleSheet/css.js | 42 - src/apis/StyleSheet/flattenStyle.js | 25 +- src/apis/StyleSheet/generateCss.js | 38 + src/apis/StyleSheet/hyphenate.js | 3 + src/apis/StyleSheet/index.js | 81 +- src/apis/StyleSheet/initialize.js | 39 + src/apis/StyleSheet/injector.js | 91 ++ ...endorPrefixes.js => prefixInlineStyles.js} | 4 +- src/apis/StyleSheet/registry.js | 178 +++ src/apis/StyleSheet/resolveBoxShadow.js | 17 +- src/apis/StyleSheet/resolveTransform.js | 2 +- src/apis/UIManager/index.js | 13 +- .../__snapshots__/index-test.js.snap | 1378 +++++++---------- src/components/Image/index.js | 4 +- .../__snapshots__/index-test.js.snap | 1254 +++++++-------- .../__snapshots__/index-test.js.snap | 147 +- src/components/Text/index.js | 1 + .../__snapshots__/index-test.js.snap | 786 ++++------ .../__snapshots__/index-test.js.snap | 48 +- src/modules/createDOMElement/index.js | 10 +- src/modules/flattenArray/index.js | 18 + src/modules/mapKeyValue/index.js | 13 + yarn.lock | 2 +- 41 files changed, 2358 insertions(+), 2290 deletions(-) create mode 100644 src/apis/AppRegistry/__tests__/__snapshots__/renderApplication-test.js.snap create mode 100644 src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap create mode 100644 src/apis/StyleSheet/__tests__/__snapshots__/generateCss-test.js.snap create mode 100644 src/apis/StyleSheet/__tests__/__snapshots__/registry-test.js.snap create mode 100644 src/apis/StyleSheet/__tests__/generateCss-test.js create mode 100644 src/apis/StyleSheet/__tests__/injector.js create mode 100644 src/apis/StyleSheet/__tests__/prefixeInlineStyles-test.js create mode 100644 src/apis/StyleSheet/__tests__/registry-test.js delete mode 100644 src/apis/StyleSheet/__tests__/resolveVendorPrefixes-test.js delete mode 100644 src/apis/StyleSheet/css.js create mode 100644 src/apis/StyleSheet/generateCss.js create mode 100644 src/apis/StyleSheet/hyphenate.js create mode 100644 src/apis/StyleSheet/initialize.js create mode 100644 src/apis/StyleSheet/injector.js rename src/apis/StyleSheet/{resolveVendorPrefixes.js => prefixInlineStyles.js} (84%) create mode 100644 src/apis/StyleSheet/registry.js create mode 100644 src/modules/flattenArray/index.js create mode 100644 src/modules/mapKeyValue/index.js diff --git a/docs/apis/StyleSheet.md b/docs/apis/StyleSheet.md index 1a0c09a5f..ec441c556 100644 --- a/docs/apis/StyleSheet.md +++ b/docs/apis/StyleSheet.md @@ -15,9 +15,9 @@ Each key of the object passed to `create` must define a style object. Flattens an array of styles into a single style object. -**render**: function +**renderToString**: function -Returns a React `" +`; diff --git a/src/apis/AppRegistry/__tests__/renderApplication-test.js b/src/apis/AppRegistry/__tests__/renderApplication-test.js index c307bf374..2a5f49abf 100644 --- a/src/apis/AppRegistry/__tests__/renderApplication-test.js +++ b/src/apis/AppRegistry/__tests__/renderApplication-test.js @@ -10,6 +10,6 @@ describe('apis/AppRegistry/renderApplication', () => { const { element, stylesheet } = getApplication(component, {}); expect(element).toBeTruthy(); - expect(stylesheet.type).toEqual('style'); + expect(stylesheet).toMatchSnapshot(); }); }); diff --git a/src/apis/AppRegistry/renderApplication.js b/src/apis/AppRegistry/renderApplication.js index 45cee16e3..47e9361c8 100644 --- a/src/apis/AppRegistry/renderApplication.js +++ b/src/apis/AppRegistry/renderApplication.js @@ -32,6 +32,6 @@ export function getApplication(RootComponent: Component, initialProps: Object): rootComponent={RootComponent} /> ); - const stylesheet = StyleSheet.render(); + const stylesheet = StyleSheet.renderToString(); return { element, stylesheet }; } diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap new file mode 100644 index 000000000..9f8fc2312 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/__snapshots__/createReactDOMStyle-test.js.snap @@ -0,0 +1,15 @@ +exports[`apis/StyleSheet/createReactDOMStyle converts ReactNative style to ReactDOM style 1`] = ` +Object { + "borderBottomWidth": "1px", + "borderLeftWidth": "1px", + "borderRightWidth": "1px", + "borderTopWidth": "1px", + "borderWidthLeft": "2px", + "borderWidthRight": "3px", + "boxShadow": "1px 1px 1px 1px #000, 1px 2px 0px rgba(255,0,0,1)", + "display": "flex", + "marginBottom": "0px", + "marginTop": "0px", + "opacity": 0, +} +`; diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/generateCss-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/generateCss-test.js.snap new file mode 100644 index 000000000..958b394e9 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/__snapshots__/generateCss-test.js.snap @@ -0,0 +1 @@ +exports[`apis/StyleSheet/generateCss generates correct css 1`] = `"-webkit-transition-duration:0.1s;transition-duration:0.1s;position:absolute;border-width-right:3px;border-width-left:2px;box-shadow:1px 1px 1px 1px #000;"`; diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/index-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/index-test.js.snap index 35bd0b8a9..95a485055 100644 --- a/src/apis/StyleSheet/__tests__/__snapshots__/index-test.js.snap +++ b/src/apis/StyleSheet/__tests__/__snapshots__/index-test.js.snap @@ -1,10 +1,14 @@ -exports[`apis/StyleSheet resolve 1`] = ` -Object { - "className": "test __style_df __style_pebn", - "style": Object { - "display": null, - "opacity": 1, - "pointerEvents": null, - }, -} +exports[`apis/StyleSheet renderToString 1`] = ` +"" `; diff --git a/src/apis/StyleSheet/__tests__/__snapshots__/registry-test.js.snap b/src/apis/StyleSheet/__tests__/__snapshots__/registry-test.js.snap new file mode 100644 index 000000000..db8ffeff3 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/__snapshots__/registry-test.js.snap @@ -0,0 +1,179 @@ +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 1`] = ` +Object { + "className": " +rn-borderTopColor:red +rn-borderRightColor:red +rn-borderBottomColor:red +rn-borderLeftColor:red +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute +rn-width:100px", + "style": Object {}, +} +`; + +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 2`] = ` +Object { + "className": " +rn-borderTopColor:red +rn-borderRightColor:red +rn-borderBottomColor:red +rn-borderLeftColor:red +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute +rn-width:200px", + "style": Object {}, +} +`; + +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to className 3`] = ` +Object { + "className": " +rn-borderTopColor:red +rn-borderRightColor:red +rn-borderBottomColor:red +rn-borderLeftColor:red +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute +rn-width:100px", + "style": Object {}, +} +`; + +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 1`] = ` +Object { + "className": " +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + "width": "100px", + }, +} +`; + +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 2`] = ` +Object { + "className": " +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute +rn-width:200px", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + }, +} +`; + +exports[`apis/StyleSheet/registry resolve with stylesheet, resolves to mixed 3`] = ` +Object { + "className": " +rn-left:50px +rn-pointerEvents:box-only +rn-position:absolute", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + "width": "100px", + }, +} +`; + +exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 1`] = ` +Object { + "className": " +", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + "left": "50px", + "pointerEvents": "box-only", + "position": "absolute", + "width": "100px", + }, +} +`; + +exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 2`] = ` +Object { + "className": " +", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + "left": "50px", + "pointerEvents": "box-only", + "position": "absolute", + "width": "200px", + }, +} +`; + +exports[`apis/StyleSheet/registry resolve without stylesheet, resolves to inline styles 3`] = ` +Object { + "className": " +", + "style": Object { + "borderBottomColor": "red", + "borderBottomWidth": "0px", + "borderLeftColor": "red", + "borderLeftWidth": "0px", + "borderRightColor": "red", + "borderRightWidth": "0px", + "borderTopColor": "red", + "borderTopWidth": "0px", + "left": "50px", + "pointerEvents": "box-only", + "position": "absolute", + "width": "100px", + }, +} +`; diff --git a/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js b/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js index 55fd22572..7955be070 100644 --- a/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js +++ b/src/apis/StyleSheet/__tests__/createReactDOMStyle-test.js @@ -2,11 +2,27 @@ import createReactDOMStyle from '../createReactDOMStyle'; +const reactNativeStyle = { + boxShadow: '1px 1px 1px 1px #000', + borderWidthLeft: 2, + borderWidth: 1, + borderWidthRight: 3, + display: 'flex', + marginVertical: 0, + opacity: 0, + shadowColor: 'red', + shadowOffset: { width: 1, height: 2 }, + resizeMode: 'contain' +}; + describe('apis/StyleSheet/createReactDOMStyle', () => { test('converts ReactNative style to ReactDOM style', () => { - const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 }; - const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 }; + expect(createReactDOMStyle(reactNativeStyle)).toMatchSnapshot(); + }); - expect(createReactDOMStyle(reactNativeStyle)).toEqual(expectedStyle); + test('noop on DOM styles', () => { + const firstStyle = createReactDOMStyle(reactNativeStyle); + const secondStyle = createReactDOMStyle(firstStyle); + expect(firstStyle).toEqual(secondStyle); }); }); diff --git a/src/apis/StyleSheet/__tests__/generateCss-test.js b/src/apis/StyleSheet/__tests__/generateCss-test.js new file mode 100644 index 000000000..5be9ef4d0 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/generateCss-test.js @@ -0,0 +1,16 @@ +/* eslint-env jasmine, jest */ + +import generateCss from '../generateCss'; + +describe('apis/StyleSheet/generateCss', () => { + test('generates correct css', () => { + const style = { + boxShadow: '1px 1px 1px 1px #000', + borderWidthLeft: 2, + borderWidthRight: 3, + position: 'absolute', + transitionDuration: '0.1s' + }; + expect(generateCss(style)).toMatchSnapshot(); + }); +}); diff --git a/src/apis/StyleSheet/__tests__/index-test.js b/src/apis/StyleSheet/__tests__/index-test.js index b1cff0bd1..e8166c398 100644 --- a/src/apis/StyleSheet/__tests__/index-test.js +++ b/src/apis/StyleSheet/__tests__/index-test.js @@ -1,7 +1,7 @@ /* eslint-env jasmine, jest */ -import { getDefaultStyleSheet } from '../css'; import StyleSheet from '..'; +import StyleRegistry from '../registry'; const isPlainObject = (x) => { const toString = Object.prototype.toString; @@ -16,7 +16,7 @@ const isPlainObject = (x) => { describe('apis/StyleSheet', () => { beforeEach(() => { - StyleSheet._reset(); + StyleRegistry.reset(); }); test('absoluteFill', () => { @@ -32,11 +32,6 @@ describe('apis/StyleSheet', () => { const style = StyleSheet.create({ root: { opacity: 1 } }); expect(Number.isInteger(style.root) === true).toBeTruthy(); }); - - test('renders a style sheet in the browser', () => { - StyleSheet.create({ root: { color: 'red' } }); - expect(document.getElementById('react-native-style__').textContent).toEqual(getDefaultStyleSheet()); - }); }); test('flatten', () => { @@ -47,18 +42,17 @@ describe('apis/StyleSheet', () => { expect(Number.isInteger(StyleSheet.hairlineWidth) === true).toBeTruthy(); }); - test('render', () => { - expect(StyleSheet.render().props.dangerouslySetInnerHTML.__html).toEqual(getDefaultStyleSheet()); - }); - - test('resolve', () => { - expect(StyleSheet.resolve({ - className: 'test', - style: { - display: 'flex', - opacity: 1, - pointerEvents: 'box-none' + test('renderToString', () => { + StyleSheet.create({ + a: { + borderWidth: 0, + borderColor: 'red' + }, + b: { + position: 'absolute', + left: 50 } - })).toMatchSnapshot(); + }); + expect(StyleSheet.renderToString()).toMatchSnapshot(); }); }); diff --git a/src/apis/StyleSheet/__tests__/injector.js b/src/apis/StyleSheet/__tests__/injector.js new file mode 100644 index 000000000..72cdbe019 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/injector.js @@ -0,0 +1,22 @@ +/* eslint-env jasmine, jest */ + +import injector from '../injector'; + +describe('apis/StyleSheet', () => { + beforeEach(() => { + document.head.insertAdjacentHTML('afterbegin', ` + + `); + }); + + test('hydrates from SSR', () => { + const classList = injector.getAvailableClassNames(); + expect(classList).toEqual([ + 'rn-alignItems\\:stretch', + 'rn-position\\:top' + ]); + }); +}); diff --git a/src/apis/StyleSheet/__tests__/prefixeInlineStyles-test.js b/src/apis/StyleSheet/__tests__/prefixeInlineStyles-test.js new file mode 100644 index 000000000..48a1e31e7 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/prefixeInlineStyles-test.js @@ -0,0 +1,13 @@ +/* eslint-env jasmine, jest */ + +import prefixInlineStyles from '../prefixInlineStyles'; + +describe('apis/StyleSheet/prefixInlineStyles', () => { + test('handles array values', () => { + const style = { + display: [ '-webkit-flex', 'flex' ] + }; + + expect(prefixInlineStyles(style)).toEqual({ display: 'flex' }); + }); +}); diff --git a/src/apis/StyleSheet/__tests__/registry-test.js b/src/apis/StyleSheet/__tests__/registry-test.js new file mode 100644 index 000000000..e89f29f6b --- /dev/null +++ b/src/apis/StyleSheet/__tests__/registry-test.js @@ -0,0 +1,54 @@ +/* eslint-env jasmine, jest */ + +import StyleRegistry from '../registry'; + +describe('apis/StyleSheet/registry', () => { + beforeEach(() => { + StyleRegistry.reset(); + }); + + test('register', () => { + const style = { opacity: 0 }; + const id = StyleRegistry.register(style); + expect(typeof id === 'number').toBe(true); + }); + + describe('resolve', () => { + const styleA = { borderWidth: 0, borderColor: 'red', width: 100 }; + const styleB = { position: 'absolute', left: 50, pointerEvents: 'box-only' }; + const styleC = { width: 200 }; + + const testResolve = (a, b, c) => { + // no common properties, different resolving order, same result + const resolve1 = StyleRegistry.resolve([ a, b ]); + expect(resolve1).toMatchSnapshot(); + const resolve2 = StyleRegistry.resolve([ b, a ]); + expect(resolve1).toEqual(resolve2); + + // common properties, different resolving order, different result + const resolve3 = StyleRegistry.resolve([ a, b, c ]); + expect(resolve3).toMatchSnapshot(); + const resolve4 = StyleRegistry.resolve([ c, a, b ]); + expect(resolve4).toMatchSnapshot(); + expect(resolve3).not.toEqual(resolve4); + }; + + test('with stylesheet, resolves to className', () => { + const a = StyleRegistry.register(styleA); + const b = StyleRegistry.register(styleB); + const c = StyleRegistry.register(styleC); + testResolve(a, b, c); + }); + + test('with stylesheet, resolves to mixed', () => { + const a = styleA; + const b = StyleRegistry.register(styleB); + const c = StyleRegistry.register(styleC); + testResolve(a, b, c); + }); + + test('without stylesheet, resolves to inline styles', () => { + testResolve(styleA, styleB, styleC); + }); + }); +}); diff --git a/src/apis/StyleSheet/__tests__/resolveVendorPrefixes-test.js b/src/apis/StyleSheet/__tests__/resolveVendorPrefixes-test.js deleted file mode 100644 index 077e63dd2..000000000 --- a/src/apis/StyleSheet/__tests__/resolveVendorPrefixes-test.js +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-env jasmine, jest */ - -import resolveVendorPrefixes from '../resolveVendorPrefixes'; - -describe('apis/StyleSheet/resolveVendorPrefixes', () => { - test('handles array values', () => { - const style = { - display: [ '-webkit-flex', 'flex' ] - }; - - expect(resolveVendorPrefixes(style)).toEqual({ display: 'flex' }); - }); -}); diff --git a/src/apis/StyleSheet/createReactDOMStyle.js b/src/apis/StyleSheet/createReactDOMStyle.js index a8bb47470..e173e6750 100644 --- a/src/apis/StyleSheet/createReactDOMStyle.js +++ b/src/apis/StyleSheet/createReactDOMStyle.js @@ -1,10 +1,6 @@ import expandStyle from './expandStyle'; -import flattenStyle from './flattenStyle'; import i18nStyle from './i18nStyle'; -import resolveVendorPrefixes from './resolveVendorPrefixes'; -const createReactDOMStyle = (reactNativeStyle) => resolveVendorPrefixes( - expandStyle(i18nStyle(flattenStyle(reactNativeStyle))) -); +const createReactDOMStyle = (flattenedReactNativeStyle) => expandStyle(i18nStyle(flattenedReactNativeStyle)); module.exports = createReactDOMStyle; diff --git a/src/apis/StyleSheet/css.js b/src/apis/StyleSheet/css.js deleted file mode 100644 index 326913a7b..000000000 --- a/src/apis/StyleSheet/css.js +++ /dev/null @@ -1,42 +0,0 @@ -const DISPLAY_FLEX_CLASSNAME = '__style_df'; -const POINTER_EVENTS_AUTO_CLASSNAME = '__style_pea'; -const POINTER_EVENTS_BOX_NONE_CLASSNAME = '__style_pebn'; -const POINTER_EVENTS_BOX_ONLY_CLASSNAME = '__style_pebo'; -const POINTER_EVENTS_NONE_CLASSNAME = '__style_pen'; - -/* eslint-disable max-len */ -const CSS_RESET = -// reset unwanted styles -'/* React Native */\n' + -'html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}\n' + -'body{margin:0}\n' + -'button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}\n' + -'input::-webkit-inner-spin-button,input::-webkit-outer-spin-button,' + -'input::-webkit-search-cancel-button,input::-webkit-search-decoration,' + -'input::-webkit-search-results-button,input::-webkit-search-results-decoration{display:none}'; - -const CSS_HELPERS = -// vendor prefix 'display:flex' until React supports fallback values for inline styles -`.${DISPLAY_FLEX_CLASSNAME} {display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}\n` + -// implement React Native's pointer event values -`.${POINTER_EVENTS_AUTO_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME}, .${POINTER_EVENTS_BOX_NONE_CLASSNAME} * {pointer-events:auto}\n` + -`.${POINTER_EVENTS_NONE_CLASSNAME}, .${POINTER_EVENTS_BOX_ONLY_CLASSNAME} *, .${POINTER_EVENTS_NONE_CLASSNAME} {pointer-events:none}`; -/* eslint-enable max-len */ - -const styleAsClassName = { - display: { - 'flex': DISPLAY_FLEX_CLASSNAME - }, - pointerEvents: { - 'auto': POINTER_EVENTS_AUTO_CLASSNAME, - 'box-none': POINTER_EVENTS_BOX_NONE_CLASSNAME, - 'box-only': POINTER_EVENTS_BOX_ONLY_CLASSNAME, - 'none': POINTER_EVENTS_NONE_CLASSNAME - } -}; - -export const getDefaultStyleSheet = () => `${CSS_RESET}\n${CSS_HELPERS}`; - -export const getStyleAsHelperClassName = (prop, value) => { - return styleAsClassName[prop] && styleAsClassName[prop][value]; -}; diff --git a/src/apis/StyleSheet/flattenStyle.js b/src/apis/StyleSheet/flattenStyle.js index f9e2d64e9..ae914b446 100644 --- a/src/apis/StyleSheet/flattenStyle.js +++ b/src/apis/StyleSheet/flattenStyle.js @@ -1,4 +1,3 @@ -/* eslint-disable */ /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -10,10 +9,8 @@ * @providesModule flattenStyle * @flow */ -'use strict'; - -var ReactNativePropRegistry = require('../../modules/ReactNativePropRegistry'); -var invariant = require('fbjs/lib/invariant'); +import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; +import invariant from 'fbjs/lib/invariant'; function getStyle(style) { if (typeof style === 'number') { @@ -22,22 +19,26 @@ function getStyle(style) { return style; } -function flattenStyle(style: ?StyleObj): ?Object { +function flattenStyle(style) { if (!style) { return undefined; } - invariant(style !== true, 'style may be false but not true'); + + if (process.env.NODE !== 'production') { + invariant(style !== true, 'style may be false but not true'); + } if (!Array.isArray(style)) { return getStyle(style); } - var result = {}; - for (var i = 0, styleLength = style.length; i < styleLength; ++i) { - var computedStyle = flattenStyle(style[i]); + const result = {}; + for (let i = 0, styleLength = style.length; i < styleLength; ++i) { + const computedStyle = flattenStyle(style[i]); if (computedStyle) { - for (var key in computedStyle) { - result[key] = computedStyle[key]; + for (const key in computedStyle) { + const value = computedStyle[key]; + result[key] = value; } } } diff --git a/src/apis/StyleSheet/generateCss.js b/src/apis/StyleSheet/generateCss.js new file mode 100644 index 000000000..433f8fab6 --- /dev/null +++ b/src/apis/StyleSheet/generateCss.js @@ -0,0 +1,38 @@ +import hyphenate from './hyphenate'; +import mapKeyValue from '../../modules/mapKeyValue'; +import normalizeValue from './normalizeValue'; +import prefixAll from 'inline-style-prefixer/static'; + +const RE_VENDOR = /^-/; + +const sortVendorPrefixes = (a, b) => { + const vendorA = RE_VENDOR.test(a); + const vendorB = RE_VENDOR.test(b); + if (vendorA && vendorB || vendorA) { + return -1; + } else { + return 1; + } +}; + +const mapDeclaration = (prop, val) => { + const name = hyphenate(prop); + const value = normalizeValue(prop, val); + if (Array.isArray(val)) { + return val.map((v) => `${name}:${v};`).join(''); + } + return `${name}:${value};`; +}; + +/** + * Generates valid CSS rule body from a JS object + * + * generateCss({ width: 20, color: 'blue' }); + * // => 'width:20px;color:blue;' + */ +const generateCss = (style) => { + const prefixed = prefixAll(style); + return mapKeyValue(prefixed, mapDeclaration).sort(sortVendorPrefixes).join(''); +}; + +module.exports = generateCss; diff --git a/src/apis/StyleSheet/hyphenate.js b/src/apis/StyleSheet/hyphenate.js new file mode 100644 index 000000000..71fca3d41 --- /dev/null +++ b/src/apis/StyleSheet/hyphenate.js @@ -0,0 +1,3 @@ +const RE_1 = /([A-Z])/g; +const RE_2 = /^ms-/; +module.exports = (s) => s.replace(RE_1, '-$1').toLowerCase().replace(RE_2, '-ms-'); diff --git a/src/apis/StyleSheet/index.js b/src/apis/StyleSheet/index.js index 7269936ef..3e90789bb 100644 --- a/src/apis/StyleSheet/index.js +++ b/src/apis/StyleSheet/index.js @@ -1,89 +1,26 @@ -import * as css from './css'; -import createReactDOMStyle from './createReactDOMStyle'; -import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment'; import flattenStyle from './flattenStyle'; -import React from 'react'; -import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; +import initialize from './initialize'; +import injector from './injector'; +import StyleRegistry from './registry'; -let styleElement; -let shouldInsertStyleSheet = ExecutionEnvironment.canUseDOM; - -const STYLE_SHEET_ID = 'react-native-style__'; +initialize(); const absoluteFillObject = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }; -const defaultStyleSheet = css.getDefaultStyleSheet(); - -const insertStyleSheet = () => { - // check if the server rendered the style sheet - styleElement = document.getElementById(STYLE_SHEET_ID); - // if not, inject the style sheet - if (!styleElement) { - document.head.insertAdjacentHTML( - 'afterbegin', - `` - ); - shouldInsertStyleSheet = false; - } -}; - module.exports = { - /** - * For testing - * @private - */ - _reset() { - if (styleElement) { - document.head.removeChild(styleElement); - styleElement = null; - shouldInsertStyleSheet = true; - } - }, - - absoluteFill: ReactNativePropRegistry.register(absoluteFillObject), - + absoluteFill: StyleRegistry.register(absoluteFillObject), absoluteFillObject, - create(styles) { - if (shouldInsertStyleSheet) { - insertStyleSheet(); - } - const result = {}; - for (const key in styles) { + Object.keys(styles).forEach((key) => { if (process.env.NODE_ENV !== 'production') { require('./StyleSheetValidation').validateStyle(key, styles); } - result[key] = ReactNativePropRegistry.register(styles[key]); - } + result[key] = StyleRegistry.register(styles[key]); + }); return result; }, - hairlineWidth: 1, - flatten: flattenStyle, - - /* @platform web */ - render() { - return `; + +const frame = () => { + if (!isDirty || !ExecutionEnvironment.canUseDOM) { return; } + + isDirty = false; + styleNode = styleNode || document.getElementById(STYLE_ELEMENT_ID); + + if (!styleNode) { + document.head.insertAdjacentHTML('afterbegin', createStyleHTML()); + styleNode = document.getElementById(STYLE_ELEMENT_ID); + } + + const css = getStyleText(); + + if (styleNode.styleSheet) { + styleNode.styleSheet.cssText = css; + } else { + /* eslint no-cond-assign:0 */ + let last; + while (last = styleNode.lastChild) { + styleNode.removeChild(last); + } + styleNode.appendChild(document.createTextNode(css)); + } +}; + +const addRule = (key, rule) => { + if (!registry[key]) { + registry[key] = rule; + if (!isDirty) { + isDirty = true; + if (ExecutionEnvironment.canUseDOM) { + asap(frame); + } + } + } +}; + +const getStyleSheetHtml = () => createStyleHTML(getStyleText()); + +module.exports = { + addRule, + getAvailableClassNames, + getStyleSheetHtml, + reset: () => { registry = {}; } +}; diff --git a/src/apis/StyleSheet/resolveVendorPrefixes.js b/src/apis/StyleSheet/prefixInlineStyles.js similarity index 84% rename from src/apis/StyleSheet/resolveVendorPrefixes.js rename to src/apis/StyleSheet/prefixInlineStyles.js index dfdddbde6..5f24b9c2a 100644 --- a/src/apis/StyleSheet/resolveVendorPrefixes.js +++ b/src/apis/StyleSheet/prefixInlineStyles.js @@ -1,6 +1,6 @@ import prefixAll from 'inline-style-prefixer/static'; -const resolveVendorPrefixes = (style) => { +const prefixInlineStyles = (style) => { const prefixedStyles = prefixAll(style); // React@15 removed undocumented support for fallback values in @@ -15,4 +15,4 @@ const resolveVendorPrefixes = (style) => { return prefixedStyles; }; -module.exports = resolveVendorPrefixes; +module.exports = prefixInlineStyles; diff --git a/src/apis/StyleSheet/registry.js b/src/apis/StyleSheet/registry.js new file mode 100644 index 000000000..ed0ba7d0b --- /dev/null +++ b/src/apis/StyleSheet/registry.js @@ -0,0 +1,178 @@ +/** + * WARNING: changes to this file in particular can cause significant changes to + * the results of render performance benchmarks. + */ + +import createReactDOMStyle from './createReactDOMStyle'; +import flattenArray from '../../modules/flattenArray'; +import flattenStyle from './flattenStyle'; +import generateCss from './generateCss'; +import injector from './injector'; +import mapKeyValue from '../../modules/mapKeyValue'; +import prefixInlineStyles from './prefixInlineStyles'; +import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; + +const prefix = 'r'; +const SPACE_REGEXP = /\s/g; +const ESCAPE_SELECTOR_CHARS_REGEXP = /[(),":?.%\\$#]/g; + +/** + * Creates an HTML class name for use on elements + */ +const createClassName = (prop, value) => { + const val = `${value}`.replace(SPACE_REGEXP, '-'); + return `rn-${prop}:${val}`; +}; + +/** + * Inject a CSS rule for a given declaration and record the availability of the + * resulting class name. + */ +let injectedClassNames = {}; +const injectClassNameIfNeeded = (prop, value) => { + const className = createClassName(prop, value); + if (!injectedClassNames[className]) { + // create a valid CSS selector for a given HTML class name + const selector = className.replace(ESCAPE_SELECTOR_CHARS_REGEXP, '\\$&'); + const body = generateCss({ [prop]: value }); + const css = `.${selector}{${body}}`; + // this adds the rule to the buffer to be injected into the document + injector.addRule(className, css); + injectedClassNames[className] = true; + } + + return className; +}; + +/** + * Converts a React Native style object to HTML class names + */ +let resolvedPropsCache = {}; +const registerStyle = (id, flatStyle) => { + const style = createReactDOMStyle(flatStyle); + const className = mapKeyValue(style, (prop, value) => { + if (value != null) { + return injectClassNameIfNeeded(prop, value); + } + }).join(' ').trim(); + + const key = `${prefix}${id}`; + resolvedPropsCache[key] = { className }; + + return id; +}; + +/** + * Resolves a React Native style object to DOM props + */ +const resolveProps = (reactNativeStyle) => { + const flatStyle = flattenStyle(reactNativeStyle); + + if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) { + console.groupCollapsed('[render] deoptimized: resolving uncached styles'); + console.log('source style\n', reactNativeStyle); + console.log('flattened style\n', flatStyle); + } + + const domStyle = createReactDOMStyle(flatStyle); + const style = {}; + + const _className = mapKeyValue(domStyle, (prop, value) => { + if (value != null) { + const singleClassName = createClassName(prop, value); + if (injectedClassNames[singleClassName]) { + return singleClassName; + } else { + style[prop] = value; + } + } + }) + // improves debugging in devtools and snapshots + .join('\n') + .trim(); + + const className = `\n${_className}`; + + const props = { + className, + style: prefixInlineStyles(style) + }; + + if (process.env.__REACT_NATIVE_DEBUG_ENABLED__) { + console.log('DOM props\n', props); + console.groupEnd(); + } + + return props; +}; + +/** + * Caching layer over 'resolveProps' + */ +const resolvePropsIfNeeded = (key, style) => { + if (!key || !resolvedPropsCache[key]) { + // slow: convert style object to props and cache + resolvedPropsCache[key] = resolveProps(style); + } + return resolvedPropsCache[key]; +}; + +/** + * Web style registry + */ +const StyleRegistry = { + initialize() { + const classNames = injector.getAvailableClassNames(); + classNames.forEach((className) => { injectedClassNames[className] = true; }); + }, + + reset() { + injectedClassNames = {}; + resolvedPropsCache = {}; + injector.reset(); + }, + + register(style) { + const id = ReactNativePropRegistry.register(style); + return registerStyle(id, style); + }, + + resolve(reactNativeStyle) { + if (!reactNativeStyle) { + return undefined; + } + + // fast and cachable + if (typeof reactNativeStyle === 'number') { + const key = `${prefix}${reactNativeStyle}`; + return resolvePropsIfNeeded(key, reactNativeStyle); + } + + // convert a RN style object to DOM props + if (!Array.isArray(reactNativeStyle)) { + return resolveProps(reactNativeStyle); + } + + // flatten the array + // [ 1, [ 2, 3 ], { prop: value }, 4, 5 ] => [ 1, 2, 3, { prop: value }, 4, 5 ]; + const flatArray = flattenArray(reactNativeStyle); + let isArrayOfNumbers = true; + for (let i = 0; i < flatArray.length; i++) { + if (typeof flatArray[i] !== 'number') { + isArrayOfNumbers = false; + break; + } + } + + if (isArrayOfNumbers) { + // cache resolved props + const key = `${prefix}${flatArray.join('-')}`; + return resolvePropsIfNeeded(key, flatArray); + } else { + // resolve + return resolveProps(flatArray); + } + } +}; + +module.exports = StyleRegistry; diff --git a/src/apis/StyleSheet/resolveBoxShadow.js b/src/apis/StyleSheet/resolveBoxShadow.js index 4840cd363..37b70239b 100644 --- a/src/apis/StyleSheet/resolveBoxShadow.js +++ b/src/apis/StyleSheet/resolveBoxShadow.js @@ -3,11 +3,13 @@ import normalizeValue from './normalizeValue'; const defaultOffset = { height: 0, width: 0 }; -const applyOpacity = (colorNumber, opacity) => { - const r = (colorNumber & 0xff000000) >>> 24; - const g = (colorNumber & 0x00ff0000) >>> 16; - const b = (colorNumber & 0x0000ff00) >>> 8; - const a = (((colorNumber & 0x000000ff) >>> 0) / 255).toFixed(2); +const applyOpacity = (color, opacity = 1) => { + const nullableColor = normalizeColor(color); + const colorInt = nullableColor === null ? 0x00000000 : nullableColor; + const r = Math.round(((colorInt & 0xff000000) >>> 24)); + const g = Math.round(((colorInt & 0x00ff0000) >>> 16)); + const b = Math.round(((colorInt & 0x0000ff00) >>> 8)); + const a = (((colorInt & 0x000000ff) >>> 0) / 255).toFixed(2); return `rgba(${r},${g},${b},${a * opacity})`; }; @@ -17,10 +19,7 @@ const resolveBoxShadow = (resolvedStyle, style) => { const offsetX = normalizeValue(null, width); const offsetY = normalizeValue(null, height); const blurRadius = normalizeValue(null, style.shadowRadius || 0); - // rgba color - const opacity = style.shadowOpacity != null ? style.shadowOpacity : 1; - const colorNumber = normalizeColor(style.shadowColor) || 0x00000000; - const color = applyOpacity(colorNumber, opacity); + const color = applyOpacity(style.shadowColor, style.shadowOpacity); const boxShadow = `${offsetX} ${offsetY} ${blurRadius} ${color}`; resolvedStyle.boxShadow = style.boxShadow ? `${style.boxShadow}, ${boxShadow}` : boxShadow; diff --git a/src/apis/StyleSheet/resolveTransform.js b/src/apis/StyleSheet/resolveTransform.js index 5db839424..7ce4e096f 100644 --- a/src/apis/StyleSheet/resolveTransform.js +++ b/src/apis/StyleSheet/resolveTransform.js @@ -19,7 +19,7 @@ const resolveTransform = (resolvedStyle, style) => { const transform = style.transform.map(mapTransform).join(' '); resolvedStyle.transform = transform; } else if (style.transformMatrix) { - const transform = convertTransformMatrix(style.transformMatrix) + const transform = convertTransformMatrix(style.transformMatrix); resolvedStyle.transform = transform; } }; diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js index aea7e0538..1901d84a1 100644 --- a/src/apis/UIManager/index.js +++ b/src/apis/UIManager/index.js @@ -1,5 +1,7 @@ import createReactDOMStyle from '../StyleSheet/createReactDOMStyle'; +import flattenStyle from '../StyleSheet/flattenStyle'; import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations'; +import prefixInlineStyles from '../StyleSheet/prefixInlineStyles'; const _measureLayout = (node, relativeToNativeNode, callback) => { const relativeNode = relativeToNativeNode || node.parentNode; @@ -41,14 +43,11 @@ const UIManager = { const value = props[prop]; switch (prop) { - case 'style': - // convert styles to DOM-styles - CSSPropertyOperations.setValueForStyles( - node, - createReactDOMStyle(value), - component._reactInternalInstance - ); + case 'style': { + const style = prefixInlineStyles(createReactDOMStyle(flattenStyle(value))); + CSSPropertyOperations.setValueForStyles(node, style, component._reactInternalInstance); break; + } case 'class': case 'className': { const nativeProp = 'class'; diff --git a/src/components/Image/__tests__/__snapshots__/index-test.js.snap b/src/components/Image/__tests__/__snapshots__/index-test.js.snap index 994f7f64a..8ca0ffee0 100644 --- a/src/components/Image/__tests__/__snapshots__/index-test.js.snap +++ b/src/components/Image/__tests__/__snapshots__/index-test.js.snap @@ -1,228 +1,168 @@ exports[`components/Image passes other props through to underlying View 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "accessibilityLabel" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "accessible" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "children" 1`] = `
+ style={Object {}}>
@@ -230,58 +170,48 @@ exports[`components/Image prop "children" 1`] = ` exports[`components/Image prop "defaultSource" does not override "height" and "width" styles 1`] = `
@@ -289,58 +219,48 @@ exports[`components/Image prop "defaultSource" does not override "height" and "w exports[`components/Image prop "defaultSource" sets "height" and "width" styles if missing 1`] = `
@@ -348,563 +268,421 @@ exports[`components/Image prop "defaultSource" sets "height" and "width" styles exports[`components/Image prop "defaultSource" sets background image when value is a string 1`] = `
`; exports[`components/Image prop "defaultSource" sets background image when value is an object 1`] = `
`; exports[`components/Image prop "resizeMode" value "contain" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "resizeMode" value "cover" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "resizeMode" value "none" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "resizeMode" value "stretch" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "resizeMode" value "undefined" 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "style" correctly supports "resizeMode" property 1`] = `
+ style={Object {}} /> `; exports[`components/Image prop "testID" 1`] = `
+ style={Object {}} /> `; exports[`components/Image sets correct accessibility role" 1`] = `
+ style={Object {}} /> `; diff --git a/src/components/Image/index.js b/src/components/Image/index.js index cdcd24a3b..edad6a33d 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -131,8 +131,8 @@ class Image extends Component { styles.initial, imageSizeStyle, originalStyle, - backgroundImage && { backgroundImage }, - resizeModeStyles[finalResizeMode] + resizeModeStyles[finalResizeMode], + backgroundImage && { backgroundImage } ]); // View doesn't support 'resizeMode' as a style delete style.resizeMode; diff --git a/src/components/Switch/__tests__/__snapshots__/index-test.js.snap b/src/components/Switch/__tests__/__snapshots__/index-test.js.snap index f51ab46c3..08b0f8931 100644 --- a/src/components/Switch/__tests__/__snapshots__/index-test.js.snap +++ b/src/components/Switch/__tests__/__snapshots__/index-test.js.snap @@ -1,855 +1,705 @@ exports[`components/Switch disabled when "false" a default checkbox is rendered 1`] = `
`; exports[`components/Switch disabled when "true" a disabled checkbox is rendered 1`] = `
`; exports[`components/Switch value when "false" an unchecked checkbox is rendered 1`] = `
`; exports[`components/Switch value when "true" a checked checkbox is rendered 1`] = `
`; diff --git a/src/components/Text/__tests__/__snapshots__/index-test.js.snap b/src/components/Text/__tests__/__snapshots__/index-test.js.snap index 1357cd611..58bff19e9 100644 --- a/src/components/Text/__tests__/__snapshots__/index-test.js.snap +++ b/src/components/Text/__tests__/__snapshots__/index-test.js.snap @@ -1,26 +1,14 @@ exports[`components/Text prop "children" 1`] = ` children @@ -28,86 +16,67 @@ exports[`components/Text prop "children" 1`] = ` exports[`components/Text prop "onPress" 1`] = ` `; exports[`components/Text prop "selectable" 1`] = ` `; exports[`components/Text prop "selectable" 2`] = ` + className=" +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-color:inherit +rn-display:inline +rn-font:inherit +rn-marginTop:0px +rn-marginRight:0px +rn-marginBottom:0px +rn-marginLeft:0px +rn-paddingTop:0px +rn-paddingRight:0px +rn-paddingBottom:0px +rn-paddingLeft:0px +rn-textDecoration:none +rn-userSelect:none +rn-whiteSpace:pre-wrap +rn-wordWrap:break-word" + style={Object {}} /> `; diff --git a/src/components/Text/index.js b/src/components/Text/index.js index 91bbde1e9..5f3957b79 100644 --- a/src/components/Text/index.js +++ b/src/components/Text/index.js @@ -78,6 +78,7 @@ const styles = StyleSheet.create({ margin: 0, padding: 0, textDecorationLine: 'none', + whiteSpace: 'pre-wrap', wordWrap: 'break-word' }, notSelectable: { diff --git a/src/components/View/__tests__/__snapshots__/index-test.js.snap b/src/components/View/__tests__/__snapshots__/index-test.js.snap index 47fe8c99c..a0ed7ad2e 100644 --- a/src/components/View/__tests__/__snapshots__/index-test.js.snap +++ b/src/components/View/__tests__/__snapshots__/index-test.js.snap @@ -1,525 +1,399 @@ exports[`components/View prop "children" 1`] = `
+ className=" +rn-alignItems:stretch +rn-backgroundColor:transparent +rn-borderTopStyle:solid +rn-borderRightStyle:solid +rn-borderBottomStyle:solid +rn-borderLeftStyle:solid +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-boxSizing:border-box +rn-color:inherit +rn-display:flex +rn-flexBasis:auto +rn-flexDirection:column +rn-flexShrink:0 +rn-font:inherit +rn-listStyle:none +rn-marginTop:0px +rn-marginRight:0px +rn-marginBottom:0px +rn-marginLeft:0px +rn-minHeight:0px +rn-minWidth:0px +rn-paddingTop:0px +rn-paddingRight:0px +rn-paddingBottom:0px +rn-paddingLeft:0px +rn-position:relative +rn-textAlign:inherit +rn-textDecoration:none" + style={Object {}}>
+ style={Object {}} />
`; exports[`components/View prop "pointerEvents" 1`] = `
`; exports[`components/View prop "style" 1`] = `
+ className=" +rn-alignItems:stretch +rn-backgroundColor:transparent +rn-borderTopStyle:solid +rn-borderRightStyle:solid +rn-borderBottomStyle:solid +rn-borderLeftStyle:solid +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-boxSizing:border-box +rn-color:inherit +rn-display:flex +rn-flexBasis:auto +rn-flexDirection:column +rn-flexShrink:0 +rn-font:inherit +rn-listStyle:none +rn-marginTop:0px +rn-marginRight:0px +rn-marginBottom:0px +rn-marginLeft:0px +rn-minHeight:0px +rn-minWidth:0px +rn-paddingTop:0px +rn-paddingRight:0px +rn-paddingBottom:0px +rn-paddingLeft:0px +rn-position:relative +rn-textAlign:inherit +rn-textDecoration:none" + style={Object {}} /> `; exports[`components/View prop "style" 2`] = `
`; exports[`components/View prop "style" 3`] = `
`; exports[`components/View prop "style" 4`] = `
`; exports[`components/View rendered element is a "div" by default 1`] = `
+ className=" +rn-alignItems:stretch +rn-backgroundColor:transparent +rn-borderTopStyle:solid +rn-borderRightStyle:solid +rn-borderBottomStyle:solid +rn-borderLeftStyle:solid +rn-borderTopWidth:0px +rn-borderRightWidth:0px +rn-borderBottomWidth:0px +rn-borderLeftWidth:0px +rn-boxSizing:border-box +rn-color:inherit +rn-display:flex +rn-flexBasis:auto +rn-flexDirection:column +rn-flexShrink:0 +rn-font:inherit +rn-listStyle:none +rn-marginTop:0px +rn-marginRight:0px +rn-marginBottom:0px +rn-marginLeft:0px +rn-minHeight:0px +rn-minWidth:0px +rn-paddingTop:0px +rn-paddingRight:0px +rn-paddingBottom:0px +rn-paddingLeft:0px +rn-position:relative +rn-textAlign:inherit +rn-textDecoration:none" + style={Object {}} /> `; exports[`components/View rendered element is a "span" when inside 1`] = ` `; diff --git a/src/modules/createDOMElement/__tests__/__snapshots__/index-test.js.snap b/src/modules/createDOMElement/__tests__/__snapshots__/index-test.js.snap index 415967432..2a0a6776b 100644 --- a/src/modules/createDOMElement/__tests__/__snapshots__/index-test.js.snap +++ b/src/modules/createDOMElement/__tests__/__snapshots__/index-test.js.snap @@ -1,75 +1,45 @@ exports[`modules/createDOMElement prop "accessibilityLabel" 1`] = ` + aria-label="accessibilityLabel" /> `; exports[`modules/createDOMElement prop "accessibilityLiveRegion" 1`] = ` + aria-live="polite" /> `; exports[`modules/createDOMElement prop "accessibilityRole" button 1`] = `