Skip to content

Commit

Permalink
Added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Wildhoney committed Jun 16, 2019
1 parent 2fb7140 commit 4123700
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 49 deletions.
73 changes: 24 additions & 49 deletions README.md
Expand Up @@ -10,7 +10,8 @@
 
![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square)

* **npm**: `npm i react-shadow --save`
* **npm**: `npm i react-shadow`
* **yarn**: `yarn add react-shadow`
* **Heroku**: [http://react-shadow.herokuapp.com/](http://react-shadow.herokuapp.com/)

![Screenshot](media/screenshot.png)
Expand All @@ -19,65 +20,39 @@

## Getting Started

By using `ReactShadow` you have all the benefits of [Shadow DOM](https://www.w3.org/TR/shadow-dom/) in React.
Creating the [shadow root](https://www.w3.org/TR/shadow-dom/) is as simple as using the default export to construct a shadow root using the node name provided – for example `root.div` would create a `div` as the host element, and a shadow root as its immediate descendant — all of the child elements would then be descendants of the shadow boundary.

```javascript
import ShadowDOM from 'react-shadow';

export default props => {
```jsx
import root from 'react-shadow';
import styles from './styles.css';

export default function Quote {
return (
<ShadowDOM include={['css/core/calendar.css', props.theme]}>
<h1>Calendar for {props.date}</h1>
</ShadowDOM>
<root.div className="quote">
<q>There is strong shadow where there is much light.”</q>
<div class="author">Johann Wolfgang von Goethe.</div>
<style type="text/css">{styles}</style>
</root.div>
);

}
```

In the above example the `h1` element will become the host element with a shadow boundary &mdash; and the two defined CSS documents will be fetched and appended.

## Preventing FOUC
Applying styles requires either applying the styles directly to the component as a string, or importing the CSS documents as a string as part of your build process. You can then append the `style` component directly to your shadow boundary via your component's tree. In [the example](https://github.com/Wildhoney/ReactShadow/tree/master/example) we use the following Webpack configuration to import CSS documents as strings.

As the CSS documents are being fetched over the network, the host element will have a `className` of `resolving` for you to avoid the dreaded [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content). Once **all** of the documents have been attached the `className` will change to `resolved`.

Using the `resolved` class name you could then allow the component to appear once all styles have been applied.

```css
.component {
opacity: 0;
transform: scale(.75);
transition: all .35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.component.resolved {
opacity: 1;
transform: scale(1);
```javascript
{
test: /\.css$/,
loader: ['to-string-loader', 'css-loader']
}
```

## Cached Documents

Oftentimes components share the same documents, however only **one** instance will be fetched due to `memoize` of the [`fetchInclude`](https://github.com/Wildhoney/ReactShadow/blob/master/src/react-shadow.js#L34-L45) function.

## Inline Styles

Instead of defining external CSS documents to fetch, you could choose to add all of the component's styles to the component itself by simply embedding a `style` node in your component. Naturally all styles added this way will be encapsulated within the shadow boundary.
You may pass any props you like to the `root.*` component which will be applied directly to the host element, including event handlers and class names. There are also a handful of options that are used for the `attachShadow` invocation.

```javascript
export default props => {

const styles = `:host { background-color: ${props.theme} }`;

return (
<ShadowDOM>
<div>
<h1>Calendar for {props.date}</h1>
<style type="text/css">{styles}</style>
</div>
</ShadowDOM>
);
}
ShadowRoot.propTypes = {
mode: PropTypes.oneOf(['open', 'closed']),
delegatesFocus: PropTypes.bool,
styleSheets: PropTypes.arrayOf(PropTypes.string),
children: PropTypes.node.isRequired,
};
```

> **Note**: Using inline styles will **not** combine styles into one `style` node.
8 changes: 8 additions & 0 deletions ava.config.js
@@ -0,0 +1,8 @@
export default {
require: [
'@babel/register',
'@babel/polyfill',
'./helpers/enzyme.js',
'./helpers/browser-env.js',
],
};
3 changes: 3 additions & 0 deletions helpers/browser-env.js
@@ -0,0 +1,3 @@
import browserEnv from 'browser-env';

browserEnv();
4 changes: 4 additions & 0 deletions helpers/enzyme.js
@@ -0,0 +1,4 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });
55 changes: 55 additions & 0 deletions src/index.test.js
@@ -0,0 +1,55 @@
import React, { useState } from 'react';
import test from 'ava';
import { mount } from 'enzyme';
import sinon from 'sinon';
import root from './';

test('It should be able to create the shadow boundary;', t => {
const wrapper = mount(<root.div>Hello Adam!</root.div>);
t.truthy(wrapper.getDOMNode().shadowRoot);
t.is(wrapper.getDOMNode().shadowRoot.innerHTML, 'Hello Adam!');
});

test('It should be able to register events in the shadow boundary;', t => {
const spies = { onClick: sinon.spy() };
const wrapper = mount(
<root.div>
<div onClick={() => spies.onClick('Maria')}>Hello Maria</div>
</root.div>,
);
const node = wrapper.getDOMNode().shadowRoot.querySelector('div');
node.click();
t.is(spies.onClick.callCount, 1);
t.true(spies.onClick.calledWith('Maria'));
});

test('It should be able to applie styles to the shadow boundary components;', t => {
const wrapper = mount(
<root.div>
Hello Adam!
<style type="text/css">{`* { border: 1px solid red; }`}</style>
</root.div>,
);
const node = wrapper.getDOMNode().shadowRoot.querySelector('style');
t.is(node.innerHTML, '* { border: 1px solid red; }');
});

test('It should be able re-render the component tree from the event handlers;', t => {
function Name() {
const [name, setName] = useState(null);
return (
<>
{name && <div>Hello {name}!</div>}
<root.section>
<button onClick={() => setName('Adam')}>Change Name</button>
</root.section>
</>
);
}

const wrapper = mount(<Name />);
const node = wrapper.getDOMNode().shadowRoot.querySelector('button');
node.click();
wrapper.update();
t.is(wrapper.find('div').text(), 'Hello Adam!');
});

0 comments on commit 4123700

Please sign in to comment.