├── .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 |
10 |
18 |
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