Skip to content

Commit

Permalink
Merge pull request jenseng#31 from jwodicka/master
Browse files Browse the repository at this point in the history
Make ComponentInterpolator a pure functional component
  • Loading branch information
jenseng authored Jul 20, 2017
2 parents 85b01f6 + 9bf9352 commit bbc8c47
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 180 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ rules:
quotes: 'off'
radix: error
"react/jsx-uses-react": 1
"react/jsx-uses-vars": 1
require-jsdoc: 'off'
require-yield: error
rest-spread-spacing: error
Expand Down
148 changes: 77 additions & 71 deletions ComponentInterpolator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var React = require('react');
var invariant = require('invariant');
var { string, object } = React.PropTypes;
var { string, object } = require('prop-types');

var WRAPPER_PATTERN = /(\*+)/;
var PLACEHOLDER_PATTERN = /(%\{.*?\})/;
Expand Down Expand Up @@ -47,79 +47,85 @@ var getInjectIndex = function(children, containerName) {
return index;
};

var ComponentInterpolator = React.createClass({
propTypes: {
string: string.isRequired,
wrappers: object
},

inferChildren() {
var tokens = (this.props.string || '').split(WRAPPER_PATTERN);
this.keyCounter = 0;
var inferredChildren = this.interpolateAllComponents(tokens);

var currentChildren = toArray(this.props.children);

var index = getInjectIndex(currentChildren, '<ComponentInterpolator>');
invariant(index >= 0, '<ComponentInterpolator> must have a "$1" text child"');
currentChildren.splice.apply(currentChildren, [index, 1].concat(inferredChildren));
return currentChildren;
},

interpolateAllComponents(tokens, eof) {
var token, child;
var children = [];
var wrappers = this.props.wrappers || {};
while (tokens.length) {
token = tokens.shift();
if (token === eof) break;
if (token.match(WRAPPER_PATTERN)) {
invariant(
child = wrappers[token],
`<ComponentInterpolator> expected '${token}' wrapper, none found`
);

child = injectNewDescendants(
child,
this.interpolateAllComponents(tokens, token),
{ key: this.keyCounter++ },
true
);
children.push(child);
}
else {
children.push.apply(children, this.interpolatePlaceholders(token));
}
var Counter = function() {
this.count = 0;
this.next = function() {
return this.count++;
};
};

var inferChildren = function(props) {
var tokens = (props.string || '').split(WRAPPER_PATTERN);
var inferredChildren = interpolateAllComponents(tokens, props);

var currentChildren = toArray(props.children);

var index = getInjectIndex(currentChildren, '<ComponentInterpolator>');
invariant(index >= 0, '<ComponentInterpolator> must have a "$1" text child"');
currentChildren.splice.apply(currentChildren, [index, 1].concat(inferredChildren));
return currentChildren;
};

var interpolateAllComponents = function(tokens, props, keyCounter, eof) {
var token, child;
var children = [];
var wrappers = props.wrappers || {};
if (!keyCounter) {
keyCounter = new Counter();
}
while (tokens.length) {
token = tokens.shift();
if (token === eof) break;
if (token.match(WRAPPER_PATTERN)) {
invariant(
child = wrappers[token],
`<ComponentInterpolator> expected '${token}' wrapper, none found`
);

child = injectNewDescendants(
child,
interpolateAllComponents(tokens, props, keyCounter, token),
{ key: keyCounter.next() },
true
);
children.push(child);
}
return children;
},

interpolatePlaceholders(string) {
var token, child;
var tokens = string.split(PLACEHOLDER_PATTERN);
var children = [];
while (tokens.length) {
token = tokens.shift();
if (token.match(PLACEHOLDER_PATTERN)) {
token = token.slice(2, -1);
invariant(
this.props.hasOwnProperty(token),
`<ComponentInterpolator> expected '${token}' placeholder value, none found`
);
child = this.props[token];
child = child && child.type ? React.cloneElement(child, {key: this.keyCounter++}) : child;
children.push(child);
} else {
children.push(token);
}
else {
children.push.apply(children, interpolatePlaceholders(token, props, keyCounter));
}
return children;
},
}
return children;
};

render() {
return React.createElement('span', {}, this.inferChildren());
var interpolatePlaceholders = function(string, props, keyCounter) {
var token, child;
var tokens = string.split(PLACEHOLDER_PATTERN);
var children = [];
while (tokens.length) {
token = tokens.shift();
if (token.match(PLACEHOLDER_PATTERN)) {
token = token.slice(2, -1);
invariant(
props.hasOwnProperty(token),
`<ComponentInterpolator> expected '${token}' placeholder value, none found`
);
child = props[token];
child = child && child.type ? React.cloneElement(child, {key: keyCounter.next()}) : child;
children.push(child);
} else {
children.push(token);
}
}
});
return children;
};

module.exports = ComponentInterpolator;
var ComponentInterpolator = function(props) {
return React.createElement('span', {}, inferChildren(props));
};

ComponentInterpolator.propTypes = {
string: string.isRequired,
wrappers: object
};

module.exports = ComponentInterpolator;
48 changes: 21 additions & 27 deletions __tests__/ComponentInterpolator.test.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,49 @@
var subjector = require('../test_utils/subjector');
var Subject = subjector(__dirname + '/../ComponentInterpolator');
var React = require('react');
var ReactDOM = require('react-dom');

var removeNoise = function(string) {
return string.replace(/<!--.*?-->/g, '')
.replace(/ data-reactid=".*?"/g, '');
};
var { shallow } = require('enzyme');
var CI = require('../ComponentInterpolator');

describe('ComponentInterpolator', function() {
it('renders', function() {
var subject = Subject({
string: 'Hello World',
wrappers: {}
}, ["$1"]);
expect(subject.isMounted()).toEqual(true);
expect(ReactDOM.findDOMNode(subject).textContent).toEqual('Hello World');
var component = <CI string='Hello World'>$1</CI>;
expect(shallow(component).text()).toEqual('Hello World');
});

it('wraps content in a <span>', function() {
var component = <CI string='Howdy!'>$1</CI>;
expect(shallow(component).html()).toEqual('<span>Howdy!</span>');
});

it('escapes html in the string', function() {
var subject = Subject({
string: 'My favorite tag is <script />',
wrappers: {}
}, ["$1"]);
expect(ReactDOM.findDOMNode(subject).textContent).toEqual('My favorite tag is <script />');
var component = <CI string='My favorite tag is <script />'>$1</CI>;
expect(shallow(component).text()).toEqual('My favorite tag is <script />');
});

it('interpolates wrapper components', function() {
var subject = Subject({
var props = {
string: 'Ohai, Jane, click *here* right ***now **please** ***',
wrappers: {
'*': <a href='/'><img />$1</a>,
'**': <i>$1</i>,
'***': <b><em>$1</em></b>
}
}, [<hr />, "$1"]);
expect(removeNoise(ReactDOM.findDOMNode(subject).innerHTML)).toEqual(
'<hr>Ohai, Jane, click <a href="/"><img>here</a> right <b><em>now <i>please</i> </em></b>'
};
var component = <CI {...props}><hr />$1</CI>;
expect(shallow(component).html()).toEqual(
'<span><hr/>Ohai, Jane, click <a href="/"><img/>here</a> right <b><em>now <i>please</i> </em></b></span>'
);
});

it('interpolates placeholder components', function() {
var subject = Subject({
var props = {
string: 'Hi %{user} (%{user_id}), create %{count} new accounts',
wrappers: {},
user: "Jane",
user_id: 0,
count: <input />
}, ["$1"]);
expect(removeNoise(ReactDOM.findDOMNode(subject).innerHTML)).toEqual(
'Hi Jane (0), create <input> new accounts'
};
var component = <CI {...props}>$1</CI>;
expect(shallow(component).html()).toEqual(
'<span>Hi Jane (0), create <input/> new accounts</span>'
);
});
});
128 changes: 68 additions & 60 deletions dist/ComponentInterpolator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

var React = require('react');
var invariant = require('invariant');
var _React$PropTypes = React.PropTypes;
var string = _React$PropTypes.string;
var object = _React$PropTypes.object;

var _require = require('prop-types'),
string = _require.string,
object = _require.object;

var WRAPPER_PATTERN = /(\*+)/;
var PLACEHOLDER_PATTERN = /(%\{.*?\})/;
Expand Down Expand Up @@ -53,65 +53,73 @@ var getInjectIndex = function getInjectIndex(children, containerName) {
return index;
};

var ComponentInterpolator = React.createClass({
displayName: 'ComponentInterpolator',

propTypes: {
string: string.isRequired,
wrappers: object
},

inferChildren: function inferChildren() {
var tokens = (this.props.string || '').split(WRAPPER_PATTERN);
this.keyCounter = 0;
var inferredChildren = this.interpolateAllComponents(tokens);

var currentChildren = toArray(this.props.children);

var index = getInjectIndex(currentChildren, '<ComponentInterpolator>');
invariant(index >= 0, '<ComponentInterpolator> must have a "$1" text child"');
currentChildren.splice.apply(currentChildren, [index, 1].concat(inferredChildren));
return currentChildren;
},
interpolateAllComponents: function interpolateAllComponents(tokens, eof) {
var token, child;
var children = [];
var wrappers = this.props.wrappers || {};
while (tokens.length) {
token = tokens.shift();
if (token === eof) break;
if (token.match(WRAPPER_PATTERN)) {
invariant(child = wrappers[token], '<ComponentInterpolator> expected \'' + token + '\' wrapper, none found');

child = injectNewDescendants(child, this.interpolateAllComponents(tokens, token), { key: this.keyCounter++ }, true);
children.push(child);
} else {
children.push.apply(children, this.interpolatePlaceholders(token));
}
var Counter = function Counter() {
this.count = 0;
this.next = function () {
return this.count++;
};
};

var inferChildren = function inferChildren(props) {
var tokens = (props.string || '').split(WRAPPER_PATTERN);
var inferredChildren = interpolateAllComponents(tokens, props);

var currentChildren = toArray(props.children);

var index = getInjectIndex(currentChildren, '<ComponentInterpolator>');
invariant(index >= 0, '<ComponentInterpolator> must have a "$1" text child"');
currentChildren.splice.apply(currentChildren, [index, 1].concat(inferredChildren));
return currentChildren;
};

var interpolateAllComponents = function interpolateAllComponents(tokens, props, keyCounter, eof) {
var token, child;
var children = [];
var wrappers = props.wrappers || {};
if (!keyCounter) {
keyCounter = new Counter();
}
while (tokens.length) {
token = tokens.shift();
if (token === eof) break;
if (token.match(WRAPPER_PATTERN)) {
invariant(child = wrappers[token], '<ComponentInterpolator> expected \'' + token + '\' wrapper, none found');

child = injectNewDescendants(child, interpolateAllComponents(tokens, props, keyCounter, token), { key: keyCounter.next() }, true);
children.push(child);
} else {
children.push.apply(children, interpolatePlaceholders(token, props, keyCounter));
}
return children;
},
interpolatePlaceholders: function interpolatePlaceholders(string) {
var token, child;
var tokens = string.split(PLACEHOLDER_PATTERN);
var children = [];
while (tokens.length) {
token = tokens.shift();
if (token.match(PLACEHOLDER_PATTERN)) {
token = token.slice(2, -1);
invariant(this.props.hasOwnProperty(token), '<ComponentInterpolator> expected \'' + token + '\' placeholder value, none found');
child = this.props[token];
child = child && child.type ? React.cloneElement(child, { key: this.keyCounter++ }) : child;
children.push(child);
} else {
children.push(token);
}
}
return children;
};

var interpolatePlaceholders = function interpolatePlaceholders(string, props, keyCounter) {
var token, child;
var tokens = string.split(PLACEHOLDER_PATTERN);
var children = [];
while (tokens.length) {
token = tokens.shift();
if (token.match(PLACEHOLDER_PATTERN)) {
token = token.slice(2, -1);
invariant(props.hasOwnProperty(token), '<ComponentInterpolator> expected \'' + token + '\' placeholder value, none found');
child = props[token];
child = child && child.type ? React.cloneElement(child, { key: keyCounter.next() }) : child;
children.push(child);
} else {
children.push(token);
}
return children;
},
render: function render() {
return React.createElement('span', {}, this.inferChildren());
}
});
return children;
};

var ComponentInterpolator = function ComponentInterpolator(props) {
return React.createElement('span', {}, inferChildren(props));
};

ComponentInterpolator.propTypes = {
string: string.isRequired,
wrappers: object
};

module.exports = ComponentInterpolator;
Loading

0 comments on commit bbc8c47

Please sign in to comment.