├── .gitignore
├── Makefile
├── README.md
├── bin
└── browser-require
├── browser-require.old.js
├── examples
├── npm
│ ├── index.html
│ ├── index.jade
│ ├── js
│ │ └── app.js
│ └── server.js
└── relative
│ ├── index.html
│ ├── index.jade
│ ├── js
│ ├── above.js
│ ├── app.js
│ ├── nested
│ │ └── nest.js
│ └── rel.js
│ └── server.js
├── lib
├── browser-require.js
├── npm_module.js
└── templates
│ ├── boilerplate.js
│ └── inject_module.js
├── package.json
└── test
├── compiled
├── index.html
├── js
│ ├── above.js
│ ├── level1.js
│ ├── level2.js
│ ├── level2
│ │ └── level3.js
│ ├── nested
│ │ ├── aboveNested.js
│ │ └── nest.js
│ ├── rel.js
│ └── test.js
└── server.js
├── hell.js
└── nested
├── css
└── qunit.css
├── index.html
├── js
├── above.js
├── level1.js
├── level2.js
├── level2
│ └── level3.js
├── nested
│ ├── aboveNested.js
│ └── nest.js
├── qunit.js
├── rel.js
└── test.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | **.swp
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | start-test-server:
2 | @cd test/nested && $(shell which node) server.js 2>&1 &
3 | stop-test-server:
4 | @pkill brtest
5 | test-chrome:
6 | @$(shell which google-chrome) http://localhost:1234/?v=$(shell date +%s)
7 | test-firefox:
8 | @$(shell which firefox) http://localhost:1234/?v=$(shell date +%s)
9 |
10 | .PHONY: start-test-server stop-test-server test-chrome test-firefox
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | browser-require
2 | ===============
3 |
4 | #### The easiest way to require and use CommonJS and NPM modules from your browser
5 |
6 | Npm makes it easy to share code on your server. But how many times have you
7 | wanted to share Javascript code easily between the server and the browser?
8 | `browser-require` allows you to easily use CommonJS and NPM modules defined
9 | on the server in your browser.
10 |
11 | browser-require enables you to require both relative (local to your project)
12 | CommonJS modules as well as global NPM modules.
13 |
14 | ### Installation
15 | To install:
16 | $ npm install browser-require
17 |
18 | ### Using browser-require within your connect app
19 |
20 | Currently, browser-require depends on the
21 | [connect](https://github.com/visionmedia/connect/) middleware framework,
22 | if you want to serve client javascript files that contain `require`s.
23 |
24 | First, add in the browser-require middleware into your `connect` server:
25 | var connect = require('connect')
26 | , app = connect.createServer()
27 | , exposeRequire = require('browser-require');
28 |
29 | // The following line "app.use(..." is what you want to add to your project
30 | // Make sure the browser-require middleware comes before staticProvider middleware
31 | app.use(exposeRequire({
32 | base: __dirname // This is where we look to find your non-global modules
33 | });
34 |
35 | app.use(connect.staticProvider(__dirname));
36 | app.listen(3000);
37 |
38 | On the browser, this is what your index.html might look like:
39 |
40 |
41 |
42 |
43 | browser-require example
44 |
45 |
46 |
47 |
48 |
49 |
50 | The script src "/js/app.js" is where your custom JavaScript code resides.
51 |
52 | Then in `/js/app.js`, you can require CommonJS and NPM modules as if you are on the server:
53 |
54 | var _ = require('underscore'); // browser-side requires FTW!!!!
55 |
56 | // This should alert "10"
57 | alert(_.reduce([1, 2, 3, 4], function (sum, num) {
58 | sum += num;
59 | return sum;
60 | }));
61 |
62 | ### How it works
63 | When you request a javascript file:
64 |
65 | 1. The server looks up the source and its module dependencies (if any) recursively.
66 | 2. Once the server has collected all dependencies, it compiles the top-level file plus
67 | its dependencies into a file that gets sent back to the browser.
68 |
69 | ### Command line binary
70 | Sometimes you need to statically compile a set of javascript client files from the command line.
71 | For example, this is necessary if you are building a Chrome plugin. A Chrome plugin can use JavaScript
72 | files that exist inside the Chrome plugin (as opposed to fetching a JavaScript file that exists on the
73 | server). Therefore, it is necessary in this case to compile your JavaScript files and their dependencies
74 | outside of the context of a server.
75 |
76 | `browser-require` supports this via a command line binary. You can use it in the following way:
77 |
78 | $ browser-require path/to/js/file.js > path/to/compiled/js/file.js
79 |
80 | ### Examples
81 | There are examples in the [./examples](https://github.com/bnoguchi/browser-require/tree/master/examples) directory.
82 |
83 | To run the relative modules example:
84 |
85 | $ cd examples/relative
86 | $ node server.js
87 |
88 | To run the npm modules example:
89 |
90 | $ npm install underscore
91 | $ npm install data-structures-js
92 | $ npm install validator
93 | $ cd examples/npm
94 | $ node server.js
95 |
96 | ### Running the tests
97 | First, make sure the following npm modules are installed, since we will be
98 | using them to test browser-require:
99 |
100 | $ npm install underscore
101 | $ npm install data-structures-js
102 | $ npm install validator
103 |
104 | First, start up the test server:
105 |
106 | $ make start-test-server
107 |
108 | To run tests in Chrome:
109 |
110 | $ make test-chrome
111 |
112 | To run tests in Firefox:
113 |
114 | $ make test-firefox
115 |
116 | Finally, stop the test server:
117 |
118 | $ make stop-test-server
119 |
120 | ### Planning on implementing
121 | - A middleware filter mechanism to include things such as a Google Closure Compiler filter.
122 |
123 | ### Contributors
124 | - [Brian Noguchi](https://github.com/bnoguchi)
125 |
126 | ### License
127 | MIT License
128 |
129 | ---
130 | ### Author
131 | Brian Noguchi
132 |
--------------------------------------------------------------------------------
/bin/browser-require:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var br = require('../lib/browser-require')
4 | , compileAll = br.compileAll
5 | , ScriptPromise = br.ScriptPromise
6 | , fs = require('fs');
7 |
8 | var fname = process.argv[2];
9 |
10 | ScriptPromise.base = process.cwd();
11 | fname = fs.realpathSync(fname);
12 | fname = fname.replace(ScriptPromise.base, '.');
13 | ScriptPromise.from(fname, null, function (script) {
14 | compileAll(script, function (compiled) {
15 | console.log(compiled);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/browser-require.old.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | , path = require('path')
3 | , npm = require('npm');
4 |
5 | module.exports = function (opts) {
6 | var baseDir = opts.base
7 | , compile = opts.compile
8 |
9 | , cache = {} // {moduleName: { mtime: ..., compiled: ...}}
10 | , templates = {
11 | response: fs.readFileSync(__dirname + '/templates/response.js', 'utf8')
12 | , src: fs.readFileSync(__dirname + '/templates/src.js', 'utf8')
13 | };
14 |
15 | /**
16 | * Loads the module's parameters into the template.
17 | * In dynamic mode, the template is sent to the browser.
18 | * In compile mode, the template is used as a fragment in the aggregate compiled output.
19 | *
20 | * @param {String} module is the name of the module (e.g., 'somemodule.js')
21 | * @param {String} src is the src of the module
22 | * @param {Array} deps is the array of the module's dependencies, as Strings
23 | * @param {Boolean} isIndex is true if module's src existed at an index.js
24 | * @return {String} the template that is filled in with the module's information
25 | */
26 | function fillinTemplate (module, src, deps, isIndex) {
27 | var dir = isIndex
28 | ? module.replace(/\.js$/, '').split('/')
29 | : module.split('/').slice(0, -1)
30 | , src = templates.src
31 | .replace('$src', src)
32 | .replace(/\$dir/g, JSON.stringify(dir));
33 | return templates.response
34 | .replace('$module', JSON.stringify(module))
35 | .replace('$src', JSON.stringify(src))
36 | .replace('$deps', JSON.stringify(deps))
37 | .replace('$isIndex', isIndex);
38 | }
39 |
40 | /**
41 | * Returns the names of the dependencies found in the source text.
42 | *
43 | * @param {String} src
44 | * @return {Array} dependencies
45 | */
46 | function depsFor (src) {
47 | var re = /require\(['"]([^'"]+)['"]\)/g
48 | , match
49 | , deps = [];
50 | while (match = re.exec(src)) {
51 | deps.push(match[1]);
52 | }
53 | return deps;
54 | }
55 |
56 | function NpmModule (url) {
57 | var modulePath = url.replace(NpmModule.npmFlag, '').replace(/\.js$/, '')
58 | , moduleChain = modulePath.split('/')
59 | , pkgName = this.pkgName = moduleChain[0]
60 | , relChain = this.relChain = moduleChain.slice(1);
61 | this.isSubmodule = !!relChain.length;
62 | var nv = pkgName.split('@')
63 | , n = this.name = nv[0]
64 | , v = this.ver = nv[1] || 'active';
65 | this.dir = path.join(npm.dir, n, v, 'package');
66 | }
67 | NpmModule.npmFlag = /^\/NPM\//;
68 |
69 | NpmModule.prototype = {
70 | __pkgJson: function (fn) {
71 | if (this._pkg) return fn(null, this._pkg);
72 | if (this._pkgerr) return fn(this._pkgerr);
73 | var self = this;
74 | fs.readFile(this.dir + '/package.json', 'utf8', function (err, body) {
75 | if (err) {
76 | self._pkgerr = err;
77 | console.error("package.json missing for " + self.pkgName);
78 | return fn(err);
79 | }
80 | var pkg = self._pkg = JSON.parse(body);
81 | fn(null, pkg);
82 | });
83 | },
84 | __mainSrc: function (fn) {
85 | if (this._mainSrc) return fn(null, this._mainSrc);
86 | if (this._mainSrcErr) return fn(this._mainSrcErr);
87 | var self = this;
88 | this.pkgJson( function (err, pkg) {
89 | if (err) return fn(self._mainSrcErr = err);
90 | if (pkg.main) {
91 | fs.stat(self.dir + '/' + pkg.main, function (err, stat) {
92 | if (err) {
93 | console.error(err);
94 | fn(self._mainSrcErr = err);
95 | } else if (stat.isDirectory()) {
96 | self._mainSrc = fs.readFileSync(self.dir + '/' + pkg.main + '/index.js', 'utf8');
97 | fn(null, self._mainSrc);
98 | } else {
99 | self._mainSrc = fs.readFileSync(self.dir + '/' + pkg.main, 'utf8');
100 | fn(null, self._mainSrc);
101 | }
102 | });
103 | } else {
104 | fn(self.mainSrcErr = "Missing main in package.json for " + self.pkgName);
105 | }
106 | });
107 | },
108 | __relSrc: function (fn) {
109 | if (this._relSrc) return fn(null, this._relSrc);
110 | if (this._relSrcErr) return fn(this._relSrcErr);
111 | var self = this;
112 | this.pkgJson( function (err, pkg) {
113 | if (err) return fn(self._relSrcErr = err);
114 | var directories = pkg.directory || pkg.directories
115 | , lib = directories && directories.lib
116 | , chain = [self.dir], direct, index;
117 | if (lib) chain.push(lib);
118 | direct = path.join.apply(this, chain.concat([self.relChain.join('/') + '.js']) );
119 | index = path.join.apply(this, chain.concat([self.relChain.join('/'), 'index.js']) );
120 | if (path.existsSync(direct)) {
121 | fn(null, fs.readFileSync(direct, 'utf8'), false);
122 | } else if (path.existsSync(index)) {
123 | fn(null, fs.readFileSync(index, 'utf8'), true);
124 | } else {
125 | if (lib) {
126 | throw new Error("Unimplemented - could not find package " + self.relChain.join('/'));
127 | } else {
128 | self._relSrcErr = 'Missing ' + self.relChain.join('/') + ' in ' + self.pkgName + ' package';
129 | fn(self._relSrcErr);
130 | }
131 | }
132 | });
133 | },
134 | __src: function (fn) {
135 | if (this._src) return fn(null, this._src);
136 | if (this._srcerr) return fn(this._srcerr);
137 | var self = this;
138 | if (this.isSubmodule) { // Handle e.g., require("npm-module/sub-module")
139 | this.relSrc( function (err, relSrc, isIndex) {
140 | if (err) return fn(self._srcerr = err);
141 | fn(null, self._src = relSrc, isIndex);
142 | });
143 | } else { // Handle e.g., require("npm-module")
144 | this.mainSrc( function (err, mainSrc) {
145 | if (err) return fn(self._srcerr = err);
146 | fn(null, self._src = mainSrc);
147 | });
148 | }
149 | },
150 | __isInstalled: function (fn) {
151 | var self = this;
152 | fs.stat(this.dir, function (err, stat) {
153 | if (err || !stat.isDirectory()) {
154 | console.error(self.name + " is not installed via npm.");
155 | fn(err, false);
156 | } else {
157 | fn(null, true);
158 | }
159 | });
160 | }
161 | };
162 |
163 | // Wrap npm in a promise, for manageable async middleware
164 | NpmModule.isNpmLoaded = false;
165 | NpmModule.callbacks = []; // Callbacks for loaded event
166 | var proto = NpmModule.prototype;
167 | for (var k in proto) {
168 | proto[k.slice(2)] = (function (k) {
169 | return function () {
170 | if (NpmModule.isNpmLoaded) return proto[k].apply(this, arguments);
171 | NpmModule.callbacks.push([proto[k], this, arguments]);
172 | };
173 | })(k);
174 | }
175 | npm.load( function () {
176 | NpmModule.isNpmLoaded = true;
177 | var callbacks = NpmModule.callbacks;
178 | for (var i = 0, l = callbacks.length; i < l; i++) {
179 | callbacks[i][0].apply(callbacks[i][1], callbacks[i][2]);
180 | }
181 | });
182 |
183 | /**
184 | * The incoming url does not always verbatim point to the src file.
185 | * This is not the case for
186 | */
187 | function extractUrl (url) {
188 | // The following continuous block handles incoming requires
189 | // that exist above the base dir
190 | var uq = url.split('?')
191 | , chain = uq[0].split('/')
192 | , q = uq[1]
193 | , match
194 | , nAboveBase;
195 | // prefix carries ..,..,.. information - i.e., how many levels above
196 | if (q) {
197 | match = q.match(/n=([^&]+)/);
198 | if (match) nAboveBase = parseInt(match[1], 10);
199 | }
200 | if (nAboveBase) {
201 | url = chain.join('/');
202 | while (nAboveBase--) url = '/..' + url;
203 | }
204 | return url;
205 | }
206 |
207 | return function (req, res, next) {
208 | var src
209 | , url = extractUrl(req.url)
210 | , body
211 | , filepath;
212 |
213 | if (src = cache[url]) {
214 | res.writeHead(200, {'Content-Type': 'text/javascript'});
215 | res.end(src);
216 | } else if ('.js' === path.extname(url)) {
217 | if (url === '/browser_require.js') {
218 | src =
219 | cache[url] = fs.readFileSync(
220 | path.dirname(__filename) +
221 | '/client/browser_require.js', 'utf8');
222 |
223 | res.writeHead(200, {'Content-Type': 'text/javascript'});
224 | res.end(src);
225 | } else if (NpmModule.npmFlag.test(url)) { // Handle npm modules
226 | var npmModule = new NpmModule(url);
227 | npmModule.isInstalled(function (err, isInstalled) {
228 | if (isInstalled) {
229 | npmModule.src(function (err, body, isIndex) {
230 | var src =
231 | cache[url] = fillinTemplate(url, body, depsFor(body), isIndex);
232 | res.writeHead(200, {'Content-Type': 'text/javascript'});
233 | res.end(src);
234 | });
235 | } else {
236 | console.error("Could not find " + npmModule.pkgName +
237 | ". Make sure it's installed via npm.");
238 | res.writeHead(404);
239 | res.end();
240 | }
241 | });
242 | } else { // Handle local, relative modules
243 | filepath = path.join(baseDir, url);
244 | if (path.existsSync(filepath)) {
245 | body = fs.readFileSync(filepath, 'utf8');
246 | src =
247 | cache[url] = fillinTemplate(url, body, depsFor(body));
248 | res.writeHead(200, {'Content-Type': 'text/javascript'});
249 | res.end(src);
250 | } else {
251 | console.error("Could not find " + filepath);
252 | next();
253 | }
254 | }
255 | } else {
256 | next();
257 | }
258 | };
259 | };
260 |
--------------------------------------------------------------------------------
/examples/npm/index.html:
--------------------------------------------------------------------------------
1 | browser-require relative module example
--------------------------------------------------------------------------------
/examples/npm/index.jade:
--------------------------------------------------------------------------------
1 | !!! 5
2 | html
3 | head
4 | title browser-require relative module example
5 | body
6 | #answer
7 | script(type: 'text/javascript', src: 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js')
8 | script(type: 'text/javascript', src: '/js/app.js')
9 |
--------------------------------------------------------------------------------
/examples/npm/js/app.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore')
2 | , Set = require('data-structures-js/set')
3 | , xss = require('validator/xss');
4 |
5 | console.log(Set);
6 | console.log(xss);
7 |
8 | $( function () {
9 | var str = _.reduce( [1, 2, 3, 4], function (str, num) {
10 | str += num;
11 | return str;
12 | }, '');
13 | $('#answer').text(str);
14 | });
15 |
--------------------------------------------------------------------------------
/examples/npm/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | require.paths.unshift(path.join(__dirname, '..', '..'));
3 | var connect = require('connect');
4 | var app = connect.createServer();
5 | var exposeRequire = require('browser-require');
6 | app.use(exposeRequire({
7 | base: __dirname
8 | }));
9 | app.use(connect.staticProvider(__dirname));
10 | app.listen(1234);
11 | console.log("Run the example at http://127.0.0.1:1234/?v=" + (+new Date));
12 |
--------------------------------------------------------------------------------
/examples/relative/index.html:
--------------------------------------------------------------------------------
1 | browser-require relative module example
--------------------------------------------------------------------------------
/examples/relative/index.jade:
--------------------------------------------------------------------------------
1 | !!! 5
2 | html
3 | head
4 | title browser-require relative module example
5 | body
6 | #answer
7 | script(type: 'text/javascript', src: 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js')
8 | script(type: 'text/javascript', src: '/js/app.js')
9 |
--------------------------------------------------------------------------------
/examples/relative/js/above.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "in the trees above";
3 | };
4 |
--------------------------------------------------------------------------------
/examples/relative/js/app.js:
--------------------------------------------------------------------------------
1 | var rel = require('./rel');
2 |
3 | $( function () {
4 | $('#answer').text(rel());
5 | });
6 |
--------------------------------------------------------------------------------
/examples/relative/js/nested/nest.js:
--------------------------------------------------------------------------------
1 | var above = require('../above');
2 |
3 | module.exports = function () {
4 | return "Bird's nest " + above();
5 | };
6 |
--------------------------------------------------------------------------------
/examples/relative/js/rel.js:
--------------------------------------------------------------------------------
1 | var nest = require('./nested/nest');
2 |
3 | module.exports = function () {
4 | return "Hello World! with " + nest();
5 | };
6 |
--------------------------------------------------------------------------------
/examples/relative/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | require.paths.unshift(path.join(__dirname, '..', '..'));
3 | var connect = require('connect');
4 | var app = connect.createServer();
5 | var exposeRequire = require('browser-require');
6 | app.use(exposeRequire({
7 | base: __dirname
8 | }));
9 | app.use(connect.staticProvider(__dirname));
10 | app.listen(1234);
11 | console.log("Run the example at http://127.0.0.1:1234/?v=" + (+new Date));
12 |
--------------------------------------------------------------------------------
/lib/browser-require.js:
--------------------------------------------------------------------------------
1 | // 1. Read the top file
2 | // 2. Parse out the dependency names
3 | // 3. Resolve the file locations based on name + file depending on it
4 | // 4. For each dependency, read it and go to (2). Rinse and repeat
5 | // until no more files to read
6 | // 5. Compile the top file and all dependencies into a single file
7 |
8 | var fs = require('fs')
9 | , path = require('path')
10 | , EventEmitter = require('events').EventEmitter
11 | , cache = {}
12 | , dependencyPromise = require('dependency-promise')
13 | , libdir = path.join(path.dirname(__filename))
14 | , NpmModule = require('./npm_module');
15 |
16 | function succeed (res, src) {
17 | res.writeHead(200, {'Content-Type': 'text/javascript'});
18 | res.end(src);
19 | }
20 |
21 | function fail(res, err) {
22 | res.writeHead(404);
23 | res.end();
24 | }
25 | var templates = {
26 | boilerplate: fs.readFileSync(libdir + '/templates/boilerplate.js', 'utf8')
27 | , injectModule: fs.readFileSync(libdir + '/templates/inject_module.js', 'utf8')
28 | }
29 |
30 | function ScriptPromise (name, location, npmBase) {
31 | EventEmitter.call(this);
32 | this.setMaxListeners(0);
33 | this.name = name;
34 | if (npmBase) {
35 | if (name.indexOf('/') === -1)
36 | this.nextRelTo = name.slice(0, -3);
37 | else
38 | this.nextRelTo = name.split('/').slice(0, -1).join('/');
39 | this.npmBase = npmBase;
40 | } else {
41 | this.nextRelTo = path.dirname(name);
42 | }
43 | this.location = location;
44 | this.addListener('error', this.onError);
45 | }
46 |
47 | ScriptPromise.prototype.__proto__ = EventEmitter.prototype;
48 | for (var k in dependencyPromise) {
49 | ScriptPromise.prototype[k] = dependencyPromise[k];
50 | }
51 |
52 |
53 | ScriptPromise.modules = {};
54 |
55 | ScriptPromise.from = function (name, parent, fn) {
56 | var self = this;
57 | if (path.extname(name) === '.js') name = name.slice(0, -3);
58 | this.lookup( name, parent, function (err, normalized, location, npmBase) {
59 | var mod = self.modules[normalized] || (self.modules[normalized] = new ScriptPromise(normalized, location, npmBase));
60 | if (err) return mod.emit('error', err);
61 | fn(mod);
62 | });
63 | };
64 |
65 | ScriptPromise.lookup = function (name, parent, fn) {
66 | var char0 = name.charAt(0);
67 | if (char0 === '.' || char0 === '/') {
68 | if (parent && parent.npmBase) {
69 | // In case we have e.g., 'some-npm-module.js' as parent.name
70 | // Then we don't want to pass along '.'. We want to pass along
71 | // 'some-npm-module'
72 | this.lookupRel(name, parent, fn);
73 | } else {
74 | this.lookupRel(name, parent, fn);
75 | }
76 | } else {
77 | this.lookupNpm(name, fn);
78 | }
79 | };
80 |
81 | ScriptPromise.lookupRel = function (name, parent, fn) {
82 | var normDirect, normIndex
83 | , directLocation
84 | , parentdir
85 | , base
86 | , dir
87 | , self = this;
88 | if (name.charAt(0) === '/') name = '.' + name;
89 | if (!parent) {
90 | normDirect = name + '.js';
91 | normIndex = name + '/index.js';
92 | dir = this.base;
93 | } else {
94 | parentdir = parent.nextRelTo;
95 | normDirect = this.normalizeToParent(name + '.js', parentdir);
96 | normIndex = this.normalizeToParent(name + '/index.js', parentdir);
97 | dir = path.dirname(parent.location);
98 | }
99 | directLocation = path.join(dir, name + '.js');
100 | path.exists(directLocation, function (doesExist) {
101 | if (doesExist) return fn(null, normDirect, directLocation, parent && parent.npmBase);
102 | var indexLocation = path.join(dir, name + '/index.js');
103 | path.exists(indexLocation, function (doesExist) {
104 | if (doesExist) return fn(null, normIndex, indexLocation, parent && parent.npmBase);
105 | fn(new Error("module " + name + " seems to be missing" +
106 | (parent ? " relative to parent " + parent.name : '')));
107 | });
108 | });
109 | };
110 |
111 | ScriptPromise.normalizeToParent = function (name, parentdir) {
112 | if (parentdir === '/') parentdir = '.';
113 | else if (parentdir.charAt(0) === '/') parentdir = '.' + parentdir;
114 | var parts = name.split('/')
115 | , dirparts = parentdir.split('/')
116 | , aboveBase = false;
117 | for (var i = 0, l = parts.length, part; i < l; i++) {
118 | part = parts[i];
119 | if (part === '.') continue;
120 | else if (part === '..') {
121 | if (dirparts.length === 1 && dirparts[0] === '.') {
122 | dirparts.pop();
123 | aboveBase = true;
124 | }
125 | if (!aboveBase) dirparts.pop();
126 | else dirparts.push(part);
127 | } else dirparts.push(part);
128 | }
129 | return dirparts.join("/");
130 | };
131 |
132 | ScriptPromise.lookupNpm = function (name, fn) {
133 | var npmModule = new NpmModule(name);
134 | npmModule.isInstalled(function (err, isInstalled) { // Handle npm modules
135 | if (err) return fn(err);
136 | if (isInstalled) {
137 | npmModule.locate(function (err, location, isIndex) {
138 | if (err) return fn(err);
139 | var normalized = name + (isIndex ? '/index.js' : '.js');
140 | fn(null, normalized, location, npmModule.dir);
141 | });
142 | } else {
143 | err = new Error("Could not find " + npmModule.pkgName +
144 | ". Make sure it's installed via npm.");
145 | fn(err);
146 | }
147 | });
148 | };
149 |
150 | ScriptPromise.prototype.onError = function (err) {
151 | throw err;
152 | };
153 |
154 | ScriptPromise.prototype.load = function () {
155 | var self = this;
156 | fs.readFile(this.location, 'utf8', function (err, src) {
157 | if (err) return self.emit('error', err);
158 | self.src = src;
159 | var depNames = self.depsFor(src);
160 | if (!depNames.length)
161 | return self.trigger('loaded', true);
162 | var deps = [];
163 | depNames.forEach( function (name) {
164 | ScriptPromise.from(name, self, function (script) {
165 | if (!script.isTriggered('loaded')) script.load();
166 | deps.push(script);
167 | // TODO
168 | script.addListener('reloaded', function (src) {
169 | });
170 | if (deps.length === depNames.length) {
171 | self.dependsOn('loaded', deps);
172 | }
173 | });
174 | });
175 | });
176 |
177 | // For --watch
178 | // fs.watchFile(this.location, function (curr, prev) {
179 | // if (curr.mtime.getTime() > prev.mtime.getTime()) {
180 | // self.reload();
181 | // } else {
182 | // throw new Error("Times are weird");
183 | // }
184 | // });
185 | }
186 | ScriptPromise.prototype.depsFor = function (src) {
187 | var re = /^[^(?:\*|\/)]*\s*require\(['"]([^'"]+)['"]\)/gm
188 | , match
189 | , deps = [];
190 | while (match = re.exec(src)) {
191 | deps.push(match[1]);
192 | }
193 | return deps;
194 | };
195 | ScriptPromise.prototype.reload = function (src) {
196 | var self = this;
197 | fs.readFile(this.location, 'utf8', function (err, src) {
198 | if (err) return self.emit('error', err);
199 | self.src = src;
200 | // Check for dependency additions or removals
201 | var depNames = self.depsFor(src);
202 | // TODO
203 |
204 | // Notify anyone who depends on me
205 | self.emit('reloaded', src);
206 | });
207 | };
208 |
209 | function wrapDeps (script) {
210 | var src = {}
211 | , deps = script.dependenciesFor('loaded')
212 | , i = deps.length, dep;
213 | while (i--) {
214 | dep = deps[i];
215 | if (src[dep.name]) continue;
216 | src[dep.name] = wrapScript(dep);
217 | var wrapped = wrapDeps(dep);
218 | for (var k in wrapped) {
219 | src[k] = wrapped[k];
220 | }
221 | }
222 | return src;
223 | }
224 |
225 | function wrapScript (script) {
226 | return templates.injectModule
227 | .replace(/\$module/g, JSON.stringify(script.name))
228 | .replace(/\$dir/g, JSON.stringify(script.nextRelTo))
229 | .replace('$src', script.src);
230 | }
231 |
232 | exports = module.exports = function exposeRequire (opts) {
233 | ScriptPromise.base = opts.base;
234 |
235 | return function (req, res, next) {
236 | var url = req.url;
237 | if (cache[url]) return succeed(res, cache[url]);
238 | if ('.js' !== path.extname(url)) return next();
239 | url = url.replace(/\.js$/, '');
240 |
241 | ScriptPromise.from(url, null, function (script) {
242 | compileAll(script, function (compiled) {
243 | if (!compiled) return next();
244 |
245 | cache[url] = compiled;
246 | succeed(res, compiled);
247 | });
248 | });
249 | };
250 | };
251 |
252 | var compileAll = exports.compileAll = function compileAll (script, fn) {
253 | script.on('loaded', function (noDeps) {
254 | if (noDeps) {
255 | return fn(script.src);
256 | }
257 | var compiled = [templates.boilerplate]
258 | , deps = wrapDeps(script);
259 | for (var k in deps) {
260 | compiled.push(deps[k]);
261 | }
262 | // TODO Make scope be window, not module.exports
263 | compiled.push(wrapScript(script));
264 | compiled.push("browserRequire('" + script.name.slice(0, -3) + "');");
265 | fn(compiled.join("\n"));
266 | });
267 | script.load();
268 | };
269 |
270 | exports.ScriptPromise = ScriptPromise;
271 |
--------------------------------------------------------------------------------
/lib/npm_module.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | , path = require('path')
3 | , npm = require('npm');
4 |
5 | function NpmModule (url) {
6 | var modulePath = url.replace(/\.js$/, '')
7 | , moduleChain = modulePath.split('/')
8 | , pkgName = this.pkgName = moduleChain[0]
9 | , relChain = this.relChain = moduleChain.slice(1);
10 | this.isSubmodule = !!relChain.length;
11 | var nv = pkgName.split('@')
12 | , n = this.name = nv[0]
13 | , v = this.ver = nv[1] || 'active';
14 | }
15 |
16 | // Different situations:
17 | // requires from `main`
18 | // requires like 'module-name/sub'
19 | // relative requires (./etc) from a file in `lib` directory
20 | NpmModule.prototype = {
21 | __locate: function (fn) {
22 | if (this._location) return fn(null, this._location, this._isIndex);
23 | if (this._locationErr) return fn(this._locationErr);
24 | var self = this;
25 | if (this.isSubmodule) {
26 | this.relLocate(function (err, location, isIndex) {
27 | self._locationerr = err;
28 | self._location = location;
29 | self._isIndex = isIndex;
30 | fn(err, location, isIndex);
31 | });
32 | } else {
33 | this.mainLocate(function (err, location) {
34 | self._locationErr = err;
35 | self._location = location;
36 | fn(err, location);
37 | });
38 | }
39 | },
40 | __relLocate: function (fn) {
41 | if (this._relLocation) return fn(null, this._relLocation);
42 | if (this._relLocationErr) return fn(this._relLocationErr);
43 | var self = this, err;
44 | this.pkgJson(function (err, pkg) {
45 | if (err) return fn(self._relLocationErr = err);
46 | var directories = pkg.directory || pkg.directories
47 | , lib = directories && directories.lib
48 | , chain = [self.dir], direct, index;
49 | if (lib) chain.push(lib);
50 | direct = path.join.apply(this, chain.concat([self.relChain.join('/') + '.js']) );
51 | index = path.join.apply(this, chain.concat([self.relChain.join('/'), 'index.js']) );
52 | if (path.existsSync(direct)) {
53 | fn(null, direct, false);
54 | } else if (path.existsSync(index)) {
55 | fn(null, index, true);
56 | } else {
57 | if (lib) {
58 | err = self._relLocationErr =
59 | new Error('Unimplemented - could not find package ' +
60 | self.relChain.join('/') + ' relative to ' +
61 | path.join.apply(path, chain));
62 | } else {
63 | err = self._relLocationErr =
64 | new Error('Missing ' + self.relChain.join('/') +
65 | ' in ' + self.pkgName + ' package');
66 | }
67 | fn(err);
68 | }
69 | });
70 | },
71 | __mainLocate: function (fn) {
72 | if (this._mainLocation) return fn(null, this._mainLocation);
73 | if (this._mainLocateErr) return fn(this._mainLocateErr);
74 | var self = this;
75 | this.pkgJson( function (err, pkg) {
76 | if (err) return fn(self._mainLocateErr = err);
77 | if (pkg.main) {
78 | pkg.main = (path.extname(pkg.main) === '.js')
79 | ? pkg.main.slice(0, -3)
80 | : pkg.main;
81 | fs.stat(self.dir + '/' + pkg.main, function (err, stat) {
82 | if (err) {
83 | path.exists(self.dir + '/' + pkg.main + '.js', function (exists) {
84 | self._mainLocation = self.dir + '/' + pkg.main + '.js';
85 | fn(null, self._mainLocation);
86 | });
87 | } else if (stat.isDirectory()) {
88 | self._mainLocation = self.dir + '/' + pkg.main + '/index.js';
89 | fn(null, self._mainLocation);
90 | }
91 | });
92 | } else {
93 | fn(self._mainLocateErr = "Missing main in package.json for " + self.pkgName);
94 | }
95 | });
96 | },
97 | __pkgJson: function (fn) {
98 | if (this._pkg) return fn(null, this._pkg);
99 | if (this._pkgerr) return fn(this._pkgerr);
100 | var self = this;
101 | fs.readFile(this.dir + '/package.json', 'utf8', function (err, body) {
102 | if (err) {
103 | self._pkgerr = err =
104 | new Error("package.json missing for " + self.pkgName);
105 | return fn(err);
106 | }
107 | var pkg = self._pkg = JSON.parse(body);
108 | fn(null, pkg);
109 | });
110 | },
111 | __isInstalled: function (fn) {
112 | var self = this;
113 | fs.stat(this.dir, function (err, stat) {
114 | if (err || !stat.isDirectory()) {
115 | err = new Error(self.name + " is not installed via npm.");
116 | fn(err, false);
117 | } else {
118 | fn(null, true);
119 | }
120 | });
121 | }
122 | };
123 |
124 | Object.defineProperty(NpmModule.prototype, 'dir', {
125 | get: function () {
126 | return path.join(npm.dir, this.name, this.ver, 'package');
127 | }
128 | });
129 |
130 | // Wrap npm in a promise, for manageable async middleware
131 | NpmModule.isNpmLoaded = false;
132 | NpmModule.callbacks = []; // Callbacks for loaded event
133 | var proto = NpmModule.prototype;
134 | for (var k in proto) {
135 | proto[k.slice(2)] = (function (k) {
136 | return function () {
137 | if (NpmModule.isNpmLoaded) return proto[k].apply(this, arguments);
138 | NpmModule.callbacks.push([proto[k], this, arguments]);
139 | };
140 | })(k);
141 | }
142 | npm.load( function () {
143 | NpmModule.isNpmLoaded = true;
144 | var callbacks = NpmModule.callbacks;
145 | for (var i = 0, l = callbacks.length; i < l; i++) {
146 | callbacks[i][0].apply(callbacks[i][1], callbacks[i][2]);
147 | }
148 | });
149 |
150 | module.exports = NpmModule;
151 |
--------------------------------------------------------------------------------
/lib/templates/boilerplate.js:
--------------------------------------------------------------------------------
1 | function browserRequire (path) {
2 | var mod = browserRequire.modules[path + '.js'] ||
3 | browserRequire.modules[path + '/index.js'];
4 | if (!mod) throw new Error("Missing module " + path);
5 | return mod.cached || mod();
6 | }
7 | browserRequire.modules = {};
8 |
--------------------------------------------------------------------------------
/lib/templates/inject_module.js:
--------------------------------------------------------------------------------
1 | browserRequire.modules[$module] = function () {
2 | var module = {}
3 | , exports = module.exports = {}
4 | , require = function (module) {
5 | if (module.charAt(0) !== '.' && module.charAt(0) !== '/')
6 | return browserRequire(module);
7 | var dir = ($dir === '/' ? '.' : $dir)
8 | , parts = module.split('/')
9 | , dirparts = dir.split('/')
10 | , aboveBase = false;
11 | for (var i = 0, l = parts.length, part; i < l; i++) {
12 | part = parts[i];
13 | if (part === '.') continue;
14 | else if (part === '..') {
15 | if (dirparts.length === 1) {
16 | dirparts.pop();
17 | aboveBase = true;
18 | }
19 | if (!aboveBase) dirparts.pop();
20 | else dirparts.push(part);
21 | } else dirparts.push(part);
22 | }
23 | return browserRequire(dirparts.join('/'));
24 | };
25 |
26 | (function () {
27 | $src
28 | }).call(module.exports);
29 |
30 | return browserRequire.modules[$module].cached = module.exports;
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "browser-require",
3 | "description": "Use CommonJS and NPM modules from the browser",
4 | "version": "0.1.6",
5 | "homepage": "https://github.com/bnoguchi/browser-require",
6 | "repository": "https://github.com/bnoguchi/browser-require.git",
7 | "author": "Brian N Noguchi (https://github.com/bnoguchi)",
8 | "main": "./lib/browser-require.js",
9 | "directories": {
10 | "lib": ""
11 | },
12 | "bin": {
13 | "browser-require": "./bin/browser-require"
14 | },
15 | "scripts": {
16 | "test": "make test"
17 | },
18 | "devDependencies": {
19 | "expresso": ">= 0.7.2"
20 | },
21 | "dependencies": {
22 | "dependency-promise": ">=0.2.0"
23 | },
24 | "engines": {
25 | "node": ">=0.2.6"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/compiled/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | QUnit Test Suite
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/compiled/js/above.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "I'm an ancestor of the descendant";
3 | };
4 |
--------------------------------------------------------------------------------
/test/compiled/js/level1.js:
--------------------------------------------------------------------------------
1 | var level3 = require('./level2/level3');
2 |
3 | module.exports = function () {
4 | return "level 1 => " + level3();
5 | };
6 |
--------------------------------------------------------------------------------
/test/compiled/js/level2.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "level 2";
3 | };
4 |
--------------------------------------------------------------------------------
/test/compiled/js/level2/level3.js:
--------------------------------------------------------------------------------
1 | var level2 = require('../level2');
2 |
3 | module.exports = function () {
4 | return "level 3 => " + level2();
5 | };
6 |
--------------------------------------------------------------------------------
/test/compiled/js/nested/aboveNested.js:
--------------------------------------------------------------------------------
1 | var above = require('../above');
2 | module.exports = function () {
3 | return above();
4 | };
5 |
--------------------------------------------------------------------------------
/test/compiled/js/nested/nest.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "I'm a descendant";
3 | };
4 |
--------------------------------------------------------------------------------
/test/compiled/js/rel.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "Hello World!";
3 | };
4 |
--------------------------------------------------------------------------------
/test/compiled/js/test.js:
--------------------------------------------------------------------------------
1 | var rel = require('./rel')
2 | , nested = require('./nested/nest')
3 | , aboveNested = require('./nested/aboveNested')
4 | , elevator = require('./level1')
5 | , hell = require('../../hell');
6 |
7 | window.module('require relatives');
8 | asyncTest('relative sibling requires', function () {
9 | equals(rel(), 'Hello World!');
10 | start();
11 | });
12 | asyncTest('relative nested requires', function () {
13 | equals(nested(), "I'm a descendant");
14 | start();
15 | });
16 | asyncTest('relative above nested requires', function () {
17 | equals(aboveNested(), "I'm an ancestor of the descendant");
18 | start();
19 | });
20 | asyncTest('elevator requires', function () {
21 | equals(elevator(), "level 1 => level 3 => level 2");
22 | start();
23 | });
24 | asyncTest('level before base dir require', function () {
25 | equals(hell(), "PYRO");
26 | start();
27 | });
28 |
29 | window.module('require npm');
30 | asyncTest('simple npm modules', function () {
31 | var _ = require('underscore');
32 | equals(10, _.reduce([1, 2, 3, 4], function (sum, num) {
33 | sum += num;
34 | return sum;
35 | }));
36 | start();
37 | });
38 | asyncTest('npm submodules', function () {
39 | var Set = require('data-structures-js/set')
40 | , s = new Set(['look', 'ma', 'no', 'hands']);
41 | s.add('look');
42 | ok(s.contains('ma'));
43 | start();
44 | });
45 | asyncTest('npm modules that use relative modules', function () {
46 | // The following file requires ./entities;
47 | // See https://github.com/chriso/node-validator/blob/master/lib/xss.js
48 | var clean = require('validator/xss').clean;
49 | equals('function', typeof clean);
50 | start();
51 | });
52 |
--------------------------------------------------------------------------------
/test/compiled/server.js:
--------------------------------------------------------------------------------
1 | require.paths.unshift('..');
2 | var path = require('path');
3 | require.paths.unshift(path.join(__dirname, '..', '..'));
4 | var connect = require('connect');
5 | var app = connect.createServer();
6 | var exposeRequire = require('browser-require');
7 | app.use(exposeRequire({
8 | base: __dirname
9 | , compiled: true
10 | }));
11 | app.use(connect.staticProvider(__dirname));
12 | app.listen(4321);
13 | console.log("Server running at http://127.0.0.1:4321");
14 | process.title = "brtest";
15 |
--------------------------------------------------------------------------------
/test/hell.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "PYRO";
3 | };
4 |
--------------------------------------------------------------------------------
/test/nested/css/qunit.css:
--------------------------------------------------------------------------------
1 | /** Font Family and Sizes */
2 |
3 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
4 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
5 | }
6 |
7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
8 | #qunit-tests { font-size: smaller; }
9 |
10 |
11 | /** Resets */
12 |
13 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
14 | margin: 0;
15 | padding: 0;
16 | }
17 |
18 |
19 | /** Header */
20 |
21 | #qunit-header {
22 | padding: 0.5em 0 0.5em 1em;
23 |
24 | color: #8699a4;
25 | background-color: #0d3349;
26 |
27 | font-size: 1.5em;
28 | line-height: 1em;
29 | font-weight: normal;
30 |
31 | border-radius: 15px 15px 0 0;
32 | -moz-border-radius: 15px 15px 0 0;
33 | -webkit-border-top-right-radius: 15px;
34 | -webkit-border-top-left-radius: 15px;
35 | }
36 |
37 | #qunit-header a {
38 | text-decoration: none;
39 | color: #c2ccd1;
40 | }
41 |
42 | #qunit-header a:hover,
43 | #qunit-header a:focus {
44 | color: #fff;
45 | }
46 |
47 | #qunit-banner {
48 | height: 5px;
49 | }
50 |
51 | #qunit-testrunner-toolbar {
52 | padding: 0.5em 0 0.5em 2em;
53 | color: #5E740B;
54 | background-color: #eee;
55 | }
56 |
57 | #qunit-userAgent {
58 | padding: 0.5em 0 0.5em 2.5em;
59 | background-color: #2b81af;
60 | color: #fff;
61 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
62 | }
63 |
64 |
65 | /** Tests: Pass/Fail */
66 |
67 | #qunit-tests {
68 | list-style-position: inside;
69 | }
70 |
71 | #qunit-tests li {
72 | padding: 0.4em 0.5em 0.4em 2.5em;
73 | border-bottom: 1px solid #fff;
74 | list-style-position: inside;
75 | }
76 |
77 | #qunit-tests.hidepass li.pass {
78 | display: none;
79 | }
80 |
81 | #qunit-tests li strong {
82 | cursor: pointer;
83 | }
84 |
85 | #qunit-tests ol {
86 | margin-top: 0.5em;
87 | padding: 0.5em;
88 |
89 | background-color: #fff;
90 |
91 | border-radius: 15px;
92 | -moz-border-radius: 15px;
93 | -webkit-border-radius: 15px;
94 |
95 | box-shadow: inset 0px 2px 13px #999;
96 | -moz-box-shadow: inset 0px 2px 13px #999;
97 | -webkit-box-shadow: inset 0px 2px 13px #999;
98 | }
99 |
100 | #qunit-tests table {
101 | border-collapse: collapse;
102 | margin-top: .2em;
103 | }
104 |
105 | #qunit-tests th {
106 | text-align: right;
107 | vertical-align: top;
108 | padding: 0 .5em 0 0;
109 | }
110 |
111 | #qunit-tests td {
112 | vertical-align: top;
113 | }
114 |
115 | #qunit-tests pre {
116 | margin: 0;
117 | white-space: pre-wrap;
118 | word-wrap: break-word;
119 | }
120 |
121 | #qunit-tests del {
122 | background-color: #e0f2be;
123 | color: #374e0c;
124 | text-decoration: none;
125 | }
126 |
127 | #qunit-tests ins {
128 | background-color: #ffcaca;
129 | color: #500;
130 | text-decoration: none;
131 | }
132 |
133 | /*** Test Counts */
134 |
135 | #qunit-tests b.counts { color: black; }
136 | #qunit-tests b.passed { color: #5E740B; }
137 | #qunit-tests b.failed { color: #710909; }
138 |
139 | #qunit-tests li li {
140 | margin: 0.5em;
141 | padding: 0.4em 0.5em 0.4em 0.5em;
142 | background-color: #fff;
143 | border-bottom: none;
144 | list-style-position: inside;
145 | }
146 |
147 | /*** Passing Styles */
148 |
149 | #qunit-tests li li.pass {
150 | color: #5E740B;
151 | background-color: #fff;
152 | border-left: 26px solid #C6E746;
153 | }
154 |
155 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
156 | #qunit-tests .pass .test-name { color: #366097; }
157 |
158 | #qunit-tests .pass .test-actual,
159 | #qunit-tests .pass .test-expected { color: #999999; }
160 |
161 | #qunit-banner.qunit-pass { background-color: #C6E746; }
162 |
163 | /*** Failing Styles */
164 |
165 | #qunit-tests li li.fail {
166 | color: #710909;
167 | background-color: #fff;
168 | border-left: 26px solid #EE5757;
169 | }
170 |
171 | #qunit-tests > li:last-child {
172 | border-radius: 0 0 15px 15px;
173 | -moz-border-radius: 0 0 15px 15px;
174 | -webkit-border-bottom-right-radius: 15px;
175 | -webkit-border-bottom-left-radius: 15px;
176 | }
177 |
178 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
179 | #qunit-tests .fail .test-name,
180 | #qunit-tests .fail .module-name { color: #000000; }
181 |
182 | #qunit-tests .fail .test-actual { color: #EE5757; }
183 | #qunit-tests .fail .test-expected { color: green; }
184 |
185 | #qunit-banner.qunit-fail { background-color: #EE5757; }
186 |
187 |
188 | /** Result */
189 |
190 | #qunit-testresult {
191 | padding: 0.5em 0.5em 0.5em 2.5em;
192 |
193 | color: #2b81af;
194 | background-color: #D2E0E6;
195 |
196 | border-bottom: 1px solid white;
197 | }
198 |
199 | /** Fixture */
200 |
201 | #qunit-fixture {
202 | position: absolute;
203 | top: -10000px;
204 | left: -10000px;
205 | }
206 |
--------------------------------------------------------------------------------
/test/nested/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | QUnit Test Suite
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/nested/js/above.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "I'm an ancestor of the descendant";
3 | };
4 |
--------------------------------------------------------------------------------
/test/nested/js/level1.js:
--------------------------------------------------------------------------------
1 | var level3 = require('./level2/level3');
2 |
3 | module.exports = function () {
4 | return "level 1 => " + level3();
5 | };
6 |
--------------------------------------------------------------------------------
/test/nested/js/level2.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "level 2";
3 | };
4 |
--------------------------------------------------------------------------------
/test/nested/js/level2/level3.js:
--------------------------------------------------------------------------------
1 | var level2 = require('../level2');
2 |
3 | module.exports = function () {
4 | return "level 3 => " + level2();
5 | };
6 |
--------------------------------------------------------------------------------
/test/nested/js/nested/aboveNested.js:
--------------------------------------------------------------------------------
1 | var above = require('../above');
2 | module.exports = function () {
3 | return above();
4 | };
5 |
--------------------------------------------------------------------------------
/test/nested/js/nested/nest.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "I'm a descendant";
3 | };
4 |
--------------------------------------------------------------------------------
/test/nested/js/qunit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * QUnit - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var defined = {
14 | setTimeout: typeof window.setTimeout !== "undefined",
15 | sessionStorage: (function() {
16 | try {
17 | return !!sessionStorage.getItem;
18 | } catch(e){
19 | return false;
20 | }
21 | })()
22 | }
23 |
24 | var testId = 0;
25 |
26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
27 | this.name = name;
28 | this.testName = testName;
29 | this.expected = expected;
30 | this.testEnvironmentArg = testEnvironmentArg;
31 | this.async = async;
32 | this.callback = callback;
33 | this.assertions = [];
34 | };
35 | Test.prototype = {
36 | init: function() {
37 | var tests = id("qunit-tests");
38 | if (tests) {
39 | var b = document.createElement("strong");
40 | b.innerHTML = "Running " + this.name;
41 | var li = document.createElement("li");
42 | li.appendChild( b );
43 | li.id = this.id = "test-output" + testId++;
44 | tests.appendChild( li );
45 | }
46 | },
47 | setup: function() {
48 | if (this.module != config.previousModule) {
49 | if ( config.previousModule ) {
50 | QUnit.moduleDone( {
51 | name: config.previousModule,
52 | failed: config.moduleStats.bad,
53 | passed: config.moduleStats.all - config.moduleStats.bad,
54 | total: config.moduleStats.all
55 | } );
56 | }
57 | config.previousModule = this.module;
58 | config.moduleStats = { all: 0, bad: 0 };
59 | QUnit.moduleStart( {
60 | name: this.module
61 | } );
62 | }
63 |
64 | config.current = this;
65 | this.testEnvironment = extend({
66 | setup: function() {},
67 | teardown: function() {}
68 | }, this.moduleTestEnvironment);
69 | if (this.testEnvironmentArg) {
70 | extend(this.testEnvironment, this.testEnvironmentArg);
71 | }
72 |
73 | QUnit.testStart( {
74 | name: this.testName
75 | } );
76 |
77 | // allow utility functions to access the current test environment
78 | // TODO why??
79 | QUnit.current_testEnvironment = this.testEnvironment;
80 |
81 | try {
82 | if ( !config.pollution ) {
83 | saveGlobal();
84 | }
85 |
86 | this.testEnvironment.setup.call(this.testEnvironment);
87 | } catch(e) {
88 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
89 | }
90 | },
91 | run: function() {
92 | if ( this.async ) {
93 | QUnit.stop();
94 | }
95 |
96 | if ( config.notrycatch ) {
97 | this.callback.call(this.testEnvironment);
98 | return;
99 | }
100 | try {
101 | this.callback.call(this.testEnvironment);
102 | } catch(e) {
103 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
104 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
105 | // else next test will carry the responsibility
106 | saveGlobal();
107 |
108 | // Restart the tests if they're blocking
109 | if ( config.blocking ) {
110 | start();
111 | }
112 | }
113 | },
114 | teardown: function() {
115 | try {
116 | checkPollution();
117 | this.testEnvironment.teardown.call(this.testEnvironment);
118 | } catch(e) {
119 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
120 | }
121 | },
122 | finish: function() {
123 | if ( this.expected && this.expected != this.assertions.length ) {
124 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
125 | }
126 |
127 | var good = 0, bad = 0,
128 | tests = id("qunit-tests");
129 |
130 | config.stats.all += this.assertions.length;
131 | config.moduleStats.all += this.assertions.length;
132 |
133 | if ( tests ) {
134 | var ol = document.createElement("ol");
135 |
136 | for ( var i = 0; i < this.assertions.length; i++ ) {
137 | var assertion = this.assertions[i];
138 |
139 | var li = document.createElement("li");
140 | li.className = assertion.result ? "pass" : "fail";
141 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
142 | ol.appendChild( li );
143 |
144 | if ( assertion.result ) {
145 | good++;
146 | } else {
147 | bad++;
148 | config.stats.bad++;
149 | config.moduleStats.bad++;
150 | }
151 | }
152 |
153 | // store result when possible
154 | defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad);
155 |
156 | if (bad == 0) {
157 | ol.style.display = "none";
158 | }
159 |
160 | var b = document.createElement("strong");
161 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
162 |
163 | addEvent(b, "click", function() {
164 | var next = b.nextSibling, display = next.style.display;
165 | next.style.display = display === "none" ? "block" : "none";
166 | });
167 |
168 | addEvent(b, "dblclick", function(e) {
169 | var target = e && e.target ? e.target : window.event.srcElement;
170 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
171 | target = target.parentNode;
172 | }
173 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
174 | window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
175 | }
176 | });
177 |
178 | var li = id(this.id);
179 | li.className = bad ? "fail" : "pass";
180 | li.removeChild( li.firstChild );
181 | li.appendChild( b );
182 | li.appendChild( ol );
183 |
184 | } else {
185 | for ( var i = 0; i < this.assertions.length; i++ ) {
186 | if ( !this.assertions[i].result ) {
187 | bad++;
188 | config.stats.bad++;
189 | config.moduleStats.bad++;
190 | }
191 | }
192 | }
193 |
194 | try {
195 | QUnit.reset();
196 | } catch(e) {
197 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
198 | }
199 |
200 | QUnit.testDone( {
201 | name: this.testName,
202 | failed: bad,
203 | passed: this.assertions.length - bad,
204 | total: this.assertions.length
205 | } );
206 | },
207 |
208 | queue: function() {
209 | var test = this;
210 | synchronize(function() {
211 | test.init();
212 | });
213 | function run() {
214 | // each of these can by async
215 | synchronize(function() {
216 | test.setup();
217 | });
218 | synchronize(function() {
219 | test.run();
220 | });
221 | synchronize(function() {
222 | test.teardown();
223 | });
224 | synchronize(function() {
225 | test.finish();
226 | });
227 | }
228 | // defer when previous test run passed, if storage is available
229 | var bad = defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName);
230 | if (bad) {
231 | run();
232 | } else {
233 | synchronize(run);
234 | };
235 | }
236 |
237 | }
238 |
239 | var QUnit = {
240 |
241 | // call on start of module test to prepend name to all tests
242 | module: function(name, testEnvironment) {
243 | config.currentModule = name;
244 | config.currentModuleTestEnviroment = testEnvironment;
245 | },
246 |
247 | asyncTest: function(testName, expected, callback) {
248 | if ( arguments.length === 2 ) {
249 | callback = expected;
250 | expected = 0;
251 | }
252 |
253 | QUnit.test(testName, expected, callback, true);
254 | },
255 |
256 | test: function(testName, expected, callback, async) {
257 | var name = '' + testName + '', testEnvironmentArg;
258 |
259 | if ( arguments.length === 2 ) {
260 | callback = expected;
261 | expected = null;
262 | }
263 | // is 2nd argument a testEnvironment?
264 | if ( expected && typeof expected === 'object') {
265 | testEnvironmentArg = expected;
266 | expected = null;
267 | }
268 |
269 | if ( config.currentModule ) {
270 | name = '' + config.currentModule + ": " + name;
271 | }
272 |
273 | if ( !validTest(config.currentModule + ": " + testName) ) {
274 | return;
275 | }
276 |
277 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
278 | test.module = config.currentModule;
279 | test.moduleTestEnvironment = config.currentModuleTestEnviroment;
280 | test.queue();
281 | },
282 |
283 | /**
284 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
285 | */
286 | expect: function(asserts) {
287 | config.current.expected = asserts;
288 | },
289 |
290 | /**
291 | * Asserts true.
292 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
293 | */
294 | ok: function(a, msg) {
295 | a = !!a;
296 | var details = {
297 | result: a,
298 | message: msg
299 | };
300 | msg = escapeHtml(msg);
301 | QUnit.log(details);
302 | config.current.assertions.push({
303 | result: a,
304 | message: msg
305 | });
306 | },
307 |
308 | /**
309 | * Checks that the first two arguments are equal, with an optional message.
310 | * Prints out both actual and expected values.
311 | *
312 | * Prefered to ok( actual == expected, message )
313 | *
314 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
315 | *
316 | * @param Object actual
317 | * @param Object expected
318 | * @param String message (optional)
319 | */
320 | equal: function(actual, expected, message) {
321 | QUnit.push(expected == actual, actual, expected, message);
322 | },
323 |
324 | notEqual: function(actual, expected, message) {
325 | QUnit.push(expected != actual, actual, expected, message);
326 | },
327 |
328 | deepEqual: function(actual, expected, message) {
329 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
330 | },
331 |
332 | notDeepEqual: function(actual, expected, message) {
333 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
334 | },
335 |
336 | strictEqual: function(actual, expected, message) {
337 | QUnit.push(expected === actual, actual, expected, message);
338 | },
339 |
340 | notStrictEqual: function(actual, expected, message) {
341 | QUnit.push(expected !== actual, actual, expected, message);
342 | },
343 |
344 | raises: function(block, expected, message) {
345 | var actual, ok = false;
346 |
347 | if (typeof expected === 'string') {
348 | message = expected;
349 | expected = null;
350 | }
351 |
352 | try {
353 | block();
354 | } catch (e) {
355 | actual = e;
356 | }
357 |
358 | if (actual) {
359 | // we don't want to validate thrown error
360 | if (!expected) {
361 | ok = true;
362 | // expected is a regexp
363 | } else if (QUnit.objectType(expected) === "regexp") {
364 | ok = expected.test(actual);
365 | // expected is a constructor
366 | } else if (actual instanceof expected) {
367 | ok = true;
368 | // expected is a validation function which returns true is validation passed
369 | } else if (expected.call({}, actual) === true) {
370 | ok = true;
371 | }
372 | }
373 |
374 | QUnit.ok(ok, message);
375 | },
376 |
377 | start: function() {
378 | config.semaphore--;
379 | if (config.semaphore > 0) {
380 | // don't start until equal number of stop-calls
381 | return;
382 | }
383 | if (config.semaphore < 0) {
384 | // ignore if start is called more often then stop
385 | config.semaphore = 0;
386 | }
387 | // A slight delay, to avoid any current callbacks
388 | if ( defined.setTimeout ) {
389 | window.setTimeout(function() {
390 | if ( config.timeout ) {
391 | clearTimeout(config.timeout);
392 | }
393 |
394 | config.blocking = false;
395 | process();
396 | }, 13);
397 | } else {
398 | config.blocking = false;
399 | process();
400 | }
401 | },
402 |
403 | stop: function(timeout) {
404 | config.semaphore++;
405 | config.blocking = true;
406 |
407 | if ( timeout && defined.setTimeout ) {
408 | clearTimeout(config.timeout);
409 | config.timeout = window.setTimeout(function() {
410 | QUnit.ok( false, "Test timed out" );
411 | QUnit.start();
412 | }, timeout);
413 | }
414 | }
415 |
416 | };
417 |
418 | // Backwards compatibility, deprecated
419 | QUnit.equals = QUnit.equal;
420 | QUnit.same = QUnit.deepEqual;
421 |
422 | // Maintain internal state
423 | var config = {
424 | // The queue of tests to run
425 | queue: [],
426 |
427 | // block until document ready
428 | blocking: true
429 | };
430 |
431 | // Load paramaters
432 | (function() {
433 | var location = window.location || { search: "", protocol: "file:" },
434 | GETParams = location.search.slice(1).split('&');
435 |
436 | for ( var i = 0; i < GETParams.length; i++ ) {
437 | GETParams[i] = decodeURIComponent( GETParams[i] );
438 | if ( GETParams[i] === "noglobals" ) {
439 | GETParams.splice( i, 1 );
440 | i--;
441 | config.noglobals = true;
442 | } else if ( GETParams[i] === "notrycatch" ) {
443 | GETParams.splice( i, 1 );
444 | i--;
445 | config.notrycatch = true;
446 | } else if ( GETParams[i].search('=') > -1 ) {
447 | GETParams.splice( i, 1 );
448 | i--;
449 | }
450 | }
451 |
452 | // restrict modules/tests by get parameters
453 | config.filters = GETParams;
454 |
455 | // Figure out if we're running the tests from a server or not
456 | QUnit.isLocal = !!(location.protocol === 'file:');
457 | })();
458 |
459 | // Expose the API as global variables, unless an 'exports'
460 | // object exists, in that case we assume we're in CommonJS
461 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
462 | extend(window, QUnit);
463 | window.QUnit = QUnit;
464 | } else {
465 | extend(exports, QUnit);
466 | exports.QUnit = QUnit;
467 | }
468 |
469 | // define these after exposing globals to keep them in these QUnit namespace only
470 | extend(QUnit, {
471 | config: config,
472 |
473 | // Initialize the configuration options
474 | init: function() {
475 | extend(config, {
476 | stats: { all: 0, bad: 0 },
477 | moduleStats: { all: 0, bad: 0 },
478 | started: +new Date,
479 | updateRate: 1000,
480 | blocking: false,
481 | autostart: true,
482 | autorun: false,
483 | filters: [],
484 | queue: [],
485 | semaphore: 0
486 | });
487 |
488 | var tests = id( "qunit-tests" ),
489 | banner = id( "qunit-banner" ),
490 | result = id( "qunit-testresult" );
491 |
492 | if ( tests ) {
493 | tests.innerHTML = "";
494 | }
495 |
496 | if ( banner ) {
497 | banner.className = "";
498 | }
499 |
500 | if ( result ) {
501 | result.parentNode.removeChild( result );
502 | }
503 |
504 | if ( tests ) {
505 | result = document.createElement( "p" );
506 | result.id = "qunit-testresult";
507 | result.className = "result";
508 | tests.parentNode.insertBefore( result, tests );
509 | result.innerHTML = 'Running...
';
510 | }
511 | },
512 |
513 | /**
514 | * Resets the test setup. Useful for tests that modify the DOM.
515 | *
516 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
517 | */
518 | reset: function() {
519 | if ( window.jQuery ) {
520 | jQuery( "#main, #qunit-fixture" ).html( config.fixture );
521 | } else {
522 | var main = id( 'main' ) || id( 'qunit-fixture' );
523 | if ( main ) {
524 | main.innerHTML = config.fixture;
525 | }
526 | }
527 | },
528 |
529 | /**
530 | * Trigger an event on an element.
531 | *
532 | * @example triggerEvent( document.body, "click" );
533 | *
534 | * @param DOMElement elem
535 | * @param String type
536 | */
537 | triggerEvent: function( elem, type, event ) {
538 | if ( document.createEvent ) {
539 | event = document.createEvent("MouseEvents");
540 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
541 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
542 | elem.dispatchEvent( event );
543 |
544 | } else if ( elem.fireEvent ) {
545 | elem.fireEvent("on"+type);
546 | }
547 | },
548 |
549 | // Safe object type checking
550 | is: function( type, obj ) {
551 | return QUnit.objectType( obj ) == type;
552 | },
553 |
554 | objectType: function( obj ) {
555 | if (typeof obj === "undefined") {
556 | return "undefined";
557 |
558 | // consider: typeof null === object
559 | }
560 | if (obj === null) {
561 | return "null";
562 | }
563 |
564 | var type = Object.prototype.toString.call( obj )
565 | .match(/^\[object\s(.*)\]$/)[1] || '';
566 |
567 | switch (type) {
568 | case 'Number':
569 | if (isNaN(obj)) {
570 | return "nan";
571 | } else {
572 | return "number";
573 | }
574 | case 'String':
575 | case 'Boolean':
576 | case 'Array':
577 | case 'Date':
578 | case 'RegExp':
579 | case 'Function':
580 | return type.toLowerCase();
581 | }
582 | if (typeof obj === "object") {
583 | return "object";
584 | }
585 | return undefined;
586 | },
587 |
588 | push: function(result, actual, expected, message) {
589 | var details = {
590 | result: result,
591 | message: message,
592 | actual: actual,
593 | expected: expected
594 | };
595 |
596 | message = escapeHtml(message) || (result ? "okay" : "failed");
597 | message = '' + message + "";
598 | expected = escapeHtml(QUnit.jsDump.parse(expected));
599 | actual = escapeHtml(QUnit.jsDump.parse(actual));
600 | var output = message + 'Expected: | ' + expected + ' |
';
601 | if (actual != expected) {
602 | output += 'Result: | ' + actual + ' |
';
603 | output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
604 | }
605 | if (!result) {
606 | var source = sourceFromStacktrace();
607 | if (source) {
608 | details.source = source;
609 | output += 'Source: | ' + source +' |
';
610 | }
611 | }
612 | output += "
";
613 |
614 | QUnit.log(details);
615 |
616 | config.current.assertions.push({
617 | result: !!result,
618 | message: output
619 | });
620 | },
621 |
622 | // Logging callbacks; all receive a single argument with the listed properties
623 | // run test/logs.html for any related changes
624 | begin: function() {},
625 | // done: { failed, passed, total, runtime }
626 | done: function() {},
627 | // log: { result, actual, expected, message }
628 | log: function() {},
629 | // testStart: { name }
630 | testStart: function() {},
631 | // testDone: { name, failed, passed, total }
632 | testDone: function() {},
633 | // moduleStart: { name }
634 | moduleStart: function() {},
635 | // moduleDone: { name, failed, passed, total }
636 | moduleDone: function() {}
637 | });
638 |
639 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
640 | config.autorun = true;
641 | }
642 |
643 | addEvent(window, "load", function() {
644 | QUnit.begin({});
645 |
646 | // Initialize the config, saving the execution queue
647 | var oldconfig = extend({}, config);
648 | QUnit.init();
649 | extend(config, oldconfig);
650 |
651 | config.blocking = false;
652 |
653 | var userAgent = id("qunit-userAgent");
654 | if ( userAgent ) {
655 | userAgent.innerHTML = navigator.userAgent;
656 | }
657 | var banner = id("qunit-header");
658 | if ( banner ) {
659 | var paramsIndex = location.href.lastIndexOf(location.search);
660 | if ( paramsIndex > -1 ) {
661 | var mainPageLocation = location.href.slice(0, paramsIndex);
662 | if ( mainPageLocation == location.href ) {
663 | banner.innerHTML = ' ' + banner.innerHTML + ' ';
664 | } else {
665 | var testName = decodeURIComponent(location.search.slice(1));
666 | banner.innerHTML = '' + banner.innerHTML + ' › ' + testName + '';
667 | }
668 | }
669 | }
670 |
671 | var toolbar = id("qunit-testrunner-toolbar");
672 | if ( toolbar ) {
673 | var filter = document.createElement("input");
674 | filter.type = "checkbox";
675 | filter.id = "qunit-filter-pass";
676 | addEvent( filter, "click", function() {
677 | var ol = document.getElementById("qunit-tests");
678 | if ( filter.checked ) {
679 | ol.className = ol.className + " hidepass";
680 | } else {
681 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
682 | ol.className = tmp.replace(/ hidepass /, " ");
683 | }
684 | if ( defined.sessionStorage ) {
685 | sessionStorage.setItem("qunit-filter-passed-tests", filter.checked ? "true" : "");
686 | }
687 | });
688 | if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
689 | filter.checked = true;
690 | var ol = document.getElementById("qunit-tests");
691 | ol.className = ol.className + " hidepass";
692 | }
693 | toolbar.appendChild( filter );
694 |
695 | var label = document.createElement("label");
696 | label.setAttribute("for", "qunit-filter-pass");
697 | label.innerHTML = "Hide passed tests";
698 | toolbar.appendChild( label );
699 | }
700 |
701 | var main = id('main') || id('qunit-fixture');
702 | if ( main ) {
703 | config.fixture = main.innerHTML;
704 | }
705 |
706 | if (config.autostart) {
707 | QUnit.start();
708 | }
709 | });
710 |
711 | function done() {
712 | config.autorun = true;
713 |
714 | // Log the last module results
715 | if ( config.currentModule ) {
716 | QUnit.moduleDone( {
717 | name: config.currentModule,
718 | failed: config.moduleStats.bad,
719 | passed: config.moduleStats.all - config.moduleStats.bad,
720 | total: config.moduleStats.all
721 | } );
722 | }
723 |
724 | var banner = id("qunit-banner"),
725 | tests = id("qunit-tests"),
726 | runtime = +new Date - config.started,
727 | passed = config.stats.all - config.stats.bad,
728 | html = [
729 | 'Tests completed in ',
730 | runtime,
731 | ' milliseconds.
',
732 | '',
733 | passed,
734 | ' tests of ',
735 | config.stats.all,
736 | ' passed, ',
737 | config.stats.bad,
738 | ' failed.'
739 | ].join('');
740 |
741 | if ( banner ) {
742 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
743 | }
744 |
745 | if ( tests ) {
746 | id( "qunit-testresult" ).innerHTML = html;
747 | }
748 |
749 | QUnit.done( {
750 | failed: config.stats.bad,
751 | passed: passed,
752 | total: config.stats.all,
753 | runtime: runtime
754 | } );
755 | }
756 |
757 | function validTest( name ) {
758 | var i = config.filters.length,
759 | run = false;
760 |
761 | if ( !i ) {
762 | return true;
763 | }
764 |
765 | while ( i-- ) {
766 | var filter = config.filters[i],
767 | not = filter.charAt(0) == '!';
768 |
769 | if ( not ) {
770 | filter = filter.slice(1);
771 | }
772 |
773 | if ( name.indexOf(filter) !== -1 ) {
774 | return !not;
775 | }
776 |
777 | if ( not ) {
778 | run = true;
779 | }
780 | }
781 |
782 | return run;
783 | }
784 |
785 | // so far supports only Firefox, Chrome and Opera (buggy)
786 | // could be extended in the future to use something like https://github.com/csnover/TraceKit
787 | function sourceFromStacktrace() {
788 | try {
789 | throw new Error();
790 | } catch ( e ) {
791 | if (e.stacktrace) {
792 | // Opera
793 | return e.stacktrace.split("\n")[6];
794 | } else if (e.stack) {
795 | // Firefox, Chrome
796 | return e.stack.split("\n")[4];
797 | }
798 | }
799 | }
800 |
801 | function escapeHtml(s) {
802 | if (!s) {
803 | return "";
804 | }
805 | s = s + "";
806 | return s.replace(/[\&"<>\\]/g, function(s) {
807 | switch(s) {
808 | case "&": return "&";
809 | case "\\": return "\\\\";
810 | case '"': return '\"';
811 | case "<": return "<";
812 | case ">": return ">";
813 | default: return s;
814 | }
815 | });
816 | }
817 |
818 | function synchronize( callback ) {
819 | config.queue.push( callback );
820 |
821 | if ( config.autorun && !config.blocking ) {
822 | process();
823 | }
824 | }
825 |
826 | function process() {
827 | var start = (new Date()).getTime();
828 |
829 | while ( config.queue.length && !config.blocking ) {
830 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
831 | config.queue.shift()();
832 | } else {
833 | window.setTimeout( process, 13 );
834 | break;
835 | }
836 | }
837 | if (!config.blocking && !config.queue.length) {
838 | done();
839 | }
840 | }
841 |
842 | function saveGlobal() {
843 | config.pollution = [];
844 |
845 | if ( config.noglobals ) {
846 | for ( var key in window ) {
847 | config.pollution.push( key );
848 | }
849 | }
850 | }
851 |
852 | function checkPollution( name ) {
853 | var old = config.pollution;
854 | saveGlobal();
855 |
856 | var newGlobals = diff( old, config.pollution );
857 | if ( newGlobals.length > 0 ) {
858 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
859 | config.current.expected++;
860 | }
861 |
862 | var deletedGlobals = diff( config.pollution, old );
863 | if ( deletedGlobals.length > 0 ) {
864 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
865 | config.current.expected++;
866 | }
867 | }
868 |
869 | // returns a new Array with the elements that are in a but not in b
870 | function diff( a, b ) {
871 | var result = a.slice();
872 | for ( var i = 0; i < result.length; i++ ) {
873 | for ( var j = 0; j < b.length; j++ ) {
874 | if ( result[i] === b[j] ) {
875 | result.splice(i, 1);
876 | i--;
877 | break;
878 | }
879 | }
880 | }
881 | return result;
882 | }
883 |
884 | function fail(message, exception, callback) {
885 | if ( typeof console !== "undefined" && console.error && console.warn ) {
886 | console.error(message);
887 | console.error(exception);
888 | console.warn(callback.toString());
889 |
890 | } else if ( window.opera && opera.postError ) {
891 | opera.postError(message, exception, callback.toString);
892 | }
893 | }
894 |
895 | function extend(a, b) {
896 | for ( var prop in b ) {
897 | a[prop] = b[prop];
898 | }
899 |
900 | return a;
901 | }
902 |
903 | function addEvent(elem, type, fn) {
904 | if ( elem.addEventListener ) {
905 | elem.addEventListener( type, fn, false );
906 | } else if ( elem.attachEvent ) {
907 | elem.attachEvent( "on" + type, fn );
908 | } else {
909 | fn();
910 | }
911 | }
912 |
913 | function id(name) {
914 | return !!(typeof document !== "undefined" && document && document.getElementById) &&
915 | document.getElementById( name );
916 | }
917 |
918 | // Test for equality any JavaScript type.
919 | // Discussions and reference: http://philrathe.com/articles/equiv
920 | // Test suites: http://philrathe.com/tests/equiv
921 | // Author: Philippe Rathé
922 | QUnit.equiv = function () {
923 |
924 | var innerEquiv; // the real equiv function
925 | var callers = []; // stack to decide between skip/abort functions
926 | var parents = []; // stack to avoiding loops from circular referencing
927 |
928 | // Call the o related callback with the given arguments.
929 | function bindCallbacks(o, callbacks, args) {
930 | var prop = QUnit.objectType(o);
931 | if (prop) {
932 | if (QUnit.objectType(callbacks[prop]) === "function") {
933 | return callbacks[prop].apply(callbacks, args);
934 | } else {
935 | return callbacks[prop]; // or undefined
936 | }
937 | }
938 | }
939 |
940 | var callbacks = function () {
941 |
942 | // for string, boolean, number and null
943 | function useStrictEquality(b, a) {
944 | if (b instanceof a.constructor || a instanceof b.constructor) {
945 | // to catch short annotaion VS 'new' annotation of a declaration
946 | // e.g. var i = 1;
947 | // var j = new Number(1);
948 | return a == b;
949 | } else {
950 | return a === b;
951 | }
952 | }
953 |
954 | return {
955 | "string": useStrictEquality,
956 | "boolean": useStrictEquality,
957 | "number": useStrictEquality,
958 | "null": useStrictEquality,
959 | "undefined": useStrictEquality,
960 |
961 | "nan": function (b) {
962 | return isNaN(b);
963 | },
964 |
965 | "date": function (b, a) {
966 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
967 | },
968 |
969 | "regexp": function (b, a) {
970 | return QUnit.objectType(b) === "regexp" &&
971 | a.source === b.source && // the regex itself
972 | a.global === b.global && // and its modifers (gmi) ...
973 | a.ignoreCase === b.ignoreCase &&
974 | a.multiline === b.multiline;
975 | },
976 |
977 | // - skip when the property is a method of an instance (OOP)
978 | // - abort otherwise,
979 | // initial === would have catch identical references anyway
980 | "function": function () {
981 | var caller = callers[callers.length - 1];
982 | return caller !== Object &&
983 | typeof caller !== "undefined";
984 | },
985 |
986 | "array": function (b, a) {
987 | var i, j, loop;
988 | var len;
989 |
990 | // b could be an object literal here
991 | if ( ! (QUnit.objectType(b) === "array")) {
992 | return false;
993 | }
994 |
995 | len = a.length;
996 | if (len !== b.length) { // safe and faster
997 | return false;
998 | }
999 |
1000 | //track reference to avoid circular references
1001 | parents.push(a);
1002 | for (i = 0; i < len; i++) {
1003 | loop = false;
1004 | for(j=0;j= 0) {
1149 | type = "array";
1150 | } else {
1151 | type = typeof obj;
1152 | }
1153 | return type;
1154 | },
1155 | separator:function() {
1156 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' ';
1157 | },
1158 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1159 | if ( !this.multiline )
1160 | return '';
1161 | var chr = this.indentChar;
1162 | if ( this.HTML )
1163 | chr = chr.replace(/\t/g,' ').replace(/ /g,' ');
1164 | return Array( this._depth_ + (extra||0) ).join(chr);
1165 | },
1166 | up:function( a ) {
1167 | this._depth_ += a || 1;
1168 | },
1169 | down:function( a ) {
1170 | this._depth_ -= a || 1;
1171 | },
1172 | setParser:function( name, parser ) {
1173 | this.parsers[name] = parser;
1174 | },
1175 | // The next 3 are exposed so you can use them
1176 | quote:quote,
1177 | literal:literal,
1178 | join:join,
1179 | //
1180 | _depth_: 1,
1181 | // This is the list of parsers, to modify them, use jsDump.setParser
1182 | parsers:{
1183 | window: '[Window]',
1184 | document: '[Document]',
1185 | error:'[ERROR]', //when no parser is found, shouldn't happen
1186 | unknown: '[Unknown]',
1187 | 'null':'null',
1188 | undefined:'undefined',
1189 | 'function':function( fn ) {
1190 | var ret = 'function',
1191 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1192 | if ( name )
1193 | ret += ' ' + name;
1194 | ret += '(';
1195 |
1196 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1197 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1198 | },
1199 | array: array,
1200 | nodelist: array,
1201 | arguments: array,
1202 | object:function( map ) {
1203 | var ret = [ ];
1204 | QUnit.jsDump.up();
1205 | for ( var key in map )
1206 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
1207 | QUnit.jsDump.down();
1208 | return join( '{', ret, '}' );
1209 | },
1210 | node:function( node ) {
1211 | var open = QUnit.jsDump.HTML ? '<' : '<',
1212 | close = QUnit.jsDump.HTML ? '>' : '>';
1213 |
1214 | var tag = node.nodeName.toLowerCase(),
1215 | ret = open + tag;
1216 |
1217 | for ( var a in QUnit.jsDump.DOMAttrs ) {
1218 | var val = node[QUnit.jsDump.DOMAttrs[a]];
1219 | if ( val )
1220 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1221 | }
1222 | return ret + close + open + '/' + tag + close;
1223 | },
1224 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1225 | var l = fn.length;
1226 | if ( !l ) return '';
1227 |
1228 | var args = Array(l);
1229 | while ( l-- )
1230 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1231 | return ' ' + args.join(', ') + ' ';
1232 | },
1233 | key:quote, //object calls it internally, the key part of an item in a map
1234 | functionCode:'[code]', //function calls it internally, it's the content of the function
1235 | attribute:quote, //node calls it internally, it's an html attribute value
1236 | string:quote,
1237 | date:quote,
1238 | regexp:literal, //regex
1239 | number:literal,
1240 | 'boolean':literal
1241 | },
1242 | DOMAttrs:{//attributes to dump from nodes, name=>realName
1243 | id:'id',
1244 | name:'name',
1245 | 'class':'className'
1246 | },
1247 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1248 | indentChar:' ',//indentation unit
1249 | multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1250 | };
1251 |
1252 | return jsDump;
1253 | })();
1254 |
1255 | // from Sizzle.js
1256 | function getText( elems ) {
1257 | var ret = "", elem;
1258 |
1259 | for ( var i = 0; elems[i]; i++ ) {
1260 | elem = elems[i];
1261 |
1262 | // Get the text from text nodes and CDATA nodes
1263 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1264 | ret += elem.nodeValue;
1265 |
1266 | // Traverse everything else, except comment nodes
1267 | } else if ( elem.nodeType !== 8 ) {
1268 | ret += getText( elem.childNodes );
1269 | }
1270 | }
1271 |
1272 | return ret;
1273 | };
1274 |
1275 | /*
1276 | * Javascript Diff Algorithm
1277 | * By John Resig (http://ejohn.org/)
1278 | * Modified by Chu Alan "sprite"
1279 | *
1280 | * Released under the MIT license.
1281 | *
1282 | * More Info:
1283 | * http://ejohn.org/projects/javascript-diff-algorithm/
1284 | *
1285 | * Usage: QUnit.diff(expected, actual)
1286 | *
1287 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over"
1288 | */
1289 | QUnit.diff = (function() {
1290 | function diff(o, n){
1291 | var ns = new Object();
1292 | var os = new Object();
1293 |
1294 | for (var i = 0; i < n.length; i++) {
1295 | if (ns[n[i]] == null)
1296 | ns[n[i]] = {
1297 | rows: new Array(),
1298 | o: null
1299 | };
1300 | ns[n[i]].rows.push(i);
1301 | }
1302 |
1303 | for (var i = 0; i < o.length; i++) {
1304 | if (os[o[i]] == null)
1305 | os[o[i]] = {
1306 | rows: new Array(),
1307 | n: null
1308 | };
1309 | os[o[i]].rows.push(i);
1310 | }
1311 |
1312 | for (var i in ns) {
1313 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1314 | n[ns[i].rows[0]] = {
1315 | text: n[ns[i].rows[0]],
1316 | row: os[i].rows[0]
1317 | };
1318 | o[os[i].rows[0]] = {
1319 | text: o[os[i].rows[0]],
1320 | row: ns[i].rows[0]
1321 | };
1322 | }
1323 | }
1324 |
1325 | for (var i = 0; i < n.length - 1; i++) {
1326 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1327 | n[i + 1] == o[n[i].row + 1]) {
1328 | n[i + 1] = {
1329 | text: n[i + 1],
1330 | row: n[i].row + 1
1331 | };
1332 | o[n[i].row + 1] = {
1333 | text: o[n[i].row + 1],
1334 | row: i + 1
1335 | };
1336 | }
1337 | }
1338 |
1339 | for (var i = n.length - 1; i > 0; i--) {
1340 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1341 | n[i - 1] == o[n[i].row - 1]) {
1342 | n[i - 1] = {
1343 | text: n[i - 1],
1344 | row: n[i].row - 1
1345 | };
1346 | o[n[i].row - 1] = {
1347 | text: o[n[i].row - 1],
1348 | row: i - 1
1349 | };
1350 | }
1351 | }
1352 |
1353 | return {
1354 | o: o,
1355 | n: n
1356 | };
1357 | }
1358 |
1359 | return function(o, n){
1360 | o = o.replace(/\s+$/, '');
1361 | n = n.replace(/\s+$/, '');
1362 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1363 |
1364 | var str = "";
1365 |
1366 | var oSpace = o.match(/\s+/g);
1367 | if (oSpace == null) {
1368 | oSpace = [" "];
1369 | }
1370 | else {
1371 | oSpace.push(" ");
1372 | }
1373 | var nSpace = n.match(/\s+/g);
1374 | if (nSpace == null) {
1375 | nSpace = [" "];
1376 | }
1377 | else {
1378 | nSpace.push(" ");
1379 | }
1380 |
1381 | if (out.n.length == 0) {
1382 | for (var i = 0; i < out.o.length; i++) {
1383 | str += '' + out.o[i] + oSpace[i] + "";
1384 | }
1385 | }
1386 | else {
1387 | if (out.n[0].text == null) {
1388 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1389 | str += '' + out.o[n] + oSpace[n] + "";
1390 | }
1391 | }
1392 |
1393 | for (var i = 0; i < out.n.length; i++) {
1394 | if (out.n[i].text == null) {
1395 | str += '' + out.n[i] + nSpace[i] + "";
1396 | }
1397 | else {
1398 | var pre = "";
1399 |
1400 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1401 | pre += '' + out.o[n] + oSpace[n] + "";
1402 | }
1403 | str += " " + out.n[i].text + nSpace[i] + pre;
1404 | }
1405 | }
1406 | }
1407 |
1408 | return str;
1409 | };
1410 | })();
1411 |
1412 | })(this);
1413 |
--------------------------------------------------------------------------------
/test/nested/js/rel.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return "Hello World!";
3 | };
4 |
--------------------------------------------------------------------------------
/test/nested/js/test.js:
--------------------------------------------------------------------------------
1 | var rel = require('./rel')
2 | , nested = require('./nested/nest')
3 | , aboveNested = require('./nested/aboveNested')
4 | , elevator = require('./level1')
5 | , hell = require('../../hell');
6 |
7 | window.module('require relatives');
8 | asyncTest('relative sibling requires', function () {
9 | equals(rel(), 'Hello World!');
10 | start();
11 | });
12 | asyncTest('relative nested requires', function () {
13 | equals(nested(), "I'm a descendant");
14 | start();
15 | });
16 | asyncTest('relative above nested requires', function () {
17 | equals(aboveNested(), "I'm an ancestor of the descendant");
18 | start();
19 | });
20 | asyncTest('elevator requires', function () {
21 | equals(elevator(), "level 1 => level 3 => level 2");
22 | start();
23 | });
24 | asyncTest('level before base dir require', function () {
25 | equals(hell(), "PYRO");
26 | start();
27 | });
28 |
29 | window.module('require npm');
30 | asyncTest('simple npm modules', function () {
31 | var _ = require('underscore');
32 | equals(10, _.reduce([1, 2, 3, 4], function (sum, num) {
33 | sum += num;
34 | return sum;
35 | }));
36 | start();
37 | });
38 | asyncTest('npm submodules', function () {
39 | var Set = require('data-structures-js/set')
40 | , s = new Set(['look', 'ma', 'no', 'hands']);
41 | s.add('look');
42 | ok(s.contains('ma'));
43 | start();
44 | });
45 | asyncTest('npm modules that use relative modules from a file under the npm lib dir', function () {
46 | // The following file requires ./entities
47 | // See https://github.com/chriso/node-validator/blob/master/lib/xss.js
48 | var clean = require('validator/xss').clean;
49 | equals('function', typeof clean);
50 | start();
51 | });
52 | asyncTest('npm modules that use relative modules from the main file', function () {
53 | // The following file requires ./lib/async from the 'main' index.js package file
54 | // See https://github.com/caolan/async/index.js
55 | var noConflict = require('async').noConflict;
56 | equals('function', typeof noConflict);
57 | start();
58 | });
59 |
--------------------------------------------------------------------------------
/test/nested/server.js:
--------------------------------------------------------------------------------
1 | require.paths.unshift('../../lib');
2 | var path = require('path');
3 | require.paths.unshift(path.join(__dirname, '..', '..'));
4 | var connect = require('connect');
5 | var exposeRequire = require('browser-require');
6 | var app = connect(
7 | exposeRequire({
8 | base: __dirname
9 | }),
10 | connect.static(__dirname)
11 | );
12 | app.listen(1234);
13 | console.log("Server running at http://127.0.0.1:1234");
14 | process.title = "brtest";
15 |
--------------------------------------------------------------------------------