-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
329 lines (279 loc) · 7.62 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
'use strict';
var browserify = require('./browserify')
, minimatch = require("minimatch")
, child = require('child_process')
, dot = require('dot-component')
, fusing = require('fusing')
, path = require('path')
, fs = require('fs');
//
// Empty dummy function, does nothing unless an error argument has been given.
//
function nope(err) {
if (err) return console.error(err.stack) && process.exit(1);
}
/**
* Illuminati: Secret society of testers.
*
* @constructor
* @param {String} dir Directory we're loaded in.
* @param {Object} options Optional options.
* @api public
*/
function Illuminati(dir, options) {
if (!(this instanceof Illuminati)) return new Illuminati(dir, options);
var self = this;
this.fuse();
this.root = dir; // Root directory
if (fs.existsSync(path.join(dir, 'package.json'))) {
(function merge(data) {
Object.keys(data || {}).forEach(function each(key) {
self.conf[key] = +data[key] || data[key];
});
})(require(path.join(dir, 'package.json')).illuminati);
}
}
fusing(Illuminati, require('eventemitter3'));
/**
* Find the configuration for the Illuminati runner. We assume that the
* configuration is added in a special `illuminati` key in the users
* `package.json` which can be used to configure various of things in the
* project. This configuration will be merged with our default illuminati
* configuration from our own `package.json` file.
*
* @type {Object}
* @public
*/
Illuminati.writable('conf', require('./package.json').illuminati);
/**
* Command line arguments.
*
* @type {Object}
* @public
*/
Illuminati.readable('argv', require('argh').argv);
/**
* The glob pattern we use for searching valid test files.
*
* @type {String}
* @public
*/
Illuminati.readable('glob', '*.test.js');
/**
* The assets that need to be served.
*
* @type {Array}
* @public
*/
Illuminati.writable('assets', [
'./mocha.js',
'./mocha.css',
'./view.html'
].map(function optimize(file) {
if (!~file.indexOf('mocha')) return file;
//
// Make sure that the files are resolved from the correct location, we cannot
// trust `npm` to always add them in a `./node_modules/mocha` folder. It can
// be some odd parent root if the user has already installed a mocha
// somewhere.
//
return path.join(path.dirname(require.resolve('mocha')), file);
}));
/**
* Run the actual test suites.
*
* @api public
*/
Illuminati.readable('run', function run() {
var illuminati = this;
/**
* The tests have run.
*
* @param {Error} err
* @api private
*/
function ran(err) {
if (err) return process.exit(1);
return process.exit(0);
}
if (this.argv.server) return this.server(nope);
if (!this.argv.phantom) return this.mocha(ran);
this.server(function listening(err) {
illuminati.phantomjs(ran);
});
});
/**
* Create our HTTP server and start listening on the provide port number.
*
* @param {Function} fn Completion callback, server is listening
* @api private
*/
Illuminati.readable('server', function server(fn) {
var app = require('http').createServer(this.incoming.bind(this))
, illuminati = this;
this.assets = this.assets.map(this.map);
browserify(this.files, this.conf.browserify || {
basedir: this.root
}, function (err, source, map, preload) {
if (err) {
console.error(err.stack);
return process.exit(1);
}
source += '//# sourceMappingURL=/illuminati.map';
map.file = '/illuminati.js';
illuminati.assets.push({
type: 'text/javascript',
url: '/illuminati.js',
data: source
}, {
data: JSON.stringify(map),
type: 'application/json',
url: '/illuminati.map'
}, {
type: 'text/javascript',
url: '/prepare-env.js',
data: preload
});
require('connected')(app, illuminati.conf.port, fn);
});
//
// We don't want our HTTP server to hold up the destruction of the world. So
// we unref it.
//
if (app.unref) app.unref();
return this;
});
/**
* Run the tests on PhantomJS.
*
* @param {Number} port The port number our server is listening on.
* @param {Function} fn Completion callback.
* @api private
*/
Illuminati.readable('phantomjs', function phantomjs(fn) {
var phantom = child.spawn(
path.join(path.dirname(require.resolve('mocha-phantomjs')), '../..', '.bin', 'mocha-phantomjs'), [
'http://localhost:'+ this.conf.port
], {
stdio: 'inherit'
});
phantom.on('close', function exit(code) {
if (code) {
return fn(new Error('Tests failed to run, returned exit code: '+ code));
}
fn();
});
return this;
});
/**
* Run the tests against the regular mocha.
*
* @param {Function} fn Completion function.
* @api private
*/
Illuminati.readable('mocha', function mochas(fn) {
var mocha = child.spawn(
path.join(path.dirname(require.resolve('mocha')), '..', '.bin', 'mocha'), [
'--require', 'assume',
'--reporter', this.conf.reporter,
'--ui', this.conf.ui
].concat(this.files), {
stdio: 'inherit'
});
mocha.on('close', function exit(code) {
if (code) {
return fn(new Error('Tests failed to run, returned exit code: '+ code));
}
fn();
});
return this;
});
/**
* Find the files that we need to test for.
*
* @type {Array}
* @public
*/
Illuminati.get('files', function files() {
if (this.argv.argv) return this.argv.argv;
var illuminati = this;
return [
path.join(illuminati.root, 'tests'),
path.join(illuminati.root, 'test')
].reduce(function reduce(files, dir) {
var folder;
try { folder = fs.readdirSync(dir); }
catch (e) { return files; }
Array.prototype.push.apply(files, folder.filter(function filter(file) {
return minimatch(file, illuminati.glob);
}).map(function map(file) {
return path.join(dir, file);
}));
return files;
}, []);
});
/**
* Introduce template tags into the given template.
*
* @param {Object} data Information to merge.
* @param {String} template Template to replace
* @returns {String} The template
* @api private
*/
Illuminati.readable('introduce', function introduce(data, template) {
var key; template = template.toString();
while (key = /{illuminati:([^{]+?)}/gm.exec(template)) {
key = key[0];
template = template.replace(key, dot.get(data, key.slice(12, -1)));
}
return template;
});
/**
* Map assets to an object that we can serve.
*
* @param {String} file Filename/address.
* @returns {Object} Serve-able object
* @api public
*/
Illuminati.readable('map', function map(file) {
if ('string' !== typeof file) {
return file; // Already processed, do not give a fuck.
}
var location = file.charAt(0) !== '/'
? path.join(__dirname, file)
: file;
return {
data: fs.readFileSync(location, 'utf-8'),
url: '/'+ path.basename(file),
type: {
js: 'text/javascript',
css: 'text/css',
html: 'text/html',
json: 'application/json'
}[path.extname(file).slice(1)] || 'text/plain'
};
});
/**
* Answer HTTP requests to our incoming test server.
*
* @param {Request} req HTTP request.
* @param {Response} res HTTP response.
* @api private
*/
Illuminati.readable('incoming', function incoming(req, res) {
res.statusCode = 200;
var illuminati = this;
if (req.url === '/') req.url = '/view.html';
if (illuminati.assets.some(function some(asset) {
if (asset.url !== req.url) return false;
res.setHeader('Content-Type', asset.type);
res.end(illuminati.introduce(illuminati.conf, asset.data));
return true;
})) return;
res.statusCode = 404;
res.end('404: Please read the documentation on: '+ illuminati.conf.homepage);
});
//
// Expose the module.
//
module.exports = Illuminati;