Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dynamic elements #462

Merged
merged 5 commits into from Jul 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
@@ -1,4 +1,4 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
7 changes: 6 additions & 1 deletion src/babel.js
Expand Up @@ -40,11 +40,16 @@ export default function({ types: t }) {
state.ignoreClosing = 0
}

const tag = path.get('name')

if (
name &&
name !== 'style' &&
name !== STYLE_COMPONENT &&
name.charAt(0) !== name.charAt(0).toUpperCase()
(name.charAt(0) !== name.charAt(0).toUpperCase() ||
Object.values(path.scope.bindings).some(binding =>
binding.referencePaths.some(r => r === tag)
))
) {
if (state.className) {
addClassName(path, state.className)
Expand Down
80 changes: 43 additions & 37 deletions test/__snapshots__/attribute.js.snap
Expand Up @@ -100,41 +100,47 @@ exports[`rewrites className 1`] = `
"var _this = this;

import _JSXStyle from \\"styled-jsx/style\\";
export default (() => <div className={\\"jsx-2886504620\\"}>
<div {...test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.className != null && test.test.className || \\"test\\")} />
<div {...test.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.test.className != null && test.test.test.className || \\"test\\")} />
<div {..._this.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (_this.test.test.className != null && _this.test.test.className || \\"test\\")} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\${true ? ' test2' : ''}\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ('test ' + test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (['test', 'test2'].join(' ') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (true && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ((test ? 'test' : null) || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && test('test') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (undefined || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (null || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (false || \\"\\")} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || 'test')} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \`test \${test ? 'test' : ''}\`)} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && 'test' || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && test2('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<_JSXStyle styleId={\\"2886504620\\"} css={\\"div.jsx-2886504620{color:red;}\\"} />
</div>);"
export default (() => {
const Element = 'div';
return <div className={\\"jsx-2886504620\\"}>
<div {...test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.className != null && test.test.className || \\"test\\")} />
<div {...test.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.test.className != null && test.test.test.className || \\"test\\")} />
<div {..._this.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (_this.test.test.className != null && _this.test.test.className || \\"test\\")} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\${true ? ' test2' : ''}\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ('test ' + test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (['test', 'test2'].join(' ') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (true && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ((test ? 'test' : null) || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && test('test') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (undefined || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (null || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (false || \\"\\")} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || 'test')} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \`test \${test ? 'test' : ''}\`)} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && 'test' || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && test2('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<Element className={\\"jsx-2886504620\\"} />
<Element className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<Element {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<_JSXStyle styleId={\\"2886504620\\"} css={\\"div.jsx-2886504620{color:red;}\\"} />
</div>;
});"
`;
14 changes: 14 additions & 0 deletions test/__snapshots__/external.js.snap
Expand Up @@ -148,6 +148,20 @@ export default {
};"
`;

exports[`use external stylesheet and dynamic element 1`] = `
"import _JSXStyle from \\"styled-jsx/style\\";
import styles from './styles2';

export default (({ level = 1 }) => {
const Element = \`h\${level}\`;

return <Element className={\`jsx-\${styles.__scopedHash}\` + \\" \\" + \\"root\\"}>
<p className={\`jsx-\${styles.__scopedHash}\`}>dynamic element</p>
<_JSXStyle styleId={styles.__scopedHash} css={styles.__scoped} />
</Element>;
});"
`;

exports[`use external stylesheets (global only) 1`] = `
"import _JSXStyle from 'styled-jsx/style';
import styles, { foo as styles3 } from './styles';
Expand Down
53 changes: 53 additions & 0 deletions test/__snapshots__/index.js.snap
Expand Up @@ -103,6 +103,59 @@ class Test extends React.Component {
}"
`;

exports[`works with dynamic element 1`] = `
"import _JSXStyle from \\"styled-jsx/style\\";
export default (({ level = 1 }) => {
const Element = \`h\${level}\`;

return <Element className={\\"jsx-1253978709\\" + \\" \\" + \\"root\\"}>
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</Element>;
});

export const TestLowerCase = ({ level = 1 }) => {
const element = \`h\${level}\`;

return <element className={\\"jsx-1253978709\\" + \\" \\" + \\"root\\"}>
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</element>;
};

const Element2 = 'div';
export const Test2 = () => {
return <Element2 className=\\"root\\">
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</Element2>;
};"
`;

exports[`works with dynamic element in class 1`] = `
"import _JSXStyle from 'styled-jsx/style';
export default class {
render() {
const Element = 'div';

return <Element className={'jsx-1800172487' + ' ' + 'root'}>
<p className={\\"jsx-1800172487\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1800172487\\"} css={\\".root.jsx-1800172487{background:red;}\\"} />
</Element>;
}
}

const Element2 = 'div';
export const Test2 = class {
render() {
return <Element2 className=\\"root\\">
<p className={\\"jsx-1800172487\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1800172487\\"} css={\\".root.jsx-1800172487{background:red;}\\"} />
</Element2>;
}
};"
`;

exports[`works with expressions in template literals 1`] = `
"import _JSXStyle from 'styled-jsx/style';
const darken = c => c;
Expand Down
5 changes: 5 additions & 0 deletions test/external.js
Expand Up @@ -88,3 +88,8 @@ test('injects JSXStyle for nested scope', async t => {
`)
t.snapshot(code)
})

test('use external stylesheet and dynamic element', async t => {
const { code } = await transform('./fixtures/dynamic-element-external.js')
t.snapshot(code)
})
84 changes: 45 additions & 39 deletions test/fixtures/attribute-generation-classname-rewriting.js
@@ -1,39 +1,45 @@
export default () => (
<div>
<div className="test" {...test.test} />
<div className="test" {...test.test.test} />
<div className="test" {...this.test.test} />
<div data-test="test" />
<div className="test" />
<div className={'test'} />
<div className={`test`} />
<div className={`test${true ? ' test2' : ''}`} />
<div className={'test ' + test} />
<div className={['test', 'test2'].join(' ')} />
<div className={true && 'test'} />
<div className={test ? 'test' : null} />
<div className={test} />
<div className={test && 'test'} />
<div className={test && test('test')} />
<div className={undefined} />
<div className={null} />
<div className={false} />
<div className={'test'} data-test />
<div data-test className={'test'} />
<div className={'test'} data-test="test" />
<div className={'test'} {...props} />
<div className={'test'} {...props} {...rest} />
<div className={`test ${test ? 'test' : ''}`} {...props} />
<div className={test && test('test')} {...props} />
<div className={test && test('test') && 'test'} {...props} />
<div className={test && test('test') && test2('test')} {...props} />
<div {...props} className={'test'} />
<div {...props} {...rest} className={'test'} />
<div {...props} className={'test'} {...rest} />
<div {...props} />
<div {...props} {...rest} />
<div {...props} data-foo {...rest} />
<div {...props} className={'test'} data-foo {...rest} />
<style jsx>{'div { color: red }'}</style>
</div>
)
export default () => {
const Element = 'div'
return (
<div>
<div className="test" {...test.test} />
<div className="test" {...test.test.test} />
<div className="test" {...this.test.test} />
<div data-test="test" />
<div className="test" />
<div className={'test'} />
<div className={`test`} />
<div className={`test${true ? ' test2' : ''}`} />
<div className={'test ' + test} />
<div className={['test', 'test2'].join(' ')} />
<div className={true && 'test'} />
<div className={test ? 'test' : null} />
<div className={test} />
<div className={test && 'test'} />
<div className={test && test('test')} />
<div className={undefined} />
<div className={null} />
<div className={false} />
<div className={'test'} data-test />
<div data-test className={'test'} />
<div className={'test'} data-test="test" />
<div className={'test'} {...props} />
<div className={'test'} {...props} {...rest} />
<div className={`test ${test ? 'test' : ''}`} {...props} />
<div className={test && test('test')} {...props} />
<div className={test && test('test') && 'test'} {...props} />
<div className={test && test('test') && test2('test')} {...props} />
<div {...props} className={'test'} />
<div {...props} {...rest} className={'test'} />
<div {...props} className={'test'} {...rest} />
<div {...props} />
<div {...props} {...rest} />
<div {...props} data-foo {...rest} />
<div {...props} className={'test'} data-foo {...rest} />
<Element />
<Element className="test" />
<Element {...props} />
<style jsx>{'div { color: red }'}</style>
</div>
)
}
32 changes: 32 additions & 0 deletions test/fixtures/dynamic-element-class.js
@@ -0,0 +1,32 @@
export default class {
render() {
const Element = 'div'

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element>
)
}
}

const Element2 = 'div'
export const Test2 = class {
render() {
return (
<Element2 className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element2>
)
}
}
12 changes: 12 additions & 0 deletions test/fixtures/dynamic-element-external.js
@@ -0,0 +1,12 @@
import styles from './styles2'

export default ({ level = 1 }) => {
const Element = `h${level}`

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{styles}</style>
</Element>
)
}
43 changes: 43 additions & 0 deletions test/fixtures/dynamic-element.js
@@ -0,0 +1,43 @@
export default ({ level = 1 }) => {
const Element = `h${level}`

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element>
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome can you add a few more tests cases? i.e. a few more combinations to where Element is defined outside of the component, and maybe when it is lowercase too (any edge case you can think of). Also can you add a class component too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Im missing something, but I dont think it will work if Element is defined outside of the component. ( Thats why I added the styled-jsx option )
I´ll add the testcases

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, it won't work (it won't add the className) and we want a snapshot of that! ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


export const TestLowerCase = ({ level = 1 }) => {
const element = `h${level}`

return (
<element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</element>
)
}

const Element2 = 'div'
export const Test2 = () => {
return (
<Element2 className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element2>
)
}
10 changes: 10 additions & 0 deletions test/index.js
Expand Up @@ -82,6 +82,16 @@ test('works with css tagged template literals in the same file', async t => {
t.snapshot(code)
})

test('works with dynamic element', async t => {
const { code } = await transform('./fixtures/dynamic-element.js')
t.snapshot(code)
})

test('works with dynamic element in class', async t => {
const { code } = await transform('./fixtures/dynamic-element-class.js')
t.snapshot(code)
})

test('does not transpile nested style tags', async t => {
const { message } = await t.throws(
transform('./fixtures/nested-style-tags.js')
Expand Down