react-ab-test
Version:
A/B testing React components and debug tools. Isomorphic with a simple, universal interface. Well documented and lightweight. Tested in popular browsers and Node.js. Includes helpers for Mixpanel and Segment.com.
166 lines (163 loc) • 7.77 kB
JSX
import React from 'react';
import ReactDOM from 'react-dom';
import emitter from "./emitter";
import {canUseDOM} from 'fbjs/lib/ExecutionEnvironment';
if(process.env.NODE_ENV === "production" || !canUseDOM) {
module.exports = {
enable() {},
disable() {}
}
} else {
let style = null;
function attachStyleSheet() {
style = document.createElement("style");
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
function addCSSRule(selector, rules) {
if("insertRule" in style.sheet) {
style.sheet.insertRule(selector + "{" + rules + "}", 0);
} else if("addRule" in style.sheet) {
style.sheet.addRule(selector, rules, 0);
}
}
addCSSRule("#pushtell-debugger", "z-index: 25000");
addCSSRule("#pushtell-debugger", "position: fixed");
addCSSRule("#pushtell-debugger", "transform: translateX(-50%)");
addCSSRule("#pushtell-debugger", "bottom: 0");
addCSSRule("#pushtell-debugger", "left: 50%");
addCSSRule("#pushtell-debugger ul", "margin: 0");
addCSSRule("#pushtell-debugger ul", "padding: 0 0 0 20px");
addCSSRule("#pushtell-debugger li", "margin: 0");
addCSSRule("#pushtell-debugger li", "padding: 0");
addCSSRule("#pushtell-debugger li", "font-size: 14px");
addCSSRule("#pushtell-debugger li", "line-height: 14px");
addCSSRule("#pushtell-debugger input", "float: left");
addCSSRule("#pushtell-debugger input", "margin: 0 10px 0 0");
addCSSRule("#pushtell-debugger input", "padding: 0");
addCSSRule("#pushtell-debugger input", "cursor: pointer");
addCSSRule("#pushtell-debugger label", "color: #999999");
addCSSRule("#pushtell-debugger label", "margin: 0 0 10px 0");
addCSSRule("#pushtell-debugger label", "cursor: pointer");
addCSSRule("#pushtell-debugger label", "font-weight: normal");
addCSSRule("#pushtell-debugger label.active", "color: #000000");
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "font-size: 16px");
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "color: #000000");
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "margin: 0 0 10px 0");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "font-size: 10px");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "color: #999999");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "text-align: center");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "margin: 10px -40px 0 -10px");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "border-top: 1px solid #b3b3b3");
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "padding: 10px 10px 5px 10px");
addCSSRule("#pushtell-debugger .pushtell-handle", "cursor: pointer");
addCSSRule("#pushtell-debugger .pushtell-handle", "padding: 5px 10px 5px 10px");
addCSSRule("#pushtell-debugger .pushtell-panel", "padding: 15px 40px 5px 10px");
addCSSRule("#pushtell-debugger .pushtell-container", "font-size: 11px");
addCSSRule("#pushtell-debugger .pushtell-container", "background-color: #ebebeb");
addCSSRule("#pushtell-debugger .pushtell-container", "color: #000000");
addCSSRule("#pushtell-debugger .pushtell-container", "box-shadow: 0px 0 5px rgba(0, 0, 0, 0.1)");
addCSSRule("#pushtell-debugger .pushtell-container", "border-top: 1px solid #b3b3b3");
addCSSRule("#pushtell-debugger .pushtell-container", "border-left: 1px solid #b3b3b3");
addCSSRule("#pushtell-debugger .pushtell-container", "border-right: 1px solid #b3b3b3");
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-left-radius: 2px");
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-right-radius: 2px");
addCSSRule("#pushtell-debugger .pushtell-close", "cursor: pointer");
addCSSRule("#pushtell-debugger .pushtell-close", "font-size: 16px");
addCSSRule("#pushtell-debugger .pushtell-close", "font-weight: bold");
addCSSRule("#pushtell-debugger .pushtell-close", "color: #CC0000");
addCSSRule("#pushtell-debugger .pushtell-close", "position: absolute");
addCSSRule("#pushtell-debugger .pushtell-close", "top: 0px");
addCSSRule("#pushtell-debugger .pushtell-close", "right: 7px");
addCSSRule("#pushtell-debugger .pushtell-close:hover", "color: #FF0000");
addCSSRule("#pushtell-debugger .pushtell-close, #pushtell-debugger label", "transition: all .25s");
}
function removeStyleSheet() {
if(style !== null){
document.head.removeChild(style);
style = null;
}
}
const Debugger = React.createClass({
displayName: "Pushtell.Debugger",
getInitialState(){
return {
experiments: emitter.getActiveExperiments(),
visible: false
};
},
toggleVisibility() {
this.setState({
visible: !this.state.visible
});
},
updateExperiments(){
this.setState({
experiments: emitter.getActiveExperiments()
});
},
setActiveVariant(experimentName, variantName) {
emitter.setActiveVariant(experimentName, variantName);
},
componentWillMount(){
this.activeSubscription = emitter.addListener("active", this.updateExperiments);
this.inactiveSubscription = emitter.addListener("inactive", this.updateExperiments);
},
componentWillUnmount(){
this.activeSubscription.remove();
this.inactiveSubscription.remove();
},
render(){
var experimentNames = Object.keys(this.state.experiments);
if(this.state.visible) {
return <div className="pushtell-container pushtell-panel">
<div className="pushtell-close" onClick={this.toggleVisibility}>×</div>
{experimentNames.map(experimentName => {
var variantNames = Object.keys(this.state.experiments[experimentName]);
if(variantNames.length === 0) {
return;
}
return <div className="pushtell-experiment" key={experimentName}>
<div className="pushtell-experiment-name">{experimentName}</div>
<ul>
{variantNames.map(variantName => {
return <li key={variantName}>
<label className={this.state.experiments[experimentName][variantName] ? "active" : null} onClick={this.setActiveVariant.bind(this, experimentName, variantName)}>
<input type="radio" name={experimentName} value={variantName} defaultChecked={this.state.experiments[experimentName][variantName]} />
{variantName}
</label>
</li>
})}
</ul>
</div>;
})}
<div className="pushtell-production-build-note">This panel is hidden on production builds.</div>
</div>;
} else if(experimentNames.length > 0){
return <div className="pushtell-container pushtell-handle" onClick={this.toggleVisibility}>
{experimentNames.length} Active Experiment{experimentNames.length > 1 ? "s" : ""}
</div>;
} else {
return null;
}
}
});
module.exports = {
enable() {
attachStyleSheet();
let body = document.getElementsByTagName('body')[0];
let container = document.createElement('div');
container.id = 'pushtell-debugger';
body.appendChild(container);
ReactDOM.render(<Debugger />, container);
},
disable() {
removeStyleSheet();
let body = document.getElementsByTagName('body')[0];
let container = document.getElementById('pushtell-debugger');
if(container) {
ReactDOM.unmountComponentAtNode(container);
body.removeChild(container);
}
}
}
}