Skip to content

Commit

Permalink
[Emotion] Add internal service for cloning elements with the css pr…
Browse files Browse the repository at this point in the history
…operty (#5835)

* Add internal service for cloning elements with the `css` property

* Remove as public API/export

* Automatically merge child and parent css properties

* Add component unit test
  • Loading branch information
Constance authored Apr 25, 2022
1 parent 042a260 commit 3a5d461
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/services/theme/clone_element.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/** @jsx jsx */
import React from 'react';
import { css, jsx } from '@emotion/react';
import { render } from 'enzyme';

import { cloneElementWithCss } from './clone_element';

describe('cloneElementWithCss', () => {
const CloningParent: React.FC<any> = ({ children, ...props }) => {
return cloneElementWithCss(children, props);
};

it('correctly renders css on elements that do not already have a `css` property', () => {
const component = render(
<CloningParent css={{ color: 'red' }}>
<div>hello world</div>
</CloningParent>
);

expect(component).toMatchInlineSnapshot(`
<div
class="css-1h3ogp1-component"
>
hello world
</div>
`);
expect(component).toHaveStyleRule('color', 'red');
});

it('combines css properties on cloned elements that already have a `css` property', () => {
const component = render(
<CloningParent css={{ color: 'red' }}>
<div
css={[
css`
background-color: blue;
`,
]}
>
hello world
</div>
</CloningParent>
);

expect(component).toMatchInlineSnapshot(`
<div
class="css-88aly5-component-component-component"
>
hello world
</div>
`);
expect(component).toHaveStyleRule('color', 'red');
expect(component).toHaveStyleRule('background-color', 'blue');
});

it('handles components', () => {
const TestComponent: React.FC = (props) => (
<div {...props} css={{ backgroundColor: 'blue' }}>
hello world
</div>
);

const component = render(
<CloningParent css={{ color: 'red' }}>
<TestComponent css={{ border: '1px solid black' }} />
</CloningParent>
);

expect(component).toMatchInlineSnapshot(`
<div
class="css-1fcrfq4-TestComponent-component-component"
>
hello world
</div>
`);
expect(component).toHaveStyleRule('color', 'red');
expect(component).toHaveStyleRule('background-color', 'blue');
expect(component).toHaveStyleRule('border', '1px solid black');
});

it('does nothing if no css property is set', () => {
const component = render(
<CloningParent className="test">
<div>hello world</div>
</CloningParent>
);

expect(component).toMatchInlineSnapshot(`
<div
class="test"
>
hello world
</div>
`);
expect(component).not.toHaveStyleRule('color', 'red');
});
});
38 changes: 38 additions & 0 deletions src/services/theme/clone_element.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { jsx } from '@emotion/react';

/**
* React.cloneElement does not work if the cloned element does not already have the
* `css` prop - as a result, we need to use `jsx()` to manually clone the element
* See https://github.com/emotion-js/emotion/issues/1404
*
* NOTE: We're still using/testing this utility internally, so this is not yet a public API
*/
export const cloneElementWithCss = (
element: any,
props: any
): React.ReactElement => {
const clonedElement =
element.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__ || element.type; // EMOTION_TYPE handles non-React elements (native JSX/HTML nodes)

const clonedProps = {
key: element.key,
ref: element.ref,
...element.props,
...props,
};

if (props.css || element.props.css) {
clonedProps.css = [element.props.css, props.css];
}

return jsx(clonedElement, clonedProps);
};

0 comments on commit 3a5d461

Please sign in to comment.