├── .gitignore ├── crowdsource.html ├── css ├── mocha.css └── style.css ├── index.html └── js ├── crowdsource.js ├── lib ├── chai-as-promised.js ├── chai.js ├── jquery.js ├── mocha.js ├── q.js ├── underscore-min.map └── underscore.js ├── setup.js └── specs.js /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TodoBackend/todo-backend-js-spec/9fdf6f9390d743116f232f08efc70e118d88c7a6/.gitignore -------------------------------------------------------------------------------- /crowdsource.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /css/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | /** 140 | * (1): approximate for browsers not supporting calc 141 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 142 | * ^^ seriously 143 | */ 144 | #mocha .test pre { 145 | display: block; 146 | float: left; 147 | clear: left; 148 | font: 12px/1.5 monaco, monospace; 149 | margin: 5px; 150 | padding: 15px; 151 | border: 1px solid #eee; 152 | max-width: 85%; /*(1)*/ 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | word-wrap: break-word; 155 | border-bottom-color: #ddd; 156 | -webkit-border-radius: 3px; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-border-radius: 3px; 159 | -moz-box-shadow: 0 1px 3px #eee; 160 | border-radius: 3px; 161 | } 162 | 163 | #mocha .test h2 { 164 | position: relative; 165 | } 166 | 167 | #mocha .test a.replay { 168 | position: absolute; 169 | top: 3px; 170 | right: 0; 171 | text-decoration: none; 172 | vertical-align: middle; 173 | display: block; 174 | width: 15px; 175 | height: 15px; 176 | line-height: 15px; 177 | text-align: center; 178 | background: #eee; 179 | font-size: 15px; 180 | -moz-border-radius: 15px; 181 | border-radius: 15px; 182 | -webkit-transition: opacity 200ms; 183 | -moz-transition: opacity 200ms; 184 | transition: opacity 200ms; 185 | opacity: 0.3; 186 | color: #888; 187 | } 188 | 189 | #mocha .test:hover a.replay { 190 | opacity: 1; 191 | } 192 | 193 | #mocha-report.pass .test.fail { 194 | display: none; 195 | } 196 | 197 | #mocha-report.fail .test.pass { 198 | display: none; 199 | } 200 | 201 | #mocha-report.pending .test.pass, 202 | #mocha-report.pending .test.fail { 203 | display: none; 204 | } 205 | #mocha-report.pending .test.pass.pending { 206 | display: block; 207 | } 208 | 209 | #mocha-error { 210 | color: #c00; 211 | font-size: 1.5em; 212 | font-weight: 100; 213 | letter-spacing: 1px; 214 | } 215 | 216 | #mocha-stats { 217 | font-size: 12px; 218 | color: #888; 219 | z-index: 1; 220 | } 221 | 222 | #mocha-stats .progress { 223 | float: right; 224 | padding-top: 0; 225 | } 226 | 227 | #mocha-stats em { 228 | color: black; 229 | } 230 | 231 | #mocha-stats a { 232 | text-decoration: none; 233 | color: inherit; 234 | } 235 | 236 | #mocha-stats a:hover { 237 | border-bottom: 1px solid #eee; 238 | } 239 | 240 | #mocha-stats li { 241 | display: inline-block; 242 | margin: 0 5px; 243 | list-style: none; 244 | padding-top: 11px; 245 | } 246 | 247 | #mocha-stats canvas { 248 | width: 40px; 249 | height: 40px; 250 | } 251 | 252 | #mocha code .comment { color: #ddd; } 253 | #mocha code .init { color: #2f6fad; } 254 | #mocha code .string { color: #5890ad; } 255 | #mocha code .keyword { color: #8a6343; } 256 | #mocha code .number { color: #2f6fad; } 257 | 258 | @media screen and (max-device-width: 480px) { 259 | #mocha { 260 | margin: 60px 0px; 261 | } 262 | 263 | #mocha #stats { 264 | position: absolute; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | #target-chooser { 2 | font: 30px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | font-weight: 100; 4 | margin: 2em 4em; 5 | position: relative; 6 | } 7 | 8 | #target-chooser .wrapper { 9 | border: 1px black dotted; 10 | padding: 1em 0.5em 1em 2em; 11 | position: relative; 12 | } 13 | 14 | #target-chooser label { 15 | font-style: italic; 16 | position: absolute; 17 | top: -25px; 18 | left: 10px; 19 | padding: 0 8px; 20 | background: white; 21 | } 22 | 23 | #target-chooser input { 24 | font: inherit; 25 | width: 100%; 26 | padding: 0 0.2em; 27 | } 28 | 29 | #target-chooser .link-to-source { 30 | font-size: small; 31 | color: rgb(48, 173, 48); 32 | text-decoration: none; 33 | } 34 | 35 | #target-chooser .link-to-source:hover { 36 | text-decoration: underline; 37 | } 38 | 39 | #target-chooser button { 40 | border: none; 41 | font: inherit; 42 | font-size: 40px; 43 | font-weight: 400; 44 | margin-top: 0.4em; 45 | margin-right: 0; 46 | 47 | float: right; 48 | 49 | padding: 0.2em 0.6em; 50 | background-color: rgb(48, 173, 48); 51 | color: white; 52 | 53 | -webkit-box-shadow: 2px 2px 1px rgba(50, 50, 50, 0.75); 54 | -moz-box-shadow: 2px 2px 1px rgba(50, 50, 50, 0.75); 55 | box-shadow: 2px 2px 1px rgba(50, 50, 50, 0.75); 56 | } 57 | #target-chooser button:hover { 58 | background-color: rgb(8, 123, 8); 59 | } 60 | 61 | #target-info { 62 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 63 | font-weight: 100; 64 | padding: 1em 2em; 65 | 66 | background: rgb(36, 110, 190); 67 | color: rgb(243, 243, 243); 68 | } 69 | 70 | #target-info h2 { 71 | font-size: 1.5em; 72 | margin: 0; 73 | } 74 | 75 | #target-info a { 76 | font-size: 1.2em; 77 | color: inherit; 78 | } 79 | 80 | #target-info .target-url { 81 | font-style: italic; 82 | font-weight: 200; 83 | } 84 | 85 | #mocha { 86 | margin-top: 5px; 87 | } 88 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Reference Specs for Todo-Backend 5 | 6 | 7 | 8 | 9 | Fork me on GitHub 10 |
11 |
12 | 13 | 14 | view source 15 |
16 | 17 |
18 |
19 |

these tests are targeting:

20 | choose a different server to target 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /js/crowdsource.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | var SERVICES_BASE = "https://services.todobackend.com/"; 4 | var TEST_RUN_CREATE_URL = new URL('/test-runs', SERVICES_BASE); 5 | 6 | mocha.setup('bdd'); 7 | mocha.slow("5s"); 8 | mocha.timeout("30s"); //so that tests don't fail with a false positive while waiting for e.g a heroku dyno to spin up 9 | window.expect = chai.expect; 10 | 11 | 12 | var targetRootUrl = window.location.search.substr(1); 13 | 14 | if( targetRootUrl ){ 15 | defineSpecsFor(targetRootUrl); 16 | runAndRecordTests(); 17 | }else{ 18 | console.warn('no target specified for tests'); 19 | } 20 | 21 | function runAndRecordTests(){ 22 | mocha.checkLeaks(); 23 | var runner = mocha.run(); 24 | 25 | var testResultsUrl = null; 26 | startRecordingTestRun().then( function(testRunResource){ 27 | testResultsUrl = new URL(testRunResource._links.results.href, SERVICES_BASE); 28 | }); 29 | 30 | track('Test Start',{targetRootUrl:targetRootUrl}); 31 | 32 | runner.on('suite end', function(suite){ 33 | if( suite.root ){ 34 | var suitePayload = serializeSuite(suite); 35 | 36 | track('Test Suite End', suitePayload); 37 | if( testResultsUrl ){ 38 | recordTestResults(testResultsUrl,suitePayload); 39 | }else{ 40 | console.log('no test run url available to record results'); 41 | } 42 | } 43 | }); 44 | }; 45 | 46 | function startRecordingTestRun(){ 47 | return $.post(TEST_RUN_CREATE_URL); 48 | } 49 | 50 | function recordTestResults(testResultsUrl,results){ 51 | return $.ajax({ 52 | url: testResultsUrl, 53 | method: 'POST', 54 | contentType: 'application/json', 55 | data: JSON.stringify(results) 56 | }); 57 | } 58 | 59 | function serializeSuite(suite){ 60 | var childSuites = _.map( suite.suites, serializeSuite ); 61 | var tests = _.map( suite.tests, function(test){ 62 | return _.pick(test,'duration','title','state','pending','speed','sync','timedOut','type'); 63 | }); 64 | 65 | return { 66 | suites: childSuites, 67 | tests: tests, 68 | root: suite.root, 69 | pending: suite.pending, 70 | title: suite.title 71 | }; 72 | } 73 | 74 | function track(eventName,eventPayload){ 75 | analytics.track(eventName, eventPayload, { context: { ip: "0.0.0.0" }}); 76 | 77 | } 78 | 79 | })(); 80 | -------------------------------------------------------------------------------- /js/lib/chai-as-promised.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | // Module systems magic dance. 5 | 6 | /* istanbul ignore else */ 7 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { 8 | // NodeJS 9 | module.exports = chaiAsPromised; 10 | } else if (typeof define === "function" && define.amd) { 11 | // AMD 12 | define(function () { 13 | return chaiAsPromised; 14 | }); 15 | } else { 16 | /*global self: false */ 17 | 18 | // Other environment (usually