From ee4b189ad3699bfc8d310b5663a94059d0c842a3 Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Tue, 15 Dec 2015 10:44:05 -0800 Subject: [PATCH 1/5] Bookmarklet --- README.md | 2 ++ assets/README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++ gulpfile.babel.js | 18 ++++++++++ package.json | 1 + 4 files changed, 110 insertions(+) create mode 100644 assets/README.md diff --git a/README.md b/README.md index 508e6ca..8cdc117 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # AB Tester +An AB testing library that supports weights, callbacks, CSS based tests, persistence, and an interface. To use the interface bookmarklet, drag this link to your bookmarks bar. + ## Developing ```bash npm install # Install dependencies diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..fa4b471 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,89 @@ +# AB Tester + +An AB testing library that supports weights, callbacks, CSS based tests, persistence, and an interface. To use the interface bookmarklet, drag this link to your bookmarks bar. + +## Developing +```bash +npm install # Install dependencies +npm build # Hint and uglify +npm build-watch # Build and watch +npm test # Run tests +``` + +## Usage +```html + + +``` + +## Options +* `name`: Test name (String) +* `data`: Tests to execute: + * `data` is an object whose keys represent test cases to be randomly chosen. + * The keys can be any name that you want. + * The available options for each test are an optional `weight`, a `Number`, and an optional `chosen`, a `Function` +* `chosen`: Function to be executed when any test is chosen + +```javascript +var data = { + testA: { + weight: 1, + chosen: Test.noop + }, + testB: { + weight: 1, + chosen: Test.noop + } +}; +``` + +* `options`: Optional object of misc test options +```javascript +var options = { + persist: true // Set to false to not store test bucket in sessionStorage + active: true // Set to false to disable test execution completely +}; +``` + +## Creating tests +```javascript +new Test('new-homepage', { + control: {}, + test: {} +}, { + persist: false +}); +``` +* Once a test is chosen, two classes will be added to the body tag to help you make style adjustments for the chosen test. + +```html + +``` +```css +.new-homepage.test { + /* Write custom styles for the new homepage test */ +} +``` + + +## License + +**MIT Licensing** + +Copyright (c) 2015 Dollar Shave Club + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/gulpfile.babel.js b/gulpfile.babel.js index aee8706..eca9855 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -1,6 +1,8 @@ import babelify from 'babelify'; +import bookmarklet from 'bookmarklet'; import browserify from 'browserify'; import buffer from 'vinyl-buffer'; +import fs from 'fs'; import gulp from 'gulp'; import rename from 'gulp-rename'; import source from 'vinyl-source-stream'; @@ -24,12 +26,28 @@ gulp.task('default', () => { .pipe(source('test-interface.min.js')) .pipe(buffer()) .pipe(uglify()) + + .pipe(function () { + console.log(arguments); + }) + .pipe(gulp.dest('build')); + }); gulp.task('do-watch', () => { gulp.watch('lib/**/*.js', ['default']); }); +gulp.task("bookmarklet", cb => { + var data = fs.readFileSync('./build/test-interface.min.js', 'utf-8'); + var code = bookmarklet.convert(data, { + + }); + + var readme = fs.readFileSync('./assets/README.md', 'utf-8'); + readme = readme.replace('_BOOKMARKLET_', code); + fs.writeFile('./README.md', readme, cb); +}); gulp.task('watch', ['default', 'do-watch']); diff --git a/package.json b/package.json index e6c54db..adfc665 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "devDependencies": { "babelify": "^7.2.0", + "bookmarklet": "0.0.4", "browserify": "^12.0.1", "chai": "3.2.0", "gulp": "^3.9.0", From 1f822f53fed48917455e34e139eac6078a022330 Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Thu, 17 Dec 2015 14:32:09 -0800 Subject: [PATCH 2/5] localStorage support check --- gulpfile.babel.js | 18 +++++++----------- vendor/storage.js | 24 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index eca9855..35a95d4 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -26,11 +26,6 @@ gulp.task('default', () => { .pipe(source('test-interface.min.js')) .pipe(buffer()) .pipe(uglify()) - - .pipe(function () { - console.log(arguments); - }) - .pipe(gulp.dest('build')); }); @@ -40,14 +35,15 @@ gulp.task('do-watch', () => { }); gulp.task("bookmarklet", cb => { + var code = fs.readFileSync('./assets/README.md', 'utf-8'); var data = fs.readFileSync('./build/test-interface.min.js', 'utf-8'); - var code = bookmarklet.convert(data, { - - }); + var escaped = bookmarklet.convert(code); - var readme = fs.readFileSync('./assets/README.md', 'utf-8'); - readme = readme.replace('_BOOKMARKLET_', code); - fs.writeFile('./README.md', readme, cb); + fs.writeFile( + './README.md', + readme.replace('_BOOKMARKLET_', escaped), + cb + ); }); gulp.task('watch', ['default', 'do-watch']); diff --git a/vendor/storage.js b/vendor/storage.js index b6a671e..22c5736 100644 --- a/vendor/storage.js +++ b/vendor/storage.js @@ -1,15 +1,27 @@ +const supportsStorage = ((store) => { + if (!store) return false; + try { + store.setItem('a','a'); + return true; + } catch(e) { + return false; + } +})(window.localStorage); + function Storage (type) { + function createCookie(name, value, days) { - var date, expires; + let date + let expires; if (days) { date = new Date(); - date.setTime(date.getTime()+(days*24*60*60*1000)); - expires = "; expires="+date.toGMTString(); + date.setTime(date.getTime() + (days*24*60*60*1000)); + expires = `; expires=${date.toGMTString()}`; } else { expires = ""; } - document.cookie = name+"="+value+expires+"; path=/"; + document.cookie = `${name}=${value + expires}; path=/`; } function readCookie(name) { @@ -89,6 +101,6 @@ function Storage (type) { }; export default { - local: window.localStorage || new Storage('local'), - session: window.sessionStorage || new Storage('session') + local: supportsStorage ? window.localStorage : new Storage('local'), + session: supportsStorage ? window.sessionStorage : new Storage('session') }; From a811af03f0e24e89b1d0c6a7b5bde9795b9c1bbb Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Thu, 17 Dec 2015 14:46:10 -0800 Subject: [PATCH 3/5] Build --- build/test-interface.min.js | 2 +- build/test.min.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/test-interface.min.js b/build/test-interface.min.js index 3abb3ec..5422ef0 100644 --- a/build/test-interface.min.js +++ b/build/test-interface.min.js @@ -1,3 +1,3 @@ !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.TestInterface=e()}}(function(){var e;return function t(e,n,r){function i(s,a){if(!n[s]){if(!e[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(o)return o(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var c=n[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return i(n?n:t)},c,c.exports,t,e,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;s\n [data-ab-tester] {\n background: #111 url(http://i.imgur.com/lnDx7VT.jpg) center center repeat;\n border: 1px solid rgba(0, 0, 0, 0.5);\n border-radius: 5px;\n color: #fff;\n padding: 10px;\n position: fixed;\n font-family: sans-serif;\n right: 10px;\n text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);\n top: 10px;\n width: 200px;\n z-index: 9;\n -webkit-font-smoothing: antialiased;\n }\n [data-ab-tester] ul {\n list-style-type: none;\n margin: 0;\n padding: 0;\n }\n [data-ab-tester] ul li:not(:last-child) {\n margin-bottom: 15px;\n }\n [data-ab-tester] label {\n margin-bottom: 5px;\n font-weight: bold;\n display: block;\n }\n [data-ab-tester] select {\n display: block;\n width: 100%;\n }\n ')}},{key:"template",value:function(){var e=a["default"]("
"),t=a["default"]("
    "),n=this.tests,r=Object.keys(n);return 0===r.length?(e.html("No Tests"),e):(r.sort().forEach(function(e){var r=n[e].bucket,i=n[e].buckets.map(function(e){return""+e+""});t.append("
  • \n \n \n
  • ")}),t.appendTo(e),e)}},{key:"listen",value:function(e){var t=this,n=this.tests;this.$template.find("select").on("change",function(r){var i=a["default"](r.target),o=i.data("test"),s=i.val();n[o].bucket=s,e.call(t)})}},{key:"update",value:function(){l["default"].local.setItem(this.key,JSON.stringify(this.tests))}}]),e}();n["default"]=p},{"../vendor/storage":4,jquery:3}],3:[function(t,n,r){!function(e,t){"object"==typeof n&&"object"==typeof n.exports?n.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(t,n){function r(e){var t="length"in e&&e.length,n=ee.type(e);return"function"===n||ee.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function i(e,t,n){if(ee.isFunction(t))return ee.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return ee.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(ue.test(t))return ee.filter(t,e,n);t=ee.filter(t,e)}return ee.grep(e,function(e){return V.call(t,e)>=0!==n})}function o(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function s(e){var t=ge[e]={};return ee.each(e.match(he)||[],function(e,n){t[n]=!0}),t}function a(){K.removeEventListener("DOMContentLoaded",a,!1),t.removeEventListener("load",a,!1),ee.ready()}function u(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=ee.expando+u.uid++}function l(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(we,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:be.test(n)?ee.parseJSON(n):n}catch(i){}xe.set(e,t,n)}else n=void 0;return n}function c(){return!0}function f(){return!1}function p(){try{return K.activeElement}catch(e){}}function d(e,t){return ee.nodeName(e,"table")&&ee.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function h(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function g(e){var t=Me.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n=0,r=e.length;r>n;n++)ye.set(e[n],"globalEval",!t||ye.get(t[n],"globalEval"))}function v(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(ye.hasData(e)&&(o=ye.access(e),s=ye.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)ee.event.add(t,i,l[i][n])}xe.hasData(e)&&(a=xe.access(e),u=ee.extend({},a),xe.set(t,u))}}function y(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&ee.nodeName(e,t)?ee.merge([e],n):n}function x(e,t){var n=t.nodeName.toLowerCase();"input"===n&&Ne.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}function b(e,n){var r,i=ee(n.createElement(e)).appendTo(n.body),o=t.getDefaultComputedStyle&&(r=t.getDefaultComputedStyle(i[0]))?r.display:ee.css(i[0],"display");return i.detach(),o}function w(e){var t=K,n=Ie[e];return n||(n=b(e,t),"none"!==n&&n||(We=(We||ee("