From be8572d6f2aad1e15ee102f750d7901bc602f1dd Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Mon, 17 Jun 2019 13:06:45 +0200 Subject: [PATCH] many more tests and some improvements to styped interface --- changelog.md | 6 + packages/react-jss/.size-snapshot.json | 26 +- packages/react-jss/package.json | 2 +- packages/react-jss/src/createUseStyles.js | 2 +- .../react-jss/src/styled-props-filter.test.js | 527 ++++++++++++++++++ packages/react-jss/src/styled.js | 60 +- packages/react-jss/src/styled.test.js | 414 +++++++------- packages/react-jss/src/utils/sheets.js | 29 +- packages/react-jss/tests/styledSystem.js | 16 +- yarn.lock | 7 - 10 files changed, 828 insertions(+), 261 deletions(-) create mode 100644 packages/react-jss/src/styled-props-filter.test.js diff --git a/changelog.md b/changelog.md index 417f911f2..41bcd509e 100755 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +## Next + +### Bug Fixes + +- [react-jss] Experimental styled API got some fixes and many more tests ([#1118](https://github.com/cssinjs/jss/pull/1118)) + ## 10.0.0-alpha.18 (2019-6-14) ### Features diff --git a/packages/react-jss/.size-snapshot.json b/packages/react-jss/.size-snapshot.json index e5146365b..64fe1d8cd 100644 --- a/packages/react-jss/.size-snapshot.json +++ b/packages/react-jss/.size-snapshot.json @@ -1,30 +1,30 @@ { "dist/react-jss.js": { - "bundled": 166059, - "minified": 57815, - "gzipped": 18791 + "bundled": 167089, + "minified": 57967, + "gzipped": 18853 }, "dist/react-jss.min.js": { - "bundled": 109367, - "minified": 41185, - "gzipped": 13853 + "bundled": 110364, + "minified": 41321, + "gzipped": 13914 }, "dist/react-jss.cjs.js": { - "bundled": 24763, - "minified": 11127, - "gzipped": 3649 + "bundled": 25739, + "minified": 11339, + "gzipped": 3704 }, "dist/react-jss.esm.js": { - "bundled": 23800, - "minified": 10291, - "gzipped": 3521, + "bundled": 24767, + "minified": 10494, + "gzipped": 3582, "treeshaked": { "rollup": { "code": 1946, "import_statements": 538 }, "webpack": { - "code": 3517 + "code": 3544 } } } diff --git a/packages/react-jss/package.json b/packages/react-jss/package.json index c1d09bbb1..c307219c1 100644 --- a/packages/react-jss/package.json +++ b/packages/react-jss/package.json @@ -40,7 +40,7 @@ "dependencies": { "@babel/runtime": "^7.3.1", "@emotion/is-prop-valid": "^0.7.3", - "css-jss": "10.0.0-alpha.18", + "css-jss": "^10.0.0-alpha.18", "hoist-non-react-statics": "^3.2.0", "is-in-browser": "^1.1.3", "jss": "10.0.0-alpha.18", diff --git a/packages/react-jss/src/createUseStyles.js b/packages/react-jss/src/createUseStyles.js index 9fb73d670..a3a50a8f8 100644 --- a/packages/react-jss/src/createUseStyles.js +++ b/packages/react-jss/src/createUseStyles.js @@ -28,7 +28,7 @@ const reducer = (prevState, action) => { } const createUseStyles = (styles: Styles, options?: HookOptions = {}) => { - const {index = getSheetIndex(), theming, name = 'Hook', ...sheetOptions} = options + const {index = getSheetIndex(), theming, name, ...sheetOptions} = options const ThemeContext = (theming && theming.context) || DefaultThemeContext const useTheme = typeof styles === 'function' diff --git a/packages/react-jss/src/styled-props-filter.test.js b/packages/react-jss/src/styled-props-filter.test.js new file mode 100644 index 000000000..765affebf --- /dev/null +++ b/packages/react-jss/src/styled-props-filter.test.js @@ -0,0 +1,527 @@ +// @flow +import expect from 'expect.js' +import React, {type StatelessFunctionalComponent} from 'react' +import TestRenderer from 'react-test-renderer' +import {stripIndent} from 'common-tags' +import {styled, JssProvider, SheetsRegistry} from '.' + +type Props = Object + +const createGenerateId = () => { + let counter = 0 + return rule => `${rule.key}-${counter++}` +} + +const renderToJSON = children => { + const registry = new SheetsRegistry() + return { + tree: TestRenderer.create( + + {children} + + ).toJSON(), + css: registry.toString() + } +} + +describe('React-JSS: styled props filter', () => { + it('should compose shouldForwardProp on composed styled components', () => { + const StyledDiv = styled('div', { + shouldForwardProp: prop => prop === 'forwardMe' + })() + + const ComposedDiv = styled(StyledDiv, { + shouldForwardProp: () => true + })() + + const {tree} = renderToJSON() + + expect(tree).to.eql({ + type: 'div', + props: {forwardMe: true, className: ''}, + children: null + }) + }) + + it('should enable custom shouldForwardProp', () => { + const Svg: StatelessFunctionalComponent = props => ( + + + + ) + + const StyledSvg = styled(Svg, { + shouldForwardProp: prop => ['className', 'width', 'height'].indexOf(prop) !== -1 + })({ + fill: ({color}) => color + }) + + const {tree, css} = renderToJSON() + + expect(css).to.be(stripIndent` + .sc-0 {} + .sc-0-1 { + fill: #0000ff; + } + `) + expect(tree).to.eql({ + type: 'svg', + props: {width: '100px', height: '100px', className: 'sc-0 sc-0-1'}, + children: [ + { + type: 'rect', + props: {x: '10', y: '10', height: '100', width: '100', style: {stroke: '#ff0000'}}, + children: null + } + ] + }) + }) + + it('should inherit shouldForwardProp for wrapped styled components', () => { + const Div1 = styled('div', { + shouldForwardProp: prop => prop !== 'color' + })({ + color: ({color}) => color + }) + + const Div2 = styled(Div1)() + + const {css, tree} = renderToJSON( + <> + + + + ) + + expect(css.trim()).to.be(stripIndent` + .sc-0 {} + .sc-0-1 { + color: red; + } + .sc-1-2 { + color: green; + } + `) + expect(tree).to.eql([ + {type: 'div', props: {id: 'test-1', className: 'sc-0 sc-0-1'}, children: null}, + {type: 'div', props: {id: 'test-2', className: 'sc-0 sc-1-2'}, children: null} + ]) + }) + + it('prop filtering', () => { + const Link = styled('a')({color: 'green'}) + const rest = {m: [3], pt: [4]} + + const {css, tree} = renderToJSON( + + hello world + + ) + + expect(css).to.be(stripIndent` + .sc-0 { + color: green; + } + `) + expect(tree).to.eql({ + type: 'a', + props: { + is: true, + 'aria-label': 'some label', + 'data-wow': 'value', + href: 'link', + className: 'sc-0' + }, + children: ['hello world'] + }) + }) + + it('should not filter on non string tags', () => { + // eslint-disable-next-line jsx-a11y/anchor-has-content + const Comp: StatelessFunctionalComponent = props => + const Link = styled(Comp)({color: 'green'}) + + const {css, tree} = renderToJSON( + + hello world + + ) + expect(css).to.be(stripIndent` + .sc-0 { + color: green; + } + `) + expect(tree).to.eql({ + type: 'a', + props: { + a: true, + b: true, + wow: true, + prop: true, + filtering: true, + is: true, + cool: true, + 'aria-label': 'some label', + 'data-wow': 'value', + href: 'link', + className: 'sc-0' + }, + children: ['hello world'] + }) + }) + + it.skip('no prop filtering on string tags started with upper case', () => { + const Link = styled('SomeCustomLink')({color: 'green'}) + + const {tree} = renderToJSON( + + hello world + + ) + + expect(tree).to.eql({}) + }) + + it('basic SVG attributes survive prop filtering', () => { + const RedCircle = styled('circle')({ + fill: '#ff0000', + strokeWidth: 0.26458332 + }) + + const {tree} = renderToJSON() + expect(tree).to.eql({ + type: 'circle', + props: {r: '9.8273811', cy: '49.047619', cx: '65.011902', className: 'sc-0'}, + children: null + }) + }) + + it('all SVG attributes survive prop filtering', () => { + const svgAttributes = { + accentHeight: 'abcd', + accumulate: 'abcd', + additive: 'abcd', + alignmentBaseline: 'abcd', + allowReorder: 'abcd', + alphabetic: 'abcd', + amplitude: 'abcd', + arabicForm: 'abcd', + ascent: 'abcd', + attributeName: 'abcd', + attributeType: 'abcd', + autoReverse: 'abcd', + azimuth: 'abcd', + baseFrequency: 'abcd', + baselineShift: 'abcd', + baseProfile: 'abcd', + bbox: 'abcd', + begin: 'abcd', + bias: 'abcd', + by: 'abcd', + calcMode: 'abcd', + capHeight: 'abcd', + clip: 'abcd', + clipPathUnits: 'abcd', + clipPath: 'abcd', + clipRule: 'abcd', + colorInterpolation: 'abcd', + colorInterpolationFilters: 'abcd', + colorProfile: 'abcd', + colorRendering: 'abcd', + contentScriptType: 'abcd', + contentStyleType: 'abcd', + cursor: 'abcd', + cx: 'abcd', + cy: 'abcd', + d: 'abcd', + decelerate: 'abcd', + descent: 'abcd', + diffuseConstant: 'abcd', + direction: 'abcd', + display: 'abcd', + divisor: 'abcd', + dominantBaseline: 'abcd', + dur: 'abcd', + dx: 'abcd', + dy: 'abcd', + edgeMode: 'abcd', + elevation: 'abcd', + enableBackground: 'abcd', + end: 'abcd', + exponent: 'abcd', + externalResourcesRequired: 'abcd', + fill: 'abcd', + fillOpacity: 'abcd', + fillRule: 'abcd', + filter: 'abcd', + filterRes: 'abcd', + filterUnits: 'abcd', + floodColor: 'abcd', + floodOpacity: 'abcd', + fontFamily: 'abcd', + fontSize: 'abcd', + fontSizeAdjust: 'abcd', + fontStretch: 'abcd', + fontStyle: 'abcd', + fontVariant: 'abcd', + fontWeight: 'abcd', + format: 'abcd', + from: 'abcd', + // fr: 'abcd', React doesn't seem to allow this on any element but it should be legal on radialGradients + fx: 'abcd', + fy: 'abcd', + g1: 'abcd', + g2: 'abcd', + glyphName: 'abcd', + glyphOrientationHorizontal: 'abcd', + glyphOrientationVertical: 'abcd', + glyphRef: 'abcd', + gradientTransform: 'abcd', + gradientUnits: 'abcd', + hanging: 'abcd', + horizAdvX: 'abcd', + horizOriginX: 'abcd', + ideographic: 'abcd', + imageRendering: 'abcd', + in: 'abcd', + in2: 'abcd', + intercept: 'abcd', + k: 'abcd', + k1: 'abcd', + k2: 'abcd', + k3: 'abcd', + k4: 'abcd', + kernelMatrix: 'abcd', + kernelUnitLength: 'abcd', + kerning: 'abcd', + keyPoints: 'abcd', + keySplines: 'abcd', + keyTimes: 'abcd', + lengthAdjust: 'abcd', + letterSpacing: 'abcd', + lightingColor: 'abcd', + limitingConeAngle: 'abcd', + local: 'abcd', + markerEnd: 'abcd', + markerMid: 'abcd', + markerStart: 'abcd', + markerHeight: 'abcd', + markerUnits: 'abcd', + markerWidth: 'abcd', + mask: 'abcd', + maskContentUnits: 'abcd', + maskUnits: 'abcd', + mathematical: 'abcd', + mode: 'abcd', + numOctaves: 'abcd', + offset: 'abcd', + opacity: 'abcd', + operator: 'abcd', + order: 'abcd', + orient: 'abcd', + orientation: 'abcd', + origin: 'abcd', + overflow: 'abcd', + overlinePosition: 'abcd', + overlineThickness: 'abcd', + panose1: 'abcd', + paintOrder: 'abcd', + pathLength: 'abcd', + patternContentUnits: 'abcd', + patternTransform: 'abcd', + patternUnits: 'abcd', + pointerEvents: 'abcd', + points: 'abcd', + pointsAtX: 'abcd', + pointsAtY: 'abcd', + pointsAtZ: 'abcd', + preserveAlpha: 'abcd', + preserveAspectRatio: 'abcd', + primitiveUnits: 'abcd', + r: 'abcd', + radius: 'abcd', + refX: 'abcd', + refY: 'abcd', + renderingIntent: 'abcd', + repeatCount: 'abcd', + repeatDur: 'abcd', + requiredExtensions: 'abcd', + requiredFeatures: 'abcd', + restart: 'abcd', + result: 'abcd', + rotate: 'abcd', + rx: 'abcd', + ry: 'abcd', + scale: 'abcd', + seed: 'abcd', + shapeRendering: 'abcd', + slope: 'abcd', + spacing: 'abcd', + specularConstant: 'abcd', + specularExponent: 'abcd', + speed: 'abcd', + spreadMethod: 'abcd', + startOffset: 'abcd', + stdDeviation: 'abcd', + stemh: 'abcd', + stemv: 'abcd', + stitchTiles: 'abcd', + stopColor: 'abcd', + stopOpacity: 'abcd', + strikethroughPosition: 'abcd', + strikethroughThickness: 'abcd', + string: 'abcd', + stroke: 'abcd', + strokeDasharray: 'abcd', + strokeDashoffset: 'abcd', + strokeLinecap: 'abcd', + strokeLinejoin: 'abcd', + strokeMiterlimit: 'abcd', + strokeOpacity: 'abcd', + strokeWidth: 'abcd', + surfaceScale: 'abcd', + systemLanguage: 'abcd', + tabIndex: 'abcd', + tableValues: 'abcd', + targetX: 'abcd', + targetY: 'abcd', + textAnchor: 'abcd', + textDecoration: 'abcd', + textRendering: 'abcd', + textLength: 'abcd', + to: 'abcd', + transform: 'abcd', + u1: 'abcd', + u2: 'abcd', + underlinePosition: 'abcd', + underlineThickness: 'abcd', + unicode: 'abcd', + unicodeBidi: 'abcd', + unicodeRange: 'abcd', + unitsPerEm: 'abcd', + vAlphabetic: 'abcd', + vHanging: 'abcd', + vIdeographic: 'abcd', + vMathematical: 'abcd', + values: 'abcd', + version: 'abcd', + vertAdvY: 'abcd', + vertOriginX: 'abcd', + vertOriginY: 'abcd', + viewBox: 'abcd', + viewTarget: 'abcd', + visibility: 'abcd', + widths: 'abcd', + wordSpacing: 'abcd', + writingMode: 'abcd', + x: 'abcd', + xHeight: 'abcd', + x1: 'abcd', + x2: 'abcd', + xChannelSelector: 'abcd', + xlinkActuate: 'abcd', + xlinkArcrole: 'abcd', + xlinkHref: 'abcd', + xlinkRole: 'abcd', + xlinkShow: 'abcd', + xlinkTitle: 'abcd', + xlinkType: 'abcd', + xmlBase: 'abcd', + xmlLang: 'abcd', + xmlSpace: 'abcd', + y: 'abcd', + y1: 'abcd', + y2: 'abcd', + yChannelSelector: 'abcd', + z: 'abcd', + zoomAndPan: 'abcd' + } + + const RedPath = styled('path')({ + strokeWidth: 0.26458332 + }) + + const {tree} = renderToJSON() + + expect(tree).to.eql({ + type: 'path', + props: { + ...svgAttributes, + className: 'sc-0' + }, + children: null + }) + }) + + it('prop filtering on composed styled components that are string tags', () => { + const BaseLink = styled('a')({background: 'red'}) + const Link = styled(BaseLink)({color: 'green'}) + + const {css, tree} = renderToJSON( + + hello world + + ) + + expect(css).to.be(stripIndent` + .sc-1 { + background: red; + } + .sc-0 { + color: green; + } + `) + expect(tree).to.eql({ + type: 'a', + props: { + is: true, + kind: true, + for: 'other reasons', + 'aria-label': 'some label', + 'data-wow': 'value', + href: 'link', + className: 'sc-0 sc-1' + }, + children: ['hello world'] + }) + }) +}) diff --git a/packages/react-jss/src/styled.js b/packages/react-jss/src/styled.js index 1a9c57811..0b7d6dcae 100644 --- a/packages/react-jss/src/styled.js +++ b/packages/react-jss/src/styled.js @@ -37,7 +37,7 @@ const parseStyles = (args: {[string]: StyleArg}) => { } const styles = {} - const label = labels.length === 0 ? 'css' : labels.join('-') + const label = labels.length === 0 ? 'sc' : labels.join('-') if (staticStyle) { // Label should not leak to the core. @@ -47,13 +47,13 @@ const parseStyles = (args: {[string]: StyleArg}) => { // When there is only one function rule, we don't need to wrap it. if (dynamicStyles.length === 1) { - styles.cssd = dynamicStyles[0] + styles.scd = dynamicStyles[0] } // We create a new function rule which will call all other function rules // and merge the styles they return. if (dynamicStyles.length > 1) { - styles.cssd = props => { + styles.scd = props => { const merged = {} for (let i = 0; i < dynamicStyles.length; i++) { const dynamicStyle = dynamicStyles[i](props) @@ -66,45 +66,75 @@ const parseStyles = (args: {[string]: StyleArg}) => { return {styles, label} } +const shouldForwardPropSymbol = Symbol('react-jss-styled') + +type ShouldForwardProp = string => boolean type StyledOptions = HookOptions & { - shouldForwardProp?: string => boolean + shouldForwardProp?: ShouldForwardProp } export default ( - type: string | StatelessFunctionalComponent | ComponentType, + tagOrComponent: string | StatelessFunctionalComponent | ComponentType, options?: StyledOptions = {} ) => { const {theming, shouldForwardProp} = options - const isTagName = typeof type === 'string' + const isTag = typeof tagOrComponent === 'string' const ThemeContext = theming ? theming.context : DefaultThemeContext + // $FlowIgnore that prop shouldn't be there. + const childShouldForwardProp: ShouldForwardProp = tagOrComponent[shouldForwardPropSymbol] + let finalShouldForwardProp = shouldForwardProp || childShouldForwardProp + if (shouldForwardProp && childShouldForwardProp) { + finalShouldForwardProp = prop => childShouldForwardProp(prop) && shouldForwardProp(prop) + } + return function StyledFactory(/* :: ...args: StyleArg[] */): StatelessFunctionalComponent< StyledProps > { // eslint-disable-next-line prefer-rest-params const {styles, label} = parseStyles(arguments) - - const useStyles = createUseStyles(styles, label ? {...options, name: label} : options) + const useStyles = createUseStyles(styles, options) const Styled = (props: StyledProps) => { const {as, className} = props // $FlowFixMe theming ThemeContext types need to be fixed. const theme = React.useContext(ThemeContext) - const classes = useStyles({...props, theme}) + const propsWithTheme: StyledProps = Object.assign(({theme}: any), props) + const classes = useStyles(propsWithTheme) const childProps: StyledProps = ({}: any) for (const prop in props) { - // We don't want to pass non-dom props to the DOM, - // but we still want to forward them to a users component. - if (isTagName && !isPropValid(prop)) continue - if (shouldForwardProp && shouldForwardProp(prop) === false) continue + if (finalShouldForwardProp) { + if (finalShouldForwardProp(prop) === true) { + childProps[prop] = props[prop] + } + continue + } + + // We don't want to pass non-dom props to the DOM. + if (isTag) { + if (isPropValid(prop)) { + childProps[prop] = props[prop] + } + continue + } + childProps[prop] = props[prop] } + // $FlowIgnore we don't care label might not exist in classes. - const classNames = `${classes[label] || classes.css || ''} ${classes.cssd || ''}`.trim() + const classNames = `${classes[label] || classes.sc || ''} ${classes.scd || ''}`.trim() childProps.className = className ? `${className} ${classNames}` : classNames + if (!isTag && finalShouldForwardProp) { + // $FlowIgnore we are not supposed to attach random properties to component functions. + tagOrComponent[shouldForwardPropSymbol] = finalShouldForwardProp + } + + if (isTag && as) { + return React.createElement(as, childProps) + } - return React.createElement(as || type, childProps) + return React.createElement(tagOrComponent, childProps) } return Styled diff --git a/packages/react-jss/src/styled.test.js b/packages/react-jss/src/styled.test.js index 4a72f8541..970ef6f6e 100644 --- a/packages/react-jss/src/styled.test.js +++ b/packages/react-jss/src/styled.test.js @@ -6,291 +6,301 @@ import TestRenderer from 'react-test-renderer' import {stripIndent} from 'common-tags' import {styled, SheetsRegistry, JssProvider, ThemeProvider} from '.' +type Props = Object + const createGenerateId = () => { let counter = 0 return rule => `${rule.key}-${counter++}` } +const renderToJSON = children => { + const registry = new SheetsRegistry() + return { + tree: TestRenderer.create( + + {children} + + ).toJSON(), + css: registry.toString() + } +} + describe('React-JSS: styled', () => { it('should render static styles', () => { - const registry = new SheetsRegistry() const Div = styled('div')({color: 'red'}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; } `) - const {className, classes} = renderer.root.findByType('div').props - expect(className).to.be('css-0') - expect(classes).to.be(undefined) + expect(tree).to.eql({ + type: 'div', + props: {className: 'sc-0'}, + children: null + }) }) it('should render dynamic values', () => { - const registry = new SheetsRegistry() const Div = styled('div')({ color: 'red', width: props => props.width }) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + + expect(css).to.be(stripIndent` + .sc-0 { color: red; } - .css-0-1 { + .sc-0-1 { width: 10px; } `) - expect(renderer.root.findByType('div').props.className).to.be('css-0 css-0-1') + + expect(tree).to.eql({ + type: 'div', + props: { + width: 10, + className: 'sc-0 sc-0-1' + }, + children: null + }) }) it('should render dynamic rules', () => { - const registry = new SheetsRegistry() const Div = styled('div')(props => ({ color: 'red', width: props.width })) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .cssd-0 {} - .cssd-0-1 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .scd-0 {} + .scd-0-1 { color: red; width: 10px; } `) - expect(renderer.root.findByType('div').props.className).to.be('cssd-0 cssd-0-1') + expect(tree).to.eql({ + type: 'div', + props: { + width: 10, + className: 'scd-0 scd-0-1' + }, + children: null + }) }) it('should accept multiple static style rules', () => { - const registry = new SheetsRegistry() // TODO add a template string case const Div = styled('div')({color: 'red'}, {border: '1px solid red'}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; border: 1px solid red; } `) - expect(renderer.root.findByType('div').props.className).to.be('css-0') + expect(tree).to.eql({ + type: 'div', + props: { + className: 'sc-0' + }, + children: null + }) }) it('should filter empty values instead of rules', () => { - const registry = new SheetsRegistry() const Div = styled('div')('', {color: 'red'}, null, {border: '1px solid red'}, undefined) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; border: 1px solid red; } `) - expect(renderer.root.findByType('div').props.className).to.be('css-0') + expect(tree).to.eql({ + type: 'div', + props: { + className: 'sc-0' + }, + children: null + }) }) it('should accept multiple dynamic style rules', () => { - const registry = new SheetsRegistry() const Div = styled('div')(props => ({width: props.width}), props => ({height: props.height})) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .cssd-0 {} - .cssd-0-1 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .scd-0 {} + .scd-0-1 { width: 10px; height: 10px; } `) - expect(renderer.root.findByType('div').props.className).to.be('cssd-0 cssd-0-1') + expect(tree).to.eql({ + type: 'div', + props: { + width: 10, + height: 10, + className: 'scd-0 scd-0-1' + }, + children: null + }) }) it('should filter empty values returned from dynamic rules', () => { - const registry = new SheetsRegistry() const Div = styled('div')(() => null, () => '', () => undefined, {color: 'red'}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; } - .cssd-1 {} - .cssd-0-2 {} + .scd-1 {} + .scd-0-2 {} `) - expect(renderer.root.findByType('div').props.className).to.be('css-0 cssd-1 cssd-0-2') + expect(tree).to.eql({ + type: 'div', + props: { + className: 'sc-0 scd-1 scd-0-2' + }, + children: null + }) }) it('should accept multiple dynamic and static style rules', () => { - const registry = new SheetsRegistry() const Div = styled('div')( {color: 'red'}, props => ({width: props.width}), {border: '1px solid red'}, props => ({height: props.height}) ) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; border: 1px solid red; } - .cssd-1 {} - .cssd-0-2 { + .scd-1 {} + .scd-0-2 { width: 10px; height: 10px; } `) - expect(renderer.root.findByType('div').props.className).to.be('css-0 cssd-1 cssd-0-2') + expect(tree).to.eql({ + type: 'div', + props: { + width: 10, + height: 10, + className: 'sc-0 scd-1 scd-0-2' + }, + children: null + }) }) it('should accept template string', () => {}) it('should merge with user class name', () => { - const registry = new SheetsRegistry() const Div = styled('div')({color: 'red'}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { color: red; } `) - expect(renderer.root.findByType('div').props.className).to.be('my-class css-0') + expect(tree).to.eql({ + type: 'div', + props: { + className: 'my-class sc-0' + }, + children: null + }) }) it('should use "as" prop', () => { - const registry = new SheetsRegistry() const Div = styled('div')({color: 'red'}) - const renderer = TestRenderer.create( - -
- -
-
+ const {css, tree} = renderToJSON( +
+ +
) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + expect(css).to.be(stripIndent` + .sc-0 { color: red; } `) - const {className, as} = renderer.root.findByType('button').props - expect(className).to.be('css-0') - expect(as).to.be(undefined) + expect(tree).to.eql({ + type: 'button', + props: { + className: 'sc-0' + }, + children: [ + { + type: 'span', + props: {}, + children: null + } + ] + }) }) - it('should not leak non-dom attrs', () => { - const registry = new SheetsRegistry() - const Div = styled('div')({ - color: 'red', - width: props => props.s - }) - const renderer = TestRenderer.create( - -
- + it('should not use "as" prop for tag name when component was passed', () => { + const Comp: StatelessFunctionalComponent = () =>
+ const Div = styled(Comp)({color: 'red'}) + const {css, tree} = renderToJSON( +
+ +
) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + expect(css).to.be(stripIndent` + .sc-0 { color: red; } - .css-0-1 { - width: 10px; - } `) - const {className, s} = renderer.root.findByType('div').props - expect(className).to.be('css-0 css-0-1') - expect(s).to.be(undefined) + expect(tree).to.eql({type: 'div', props: {}, children: null}) }) it('should compose with styled component', () => { - const registry = new SheetsRegistry() const BaseDiv = styled('div')({color: 'red'}) const Div = styled(BaseDiv)({width: 10}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-1 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-1 { color: red; } - .css-0 { + .sc-0 { width: 10px; } `) - const {className} = renderer.root.findByType('div').props - expect(className).to.be('css-0 css-1') + expect(tree).to.eql({ + type: 'div', + props: { + className: 'sc-0 sc-1' + }, + children: null + }) }) it('should pass className to a user component', () => { - const registry = new SheetsRegistry() - type Props = Object const BaseDiv: StatelessFunctionalComponent = ({className}: Props) => (
) const Div = styled(BaseDiv)({width: 10}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { width: 10px; } `) - const {className} = renderer.root.findByType('div').props - expect(className).to.be('css-0') - }) - - it("should pass custom props to a user component if shouldForwardProp doesn't return false", () => { - type Props = Object - let receivedProps = {} - const UserComponent: StatelessFunctionalComponent = (props: Props) => { - receivedProps = props - return
- } - const shouldForwardProp = prop => prop !== 'disalowed' - const Div = styled(UserComponent, {shouldForwardProp})({width: 10}) - TestRenderer.create(
) - expect(receivedProps.allowed).to.be(true) - expect(receivedProps.disalowed).to.be(undefined) + expect(tree).to.eql({ + type: 'div', + props: { + className: 'sc-0' + }, + children: null + }) }) it.skip('should target another styled component (not sure if we really need this)', () => { - const registry = new SheetsRegistry() const Span = styled('span')({color: 'red'}) const Div = styled('div')({ // $FlowFixMe @@ -299,80 +309,76 @@ describe('React-JSS: styled', () => { } }) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .sc-0 { width: 10px; } `) - expect(renderer.root.findByType('div').props.className).to.be('XXX') - expect(renderer.root.findByType('span').props.className).to.be('XXX') + expect(tree).to.eql({}) + // expect(renderer.root.findByType('div').props.className).to.be('XXX') + // expect(renderer.root.findByType('span').props.className).to.be('XXX') }) it('should render theme', () => { - const registry = new SheetsRegistry() const Div = styled('div')({ color: 'red', margin: props => props.theme.spacing }) - TestRenderer.create( - - -
- - + const {css} = renderToJSON( + +
+ ) - expect(registry.toString()).to.be(stripIndent` - .css-0 { + expect(css).to.be(stripIndent` + .sc-0 { color: red; } - .css-0-1 { + .sc-0-1 { margin: 10px; } `) }) + it.skip('should override theme over props', () => {}) + it('should render label', () => { - const registry = new SheetsRegistry() const Div = styled('div')({label: 'my-div', color: 'red'}) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .my-div-0 { - color: red; - } - `) - const {className} = renderer.root.findByType('div').props - expect(className).to.be('my-div-0') + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .my-div-0 { + color: red; + } + `) + expect(tree).to.eql({ + type: 'div', + props: { + className: 'my-div-0' + }, + children: null + }) }) it('should merge labels', () => { - const registry = new SheetsRegistry() const Div = styled('div')( {label: 'labela', color: 'red'}, {label: 'labelb', background: 'red'}, {label: 'labela', float: 'left'} ) - const renderer = TestRenderer.create( - -
- - ) - expect(registry.toString()).to.be(stripIndent` - .labela-labelb-0 { - color: red; - float: left; - background: red; - } - `) - const {className} = renderer.root.findByType('div').props - expect(className).to.be('labela-labelb-0') + const {css, tree} = renderToJSON(
) + expect(css).to.be(stripIndent` + .labela-labelb-0 { + color: red; + float: left; + background: red; + } + `) + expect(tree).to.eql({ + type: 'div', + props: { + className: 'labela-labelb-0' + }, + children: null + }) }) }) diff --git a/packages/react-jss/src/utils/sheets.js b/packages/react-jss/src/utils/sheets.js index 5135e7fe3..66381e0a5 100644 --- a/packages/react-jss/src/utils/sheets.js +++ b/packages/react-jss/src/utils/sheets.js @@ -7,13 +7,13 @@ import {getManager} from './managers' import defaultJss from '../jss' import {addMeta, getMeta} from './sheetsMeta' -interface Options { - context: Context; - theme: Theme; - name: string; - index: number; - styles: Styles; - sheetOptions: $Diff; +type Options = { + context: Context, + theme: Theme, + name?: string, + index: number, + styles: Styles, + sheetOptions: $Diff } const getStyles = (options: Options) => { @@ -24,9 +24,8 @@ const getStyles = (options: Options) => { warning( styles.length !== 0, - `[JSS] <${ - options.name - } />'s styles function doesn't rely on the "theme" argument. We recommend declaring styles as an object instead.` + `[JSS] <${options.name || + 'Hook'} />'s styles function doesn't rely on the "theme" argument. We recommend declaring styles as an object instead.` ) return styles(options.theme) @@ -34,13 +33,19 @@ const getStyles = (options: Options) => { function getSheetOptions(options: Options, link: boolean) { const classNamePrefix = - process.env.NODE_ENV === 'production' ? '' : `${options.name.replace(/\s/g, '-')}-` + process.env.NODE_ENV === 'production' || !options.name + ? '' + : `${options.name.replace(/\s/g, '-')}-` + + let meta = '' + if (options.name) meta = `${options.name}, ` + meta += typeof options.styles === 'function' ? 'Themed' : 'Unthemed' return { ...options.sheetOptions, ...options.context.sheetOptions, index: options.index, - meta: `${options.name}, ${typeof options.styles === 'function' ? 'Themed' : 'Unthemed'}`, + meta, classNamePrefix: options.context.sheetOptions.classNamePrefix + classNamePrefix, link } diff --git a/packages/react-jss/tests/styledSystem.js b/packages/react-jss/tests/styledSystem.js index edb78e5e3..3cbc4ef2a 100644 --- a/packages/react-jss/tests/styledSystem.js +++ b/packages/react-jss/tests/styledSystem.js @@ -46,14 +46,14 @@ describe('React-JSS: styled-system', () => { ) // TODO we should not need a static rule in such cases. expect(registry.toString()).to.be(stripIndent` - .cssd-0 {} - .cssd-0-1 { + .scd-0 {} + .scd-0-1 { padding-left: 4px; padding-right: 4px; } `) const {className, classes} = renderer.root.findByType('div').props - expect(className).to.be('cssd-0 cssd-0-1') + expect(className).to.be('scd-0 scd-0-1') expect(classes).to.be(undefined) }) @@ -85,8 +85,8 @@ describe('React-JSS: styled-system', () => { ) // TODO we should not need a static rule in such cases. expect(registry.toString()).to.be(stripIndent` - .cssd-0 {} - .cssd-0-1 { + .scd-0 {} + .scd-0-1 { color: white; font-size: 32px; font-weight: bold; @@ -97,7 +97,7 @@ describe('React-JSS: styled-system', () => { background-color: #07c; } @media screen and (min-width: 40em) { - .cssd-0-1 { + .scd-0-1 { font-size: 48px; padding-top: 8px; padding-left: 32px; @@ -106,13 +106,13 @@ describe('React-JSS: styled-system', () => { } } @media screen and (min-width: 52em) { - .cssd-0-1 { + .scd-0-1 { font-size: 64px; } } `) const {className, classes} = renderer.root.findByType('div').props - expect(className).to.be('cssd-0 cssd-0-1') + expect(className).to.be('scd-0 scd-0-1') expect(classes).to.be(undefined) }) diff --git a/yarn.lock b/yarn.lock index a3e4bf33c..4c9989368 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3155,13 +3155,6 @@ css-initials@^0.3.1: resolved "https://registry.yarnpkg.com/css-initials/-/css-initials-0.3.1.tgz#0406d78e586fd12b9984a3f7d8a87fcbb2073208" integrity sha512-fkshKv9vV8AmcxkAWVQ9DmEAKiqe09GHdnFaXecp0NIfsGnXIHVJAHfsxdRy9KXV0/KiWdjBqrCYto2fYIO4xQ== -css-jss@^10.0.0-alpha.17: - version "10.0.0-alpha.17" - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.0.0-alpha.17" - jss-preset-default "10.0.0-alpha.17" - css-vendor@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.1.tgz#1bfaad119b545287f358bdc62875c26d44b6aae0"