react_webpack_rails 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/js/.eslintrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "airbnb",
3
+ "globals": {
4
+ "__RWR_ENV__": true
5
+ },
6
+ "env": {
7
+ "mocha": true
8
+ },
9
+ "rules": {
10
+ func-names: 0
11
+ }
12
+ }
data/js/src/env.js ADDED
@@ -0,0 +1 @@
1
+ export default (typeof __RWR_ENV__ !== 'undefined') ? __RWR_ENV__ : {};
data/js/src/index.js ADDED
@@ -0,0 +1,30 @@
1
+ import env from './env';
2
+ import nodes from './nodes';
3
+ import integrationsManager from './integrations-manager';
4
+ import react from './integrations/react';
5
+ import reactRouter from './integrations/react-router';
6
+ import version from './version';
7
+
8
+ export { react as react };
9
+ export { nodes as nodes };
10
+ export { integrationsManager as integrationsManager };
11
+ export { env as env };
12
+ export { reactRouter as reactRouter };
13
+
14
+ export default {
15
+ version: version,
16
+
17
+ registerComponent: react.registerComponent,
18
+ getComponent: react.getComponent,
19
+ createComponent: react.createComponent,
20
+ renderComponent: react.renderComponent,
21
+ unmountComponent: react.unmountComponent,
22
+
23
+ renderRouter: reactRouter.renderRouter,
24
+ registerRouter: reactRouter.registerRouter,
25
+ unmountRouter: reactRouter.unmountRouter,
26
+ getRouter: reactRouter.getRouter,
27
+
28
+ mountNodes: nodes.mountNodes,
29
+ unmountNodes: nodes.unmountNodes,
30
+ };
@@ -0,0 +1,22 @@
1
+ import reactIntegration from './integrations/react';
2
+ import reactRouterIntegration from './integrations/react-router';
3
+
4
+ class IntegrationsManager {
5
+ constructor() {
6
+ this.integrations = {
7
+ 'react-component': reactIntegration.integrationWrapper,
8
+ 'react-router': reactRouterIntegration.integrationWrapper,
9
+ };
10
+ }
11
+
12
+ get(name) {
13
+ // handle missing one here;
14
+ return this.integrations[name];
15
+ }
16
+
17
+ register(name, integration) {
18
+ this.integrations[name] = integration;
19
+ }
20
+ }
21
+
22
+ export default new IntegrationsManager;
@@ -0,0 +1,48 @@
1
+ import ReactDOM from 'react-dom';
2
+
3
+ class ReactRouterIntegration {
4
+ constructor() {
5
+ this.routers = {};
6
+ this.enabled = false;
7
+ this.registerRouter = this.registerRouter.bind(this);
8
+ this.getRouter = this.getRouter.bind(this);
9
+ this.renderRouter = this.renderRouter.bind(this);
10
+ }
11
+
12
+ registerRouter(name, route) {
13
+ this.routers[name] = route;
14
+ }
15
+
16
+ getRouter(name) {
17
+ return this.routers[name];
18
+ }
19
+
20
+ renderRouter(name, node) {
21
+ if (this.enabled === true) {
22
+ throw new Error(
23
+ `Error when rendering ${name}\n\trenderRouter: can't render more than one router.`
24
+ );
25
+ }
26
+ this.enabled = true;
27
+ ReactDOM.render(this.getRouter(name), node);
28
+ }
29
+
30
+ unmountRouter(node) {
31
+ ReactDOM.unmountComponentAtNode(node);
32
+ this.enabled = false;
33
+ }
34
+
35
+ get integrationWrapper() {
36
+ return {
37
+ mount: function _mount(node, payload) {
38
+ this.renderRouter(payload.name, node);
39
+ }.bind(this),
40
+
41
+ unmount: function _unmount(node) {
42
+ this.unmountRouter(node);
43
+ }.bind(this),
44
+ };
45
+ }
46
+ }
47
+
48
+ export default new ReactRouterIntegration;
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ class ReactIntegration {
5
+ constructor() {
6
+ this.components = {};
7
+ this.registerComponent = this.registerComponent.bind(this);
8
+ this.getComponent = this.getComponent.bind(this);
9
+ this.createComponent = this.createComponent.bind(this);
10
+ this.renderComponent = this.renderComponent.bind(this);
11
+ this.unmountComponent = this.unmountComponent.bind(this);
12
+ }
13
+
14
+ registerComponent(name, component) {
15
+ this.components[name] = component;
16
+ }
17
+
18
+ getComponent(name) {
19
+ return this.components[name];
20
+ }
21
+
22
+ createComponent(name, props) {
23
+ const constructor = this.getComponent(name);
24
+ return React.createElement(constructor, props);
25
+ }
26
+
27
+ renderComponent(name, props, node) {
28
+ const component = this.createComponent(name, props);
29
+ ReactDOM.render(component, node);
30
+ }
31
+
32
+ unmountComponent(node) {
33
+ ReactDOM.unmountComponentAtNode(node);
34
+ }
35
+
36
+ get integrationWrapper() {
37
+ return {
38
+ mount: function _mount(node, payload) {
39
+ this.renderComponent(payload.name, payload.props, node);
40
+ }.bind(this),
41
+
42
+ unmount: function _unmount(node) {
43
+ this.unmountComponent(node);
44
+ }.bind(this),
45
+ };
46
+ }
47
+ }
48
+
49
+ export default new ReactIntegration;
data/js/src/nodes.js ADDED
@@ -0,0 +1,51 @@
1
+ import IntegrationsManager from './integrations-manager';
2
+
3
+ const ELEMENT_ATTR = 'data-react-element';
4
+ const PAYLOAD_ATTR = 'data-payload';
5
+ const INTEGRATION_NAME_ATTR = 'data-integration-name';
6
+ const OPTIONS_ATTR = 'data-options';
7
+
8
+
9
+ function _findDOMNodes(searchSelector) {
10
+ const selector = searchSelector || '[' + ELEMENT_ATTR + ']';
11
+ return document.querySelectorAll(selector);
12
+ }
13
+
14
+ function _nodeData(node) {
15
+ const rawPayload = node.getAttribute(PAYLOAD_ATTR);
16
+ const rawOptions = node.getAttribute(OPTIONS_ATTR);
17
+
18
+ return {
19
+ payload: rawPayload && JSON.parse(rawPayload),
20
+ options: rawOptions && JSON.parse(rawOptions),
21
+ integrationName: node.getAttribute(INTEGRATION_NAME_ATTR),
22
+ };
23
+ }
24
+
25
+ function _mountNode(node) {
26
+ const data = _nodeData(node);
27
+ const mount = IntegrationsManager.get(data.integrationName).mount;
28
+ if (typeof(mount) === 'function') { mount(node, data.payload); }
29
+ }
30
+
31
+ function _unmountNode(node) {
32
+ const data = _nodeData(node);
33
+ const unmount = IntegrationsManager.get(data.integrationName).unmount;
34
+ if (typeof(unmount) === 'function') { unmount(node, data.payload); }
35
+ }
36
+
37
+ export default {
38
+ mountNodes: function _mountNodes(searchSelector) {
39
+ const nodes = _findDOMNodes(searchSelector);
40
+ for (let i = 0; i < nodes.length; ++i) {
41
+ _mountNode(nodes[i]);
42
+ }
43
+ },
44
+
45
+ unmountNodes: function _unmountNodes(searchSelector) {
46
+ const nodes = _findDOMNodes(searchSelector);
47
+ for (let i = 0; i < nodes.length; ++i) {
48
+ _unmountNode(nodes[i]);
49
+ }
50
+ },
51
+ };
data/js/src/version.js ADDED
@@ -0,0 +1 @@
1
+ export default '0.1.0';
@@ -0,0 +1,10 @@
1
+ import expect from 'expect';
2
+ import subject from '../src/env';
3
+
4
+ describe('env', function () {
5
+ context('when global config does not exist', function () {
6
+ it('sets empty object', function () {
7
+ expect(subject).toEqual({});
8
+ });
9
+ });
10
+ });
@@ -0,0 +1,38 @@
1
+ import expect from 'expect';
2
+ import reactIntegration from '../src/integrations/react';
3
+ import reactRouterIntegration from '../src/integrations/react-router';
4
+ import subject from '../src/integrations-manager';
5
+
6
+ describe('IntegrationsManager', function () {
7
+ describe('.constructor', function () {
8
+ it('by default sets react and react-router integrations', function () {
9
+ expect(subject.integrations['react-component']).toEqual(
10
+ reactIntegration.integrationWrapper
11
+ );
12
+ expect(subject.integrations['react-router']).toEqual(
13
+ reactRouterIntegration.integrationWrapper
14
+ );
15
+ expect(Object.keys(subject.integrations).length).toEqual(2);
16
+ });
17
+ });
18
+
19
+ describe('#get', function () {
20
+ it('returns integration by name', function () {
21
+ expect(subject.get('react-component')).toEqual(
22
+ reactIntegration.integrationWrapper
23
+ );
24
+ });
25
+
26
+ it('returns undefined if name is invalid', function () {
27
+ expect(subject.get('invalidName')).toBe(undefined);
28
+ });
29
+ });
30
+
31
+ describe('#register', function () {
32
+ it('registers integration', function () {
33
+ subject.register('newIntegration', {});
34
+
35
+ expect(subject.integrations.newIntegration).toEqual({});
36
+ });
37
+ });
38
+ });
@@ -0,0 +1,133 @@
1
+ import expect, { spyOn } from 'expect';
2
+ import ReactDOM from 'react-dom';
3
+ import subject from '../../src/integrations/react-router';
4
+
5
+ const HelloRouter = {};
6
+
7
+ describe('ReactIntegration', function () {
8
+ afterEach(function () {
9
+ subject.routers = {};
10
+ subject.enabled = false;
11
+ });
12
+
13
+ describe('.constructor', function () {
14
+ it('intializes empty components dictionary', function () {
15
+ expect(subject.routers).toEqual({});
16
+ });
17
+
18
+ it('by default sets router presence flag to false', function () {
19
+ expect(subject.enabled).toBe(false);
20
+ });
21
+ });
22
+
23
+ describe('#registerRouter', function () {
24
+ it('adds router to the routers storage', function () {
25
+ subject.registerRouter('HelloRouter', HelloRouter);
26
+ expect(subject.routers.HelloRouter).toBe(HelloRouter);
27
+ });
28
+ });
29
+
30
+ describe('#getRouter', function () {
31
+ it('returns route by name', function () {
32
+ subject.registerRouter('HelloRouter', HelloRouter);
33
+ expect(subject.getRouter('HelloRouter')).toBe(HelloRouter);
34
+ });
35
+
36
+ it('returns undefined if route is not found', function () {
37
+ expect(subject.getRouter('HelloRouter')).toBe(undefined);
38
+ });
39
+ });
40
+
41
+ describe('#renderRouter', function () {
42
+ beforeEach(function () {
43
+ subject.registerRouter('HelloRouter', HelloRouter);
44
+ });
45
+
46
+ context('when router is present', function () {
47
+ it('throws an error', function () {
48
+ expect(function () {
49
+ subject.renderRouter('HelloRouter', { node: 'someNode' });
50
+ })
51
+ .withContext(subject.enabled = true)
52
+ .toThrow(/Error when rendering HelloRouter/);
53
+ });
54
+ });
55
+
56
+ context('when router does not exist', function () {
57
+ let ReactDOMSpy;
58
+ const node = { nodeType: 1, nodeName: 'DIV' };
59
+ beforeEach(function () {
60
+ ReactDOMSpy = spyOn(ReactDOM, 'render');
61
+ });
62
+
63
+ afterEach(function () {
64
+ expect.restoreSpies();
65
+ });
66
+
67
+ it('sets router presence flag to true', function () {
68
+ subject.renderRouter('HelloRouter', node);
69
+
70
+ expect(subject.enabled).toBe(true);
71
+ });
72
+
73
+ it('calls ReactDOM renderer once', function () {
74
+ subject.renderRouter('HelloRouter', { nodeType: 1, nodeName: 'DIV' });
75
+
76
+ expect(ReactDOMSpy.calls.length).toEqual(1);
77
+ expect(ReactDOMSpy).toHaveBeenCalledWith(HelloRouter, { nodeType: 1, nodeName: 'DIV' });
78
+ });
79
+ });
80
+ });
81
+
82
+ describe('#unmountRouter', function () {
83
+ let ReactDOMSpy;
84
+ beforeEach(function () {
85
+ ReactDOMSpy = spyOn(ReactDOM, 'unmountComponentAtNode');
86
+ });
87
+
88
+ afterEach(function () {
89
+ expect.restoreSpies();
90
+ });
91
+
92
+ it('unmounts router component at specified node', function () {
93
+ subject.unmountRouter({ node: 'someNode' });
94
+
95
+ expect(ReactDOMSpy.calls.length).toEqual(1);
96
+ expect(ReactDOMSpy).toHaveBeenCalledWith({ node: 'someNode' });
97
+ });
98
+
99
+ it('allows to add new router', function () {
100
+ subject.unmountRouter({ node: 'someNode' });
101
+ expect(subject.enabled).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('#integrationWrapper', function () {
106
+ const node = { nodeType: 1, nodeName: 'DIV' };
107
+
108
+ describe('function mount', function () {
109
+ it('calls renderComponent', function () {
110
+ const mountSpy = spyOn(subject, 'renderRouter');
111
+
112
+ const payload = { name: 'routerName' };
113
+ subject.integrationWrapper.mount(node, payload);
114
+
115
+ expect(mountSpy.calls.length).toEqual(1);
116
+ expect(mountSpy).toHaveBeenCalledWith(
117
+ 'routerName',
118
+ { nodeType: 1, nodeName: 'DIV' }
119
+ );
120
+ });
121
+ });
122
+
123
+ describe('function unmount', function () {
124
+ it('calls unmountComponent', function () {
125
+ const unmountSpy = spyOn(subject, 'unmountRouter');
126
+ subject.integrationWrapper.unmount(node);
127
+
128
+ expect(unmountSpy.calls.length).toEqual(1);
129
+ expect(unmountSpy).toHaveBeenCalledWith({ nodeType: 1, nodeName: 'DIV' });
130
+ });
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,96 @@
1
+ import expect, { spyOn } from 'expect';
2
+ import React, { PropTypes } from 'react';
3
+ import ReactDOM from 'react-dom';
4
+ import subject from '../../src/integrations/react';
5
+
6
+ class HelloComponent extends React.Component {
7
+ static propTypes() {
8
+ return {
9
+ username: PropTypes.string.isRequired,
10
+ };
11
+ }
12
+
13
+ render() {
14
+ return (<div>Hello World! {this.props.username}</div>);
15
+ }
16
+ }
17
+
18
+ describe('ReactIntegration', function () {
19
+ afterEach(function () {
20
+ subject.components = {};
21
+ });
22
+
23
+ describe('.constructor', function () {
24
+ it('intializes empty components dictionary', function () {
25
+ expect(subject.components).toEqual({});
26
+ });
27
+ });
28
+
29
+ describe('#registerComponent', function () {
30
+ it('adds component to the components storage', function () {
31
+ subject.registerComponent('HelloWorld', HelloComponent);
32
+ expect(subject.components.HelloWorld).toBe(HelloComponent);
33
+ });
34
+ });
35
+
36
+ describe('#getComponent', function () {
37
+ it('returns component by name', function () {
38
+ subject.registerComponent('HelloWorld', HelloComponent);
39
+ expect(subject.getComponent('HelloWorld')).toBe(HelloComponent);
40
+ });
41
+
42
+ it('returns undefined if component is not found', function () {
43
+ expect(subject.getComponent('HelloWorld')).toBe(undefined);
44
+ });
45
+ });
46
+
47
+ describe('#createComponent', function () {
48
+ it('creates component with given props', function () {
49
+ subject.registerComponent('HelloWorld', HelloComponent);
50
+ const component = subject.createComponent('HelloWorld', { username: 'testUser' });
51
+
52
+ expect(component.props).toEqual({ username: 'testUser' });
53
+ expect(component.type).toBe(HelloComponent);
54
+ });
55
+ });
56
+
57
+ describe('#unmountComponent', function () {
58
+ it('unmount component at specified node', function () {
59
+ const node = { nodeType: 1, nodeName: 'DIV' };
60
+ const unmountSpy = spyOn(ReactDOM, 'unmountComponentAtNode');
61
+ subject.unmountComponent(node);
62
+
63
+ expect(unmountSpy.calls.length).toEqual(1);
64
+ expect(unmountSpy).toHaveBeenCalledWith({ nodeType: 1, nodeName: 'DIV' });
65
+ });
66
+ });
67
+
68
+ describe('#integrationWrapper', function () {
69
+ const node = { nodeType: 1, nodeName: 'DIV' };
70
+
71
+ describe('function mount', function () {
72
+ it('calls renderComponent', function () {
73
+ const payload = { name: 'componentName', props: { username: 'testUser' } };
74
+ const mountSpy = spyOn(subject, 'renderComponent');
75
+ subject.integrationWrapper.mount(node, payload);
76
+
77
+ expect(mountSpy.calls.length).toEqual(1);
78
+ expect(mountSpy).toHaveBeenCalledWith(
79
+ 'componentName',
80
+ { username: 'testUser' },
81
+ { nodeType: 1, nodeName: 'DIV' }
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('function unmount', function () {
87
+ it('calls unmountComponent', function () {
88
+ const unmountSpy = spyOn(subject, 'unmountComponent');
89
+ subject.integrationWrapper.unmount(node);
90
+
91
+ expect(unmountSpy.calls.length).toEqual(1);
92
+ expect(unmountSpy).toHaveBeenCalledWith({ nodeType: 1, nodeName: 'DIV' });
93
+ });
94
+ });
95
+ });
96
+ });