Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit, extracted from SlimJim

  • Loading branch information...
commit b8a5bfe2b4d72bde51593f36495a78642f91eb2f 0 parents
@vojtajina authored
10 .npmignore
@@ -0,0 +1,10 @@
+./.git/
+./node_modules/
+./.idea/
+./test/
+
+.gitignore
+.npmignore
+pom.xml
+TODO.md
+jsl.conf
4 CHANGELOG.md
@@ -0,0 +1,4 @@
+### 0.0.1
+* Initial version, extracted from [SlimJim]
+
+[SlimJim]: https://github.com/vojtajina/slim-jim/
60 Jakefile
@@ -0,0 +1,60 @@
+desc('Run unit tests.');
+task('test', function() {
+ console.log('Running unit tests...');
+ jake.exec(['jasmine-node --coffee test'], complete, {stdout: true});
+});
+
+
+desc('Bump minor version, update changelog, create tag, push to github.');
+task('version', function () {
+ var fs = require('fs');
+
+ var packagePath = process.cwd() + '/package.json';
+ var pkg = JSON.parse(fs.readFileSync(packagePath).toString());
+ var versionArray = pkg.version.split('.');
+ var previousVersionTag = 'v' + pkg.version;
+
+ // bump minor version
+ versionArray.push(parseInt(versionArray.pop(), 10) + 1);
+ pkg.version = versionArray.join('.');
+
+ // Update package.json with the new version-info
+ fs.writeFileSync(packagePath, JSON.stringify(pkg, true, 2));
+
+ var TEMP_FILE = '.changelog.temp';
+ var message = 'Bump version to v' + pkg.version;
+ jake.exec([
+ // update changelog
+ 'echo "### v' + pkg.version + '" > ' + TEMP_FILE,
+ 'git log --pretty=%s ' + previousVersionTag + '..HEAD >> ' + TEMP_FILE,
+ 'echo "" >> ' + TEMP_FILE,
+ 'mvim CHANGELOG.md -c ":0r ' + TEMP_FILE + '"',
+ 'rm ' + TEMP_FILE,
+
+ // commit + push to github
+ 'git commit package.json CHANGELOG.md -m "' + message + '"',
+ 'git push origin master',
+ 'git tag -a v' + pkg.version + ' -m "Version to v' + pkg.version + '"',
+ 'git push --tags'
+ ], function () {
+ console.log(message);
+ complete();
+ });
+});
+
+
+desc('Bump version, publish to npm.');
+task('publish', ['version'], function() {
+ jake.exec([
+ 'npm publish'
+ ], function() {
+ console.log('Published to npm');
+ complete();
+ })
+});
+
+
+desc('Run JSLint check.');
+task('jsl', function() {
+ jake.exec(['jsl -conf jsl.conf'], complete, {stdout: true});
+});
0  TODO.md
No changes.
124 jsl.conf
@@ -0,0 +1,124 @@
+#
+# Configuration File for JavaScript Lint 0.3.0
+# Developed by Matthias Miller (http://www.JavaScriptLint.com)
+#
+# This configuration file can be used to lint a collection of scripts, or to enable
+# or disable warnings for scripts that are linted via the command line.
+#
+
+### Warnings
+# Enable or disable warnings based on requirements.
+# Use "+WarningName" to display or "-WarningName" to suppress.
+#
+-no_return_value # function {0} does not always return a value
++duplicate_formal # duplicate formal argument {0}
+-equal_as_assign # test for equality (==) mistyped as assignment (=)?{0}
++var_hides_arg # variable {0} hides argument
++redeclared_var # redeclaration of {0} {1}
+-anon_no_return_value # anonymous function does not always return a value
++missing_semicolon # missing semicolon
++meaningless_block # meaningless block; curly braces have no impact
++comma_separated_stmts # multiple statements separated by commas (use semicolons?)
+-unreachable_code # unreachable code
+-missing_break # missing break statement
++missing_break_for_last_case # missing break statement for last case in switch
++comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
+-inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
++useless_void # use of the void type may be unnecessary (void is always undefined)
++multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
++use_of_label # use of label
+-block_without_braces # block statement without curly braces
++leading_decimal_point # leading decimal point may indicate a number or an object member
++trailing_decimal_point # trailing decimal point may indicate a number or an object member
++octal_number # leading zeros make an octal number
++nested_comment # nested comment
+-misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma
++ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement
++empty_statement # empty statement or extra semicolon
+-missing_option_explicit # the "option explicit" control comment is missing
++partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag
++dup_option_explicit # duplicate "option explicit" control comment
++useless_assign # useless assignment
++ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity
++ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent)
+-missing_default_case # missing default case in switch statement
++duplicate_case_in_switch # duplicate case in switch statements
++default_not_at_end # the default case is not at the end of the switch statement
++legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax
++jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax
++useless_comparison # useless comparison; comparing identical expressions
++with_statement # with statement hides undeclared variables; use temporary variable instead
++trailing_comma_in_array # extra comma is not recommended in array initializers
++assign_to_function_call # assignment to a function call
++parseint_missing_radix # parseInt missing radix parameter
+
+
+### Output format
+# Customize the format of the error message.
+# __FILE__ indicates current file path
+# __FILENAME__ indicates current file name
+# __LINE__ indicates current line
+# __ERROR__ indicates error message
+#
+# Visual Studio syntax (default):
++output-format __FILE__(__LINE__): __ERROR__
+# Alternative syntax:
+#+output-format __FILE__:__LINE__: __ERROR__
+
+
+### Context
+# Show the in-line position of the error.
+# Use "+context" to display or "-context" to suppress.
+#
++context
+
+
+### Semicolons
+# By default, assignments of an anonymous function to a variable or
+# property (such as a function prototype) must be followed by a semicolon.
+#
++lambda_assign_requires_semicolon
+
+
+### Control Comments
+# Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for
+# the /*@keyword@*/ control comments and JScript conditional comments. (The latter is
+# enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason,
+# although legacy control comments are enabled by default for backward compatibility.
+#
++legacy_control_comments
+
+
+### JScript Function Extensions
+# JScript allows member functions to be defined like this:
+# function MyObj() { /*constructor*/ }
+# function MyObj.prototype.go() { /*member function*/ }
+#
+# It also allows events to be attached like this:
+# function window::onload() { /*init page*/ }
+#
+# This is a Microsoft-only JavaScript extension. Enable this setting to allow them.
+#
+-jscript_function_extensions
+
+
+### Defining identifiers
+# By default, "option explicit" is enabled on a per-file basis.
+# To enable this for all files, use "+always_use_option_explicit"
+-always_use_option_explicit
+
+# Define certain identifiers of which the lint is not aware.
+# (Use this in conjunction with the "undeclared identifier" warning.)
+#
+# Common uses for webpages might be:
+#+define window
+#+define document
+
+
+### Files
+# Specify which files to lint
+# Use "+recurse" to enable recursion (disabled by default).
+# To add a set of files, use "+process FileName", "+process Folder\Path\*.js",
+# or "+process Folder\Path\*.htm".
+#
++process lib/*.js
144 lib/fs.js
@@ -0,0 +1,144 @@
+// TODO(vojta): allow relative paths
+var util = require('util');
+var predictableNextTick = require('./util.js').predictableNextTick;
+
+/**
+ * @constructor
+ * @param {boolean} isDirectory
+ */
+var Stats = function(isFile, mtime) {
+ this.mtime = mtime;
+ this.isDirectory = function() {
+ return !isFile;
+ };
+ this.isFile = function() {
+ return isFile;
+ };
+};
+
+var File = function(mtime, content) {
+ this.mtime = mtime;
+ this.content = content || '';
+ this.getStats = function() {
+ return new Stats(true, new Date(this.mtime));
+ };
+ this.getBuffer = function() {
+ return new Buffer(this.content);
+ };
+};
+
+/**
+ * @constructor
+ * @param {Object} structure
+ */
+var Mock = function(structure) {
+ var watchers = {};
+
+ var getPointer = function(path, pointer) {
+ var parts = path.split('/').slice(1);
+
+ while (parts.length) {
+ if (!pointer[parts[0]]) break;
+ pointer = pointer[parts.shift()];
+ }
+
+ return parts.length ? null : pointer;
+ };
+
+ var validatePath = function(path) {
+ if (path.charAt(0) !== '/') {
+ throw new Error('Relative path not supported !');
+ }
+ };
+
+ // public API
+ this.stat = function(path, callback) {
+ validatePath(path);
+ predictableNextTick(function() {
+ var pointer = getPointer(path, structure);
+ if (!pointer) return callback({});
+
+ var stats = pointer instanceof File ? pointer.getStats() :
+ new Stats(typeof pointer !== 'object');
+ return callback(null, stats);
+ });
+ };
+
+ this.readdir = function(path, callback) {
+ validatePath(path);
+ predictableNextTick(function() {
+ var pointer = getPointer(path, structure);
+ return pointer && typeof pointer === 'object' && !(pointer instanceof File) ?
+ callback(null, Object.getOwnPropertyNames(pointer).sort()) : callback({});
+ });
+ };
+
+ this.readFile = function(path, encoding, callback) {
+ var readFileSync = this.readFileSync;
+ callback = callback || encoding;
+
+ predictableNextTick(function() {
+ var data = null;
+ var error = null;
+
+ try {
+ data = readFileSync(path);
+ } catch(e) {
+ error = e;
+ }
+
+ callback(error, data);
+ });
+ };
+
+ this.readFileSync = function(path) {
+ var pointer = getPointer(path, structure);
+
+ if (!pointer) {
+ throw new Error(util.format('No such file or directory "%s"', path));
+ }
+
+ if (pointer instanceof File) {
+ return pointer.getBuffer();
+ }
+
+ if (typeof pointer === 'object') {
+ throw new Error('Illegal operation on directory');
+ }
+
+ return new Buffer('');
+ };
+
+ this.watchFile = function(path, options, callback) {
+ callback = callback || options;
+ watchers[path] = watchers[path] || [];
+ watchers[path].push(callback);
+ };
+
+ // Mock API
+ this._touchFile = function(path, mtime, content) {
+ var pointer = getPointer(path, structure);
+ var previous = pointer.getStats();
+
+ // update the file
+ if (typeof mtime !== 'undefined') pointer.mtime = mtime;
+ if (typeof content !== 'undefined') pointer.content = content;
+
+ var current = pointer.getStats();
+ (watchers[path] || []).forEach(function(callback) {
+ callback(current, previous);
+ });
+ };
+
+
+};
+
+
+// PUBLIC stuff
+exports.create = function(structure) {
+ return new Mock(structure);
+};
+
+exports.file = function(mtime, content) {
+ return new File(mtime, content);
+};
48 lib/http.js
@@ -0,0 +1,48 @@
+var ServerResponse = function() {
+ var headSent = false;
+ var bodySent = false;
+
+ this._headers = {};
+ this._body = null;
+
+ this._isFinished = function() {
+ return headSent && bodySent;
+ };
+
+ this.setHeader = function(name, value) {
+ if (headSent) {
+ throw new Error("Can't set headers after they are sent.");
+ }
+
+ this._headers[name] = value;
+ };
+
+ this.removeHeader = function(name) {
+ delete this._headers[name];
+ };
+
+ this.writeHead = function(status) {
+ if (headSent) {
+ throw new Error("Can't render headers after they are sent to the client.");
+ }
+
+ headSent = true;
+ this._status = status;
+ };
+
+ this.end = function(body) {
+ if (bodySent) return;
+
+ bodySent = true;
+ this._body = body.toString();
+ };
+};
+
+var ServerRequest = function(url) {
+ this.url = url;
+};
+
+
+// PUBLIC stuff
+exports.ServerResponse = ServerResponse;
+exports.ServerRequest = ServerRequest;
6 lib/index.js
@@ -0,0 +1,6 @@
+var util = require('./util');
+
+exports.fs = require('./fs');
+exports.http = require('./http');
+exports.predictableNextTick = util.predictableNextTick;
+exports.loadFile = util.loadFile;
100 lib/util.js
@@ -0,0 +1,100 @@
+var vm = require('vm');
+var fs = require('fs');
+var path = require('path');
+
+var nextTickQueue = [];
+var nextTickRegistered = false;
+
+var nextTickHandler = function() {
+ var pattern = predictableNextTick.pattern;
+ var queue = nextTickQueue;
+
+ nextTickRegistered = false;
+ nextTickQueue = [];
+
+ var base = 0;
+ while (base < queue.length) {
+ pattern.forEach(function(i) {
+ var index = base + i;
+ if (queue[index]) {
+ try {
+ queue[index]();
+ queue[index] = null;
+ } catch(e) {
+ // filter only fns that still needs to be executed
+ // just in the case someone will handle the exception
+ queue[index] = null;
+ var stillNeedToBeExec = queue.filter(function(fn) {
+ return fn;
+ });
+
+ // re-register handler if there are more fns to execute
+ if (stillNeedToBeExec.length) {
+ nextTickQueue = stillNeedToBeExec.concat(nextTickQueue);
+
+ if (!nextTickRegistered) {
+ process.nextTick(nextTickHandler);
+ nextTickHandlerRegistered = true;
+ }
+ }
+
+ throw e;
+ }
+ }
+ });
+ base += pattern.length;
+ }
+};
+
+var predictableNextTick = function(callback) {
+ nextTickQueue.push(callback);
+
+ if (!nextTickRegistered) {
+ process.nextTick(nextTickHandler);
+ nextTickRegistered = true;
+ }
+};
+
+predictableNextTick.pattern = [0];
+
+/**
+ * Helper for unit testing:
+ * - load module with mocked dependencies
+ * - allow accessing private state of the module
+ *
+ * @param {string} path Absolute path to module (file to load)
+ * @param {Object=} mocks Hash of mocked dependencies
+ */
+var loadFile = function(filePath, mocks) {
+ mocks = mocks || {};
+
+ // this is necessary to allow relative path modules within loaded file
+ // i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
+ var resolveModule = function(module) {
+ if (module.charAt(0) !== '.') return module;
+ return path.resolve(path.dirname(filePath), module);
+ };
+
+ var exports = {};
+ var context = {
+ require: function(name) {
+ return mocks[name] || require(resolveModule(name));
+ },
+ __dirname: path.dirname(filePath),
+ __filename: filePath,
+ console: console,
+ exports: exports,
+ module: {
+ exports: exports
+ }
+ };
+
+ vm.runInNewContext(fs.readFileSync(filePath), context);
+ return context;
+};
+
+
+// PUBLIC stuff
+exports.loadFile = loadFile;
+exports.predictableNextTick = predictableNextTick;
+exports.predictableNextTickPattern = [0];
38 package.json
@@ -0,0 +1,38 @@
+{
+ "name": "mocks",
+ "description": "Set of mock modules for easy testing (fs, http)",
+ "homepage": "",
+ "keywords": [
+ "mock",
+ "stub",
+ "dummy",
+ "test double",
+ "fake",
+ "nodejs",
+ "js",
+ "testing",
+ "test",
+ "fs",
+ "fs mock",
+ "http",
+ "http mock"
+ ],
+ "author": "Vojta Jína <vojta.jina@gmail.com>",
+ "contributors": [],
+ "dependencies": {},
+ "devDependencies": {
+ "coffee-script": "1.1.2",
+ "jasmine-node": "1.0.11",
+ "jake": "0.2.15"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/vojtajina/node-mocks.git"
+ },
+ "main": "./lib/index",
+ "bin": {},
+ "engines": {
+ "node": ">= 0.6.5"
+ },
+ "version": "0.0.0"
+}
229 test/fs.spec.coffee
@@ -0,0 +1,229 @@
+#==============================================================================
+# test/mock/fs.js module
+#==============================================================================
+describe 'fs', ->
+ fsMock = require '../lib/fs'
+ fs = callback = finished = null
+
+ waitForFinished = (count = 1, name = 'FS') ->
+ waitsFor (-> finished == count), name, 100
+
+ beforeEach ->
+ finished = 0
+
+ fs = fsMock.create
+ bin:
+ grep: 1
+ chmod: 1
+ home:
+ vojta:
+ sub:
+ 'first.js': 1
+ 'second.js': 1
+ 'third.log': 1
+ sub2:
+ 'first.js': 1
+ 'second.js': 1
+ 'third.log': 1
+ 'some.js': fsMock.file '2012-01-01', 'some'
+ 'another.js': fsMock.file '2012-01-02', 'content'
+
+
+ # ===========================================================================
+ # fs.stat()
+ # ===========================================================================
+ describe 'stat', ->
+
+ it 'should be async', ->
+ callback = jasmine.createSpy 'done'
+ fs.stat '/bin', callback
+ expect(callback).not.toHaveBeenCalled()
+
+
+ it 'should stat directory', ->
+ fs.stat '/bin', (err, stat) ->
+ expect(err).toBeFalsy()
+ expect(stat.isDirectory()).toBe true
+ finished++
+ waitForFinished()
+
+
+ it 'should stat file', ->
+ callback = (err, stat) ->
+ expect(err).toBeFalsy()
+ expect(stat.isDirectory()).toBe false
+ finished++
+
+ fs.stat '/bin/grep', callback
+ fs.stat '/home/vojta/some.js', callback
+ waitForFinished 2
+
+
+ it 'should return error when path does not exist', ->
+ callback = (err, stat) ->
+ expect(err).toBeTruthy()
+ expect(stat).toBeFalsy()
+ finished++
+
+ fs.stat '/notexist', callback
+ fs.stat '/home/notexist', callback
+ waitForFinished 2
+
+
+ it 'should have modified timestamp', ->
+ callback = (err, stat) ->
+ expect(err).toBeFalsy()
+ expect(stat.mtime instanceof Date).toBe true
+ expect(stat.mtime).toEqual new Date '2012-01-01'
+ finished++
+
+ fs.stat '/home/vojta/some.js', callback
+ waitForFinished()
+
+
+ # ===========================================================================
+ # fs.readdir()
+ # ===========================================================================
+ describe 'readdir', ->
+
+ it 'should be async', ->
+ callback = jasmine.createSpy 'done'
+ fs.readdir '/bin', callback
+ expect(callback).not.toHaveBeenCalled()
+
+
+ it 'should return array of files and directories', ->
+ callback = (err, files) ->
+ expect(err).toBeFalsy()
+ expect(files).toContain 'sub'
+ expect(files).toContain 'some.js'
+ expect(files).toContain 'another.js'
+ finished++
+
+ fs.readdir '/home/vojta', callback
+ waitForFinished()
+
+
+ it 'should return error if does not exist', ->
+ callback = (err, files) ->
+ expect(err).toBeTruthy()
+ expect(files).toBeFalsy()
+ finished++
+
+ fs.readdir '/home/not', callback
+ waitForFinished()
+
+
+ # ===========================================================================
+ # fs.readFile
+ # ===========================================================================
+ describe 'readFile', ->
+
+ it 'should read file content as Buffer', ->
+ callback = (err, data) ->
+ expect(err).toBeFalsy()
+ expect(data instanceof Buffer).toBe true
+ expect(data.toString()).toBe 'some'
+ finished++
+
+ fs.readFile '/home/vojta/some.js', callback
+ waitForFinished()
+
+
+ it 'should be async', ->
+ callback = jasmine.createSpy 'calback'
+ fs.readFile '/home/vojta/some.js', callback
+ expect(callback).not.toHaveBeenCalled()
+
+
+ it 'should call error callback when non existing file or directory', ->
+ callback = (err, data) ->
+ expect(err).toBeTruthy()
+ finished++
+
+ fs.readFile '/home/vojta', callback
+ fs.readFile '/some/non-existing', callback
+ waitForFinished 2
+
+
+ # regression
+ it 'should not silent exception from callback', ->
+ fs.readFile '/home/vojta/some.js', (err) ->
+ throw 'CALLBACK EXCEPTION' if not err
+
+ uncaughtExceptionCallback = (err) ->
+ process.removeListener 'uncaughtException', uncaughtExceptionCallback
+ expect(err).toEqual 'CALLBACK EXCEPTION'
+ finished++
+
+ process.on 'uncaughtException', uncaughtExceptionCallback
+ waitForFinished 1, 'exception', 100
+
+
+ it 'should allow optional second argument (encoding)', ->
+ fs.readFile '/home/vojta/some.js', 'utf-8', (err) ->
+ finished++
+
+ waitForFinished()
+
+
+ # ===========================================================================
+ # fs.readFileSync
+ # ===========================================================================
+ describe 'readFileSync', ->
+
+ it 'should read file content and sync return buffer', ->
+ buffer = fs.readFileSync '/home/vojta/another.js'
+ expect(buffer instanceof Buffer).toBe true
+ expect(buffer.toString()).toBe 'content'
+
+
+ it 'should throw when file does not exist', ->
+ expect(-> fs.readFileSync '/non-existing').
+ toThrow 'No such file or directory "/non-existing"'
+
+
+ it 'should throw when reading a directory', ->
+ expect(-> fs.readFileSync '/home/vojta').
+ toThrow 'Illegal operation on directory'
+
+
+ # ===========================================================================
+ # fs.watchFile
+ # ===========================================================================
+ describe 'watchFile', ->
+
+ it 'should call when when file accessed', ->
+ callback = jasmine.createSpy('watcher').andCallFake (current, previous) ->
+ expect(current.isFile()).toBe true
+ expect(previous.isFile()).toBe true
+ expect(current.mtime).toEqual previous.mtime
+
+ fs.watchFile '/home/vojta/some.js', callback
+ expect(callback).not.toHaveBeenCalled()
+
+ fs._touchFile '/home/vojta/some.js'
+ expect(callback).toHaveBeenCalled()
+
+
+ it 'should call when file modified', ->
+ original = new Date '2012-01-01'
+ modified = new Date '2012-01-02'
+
+ callback = jasmine.createSpy('watcher').andCallFake (current, previous) ->
+ expect(previous.mtime).toEqual original
+ expect(current.mtime).toEqual modified
+
+ fs.watchFile '/home/vojta/some.js', callback
+ expect(callback).not.toHaveBeenCalled()
+
+ fs._touchFile '/home/vojta/some.js', '2012-01-02', 'new content'
+ expect(callback).toHaveBeenCalled()
+
+
+ it 'should allow optional second argument (options)', ->
+ callback = jasmine.createSpy 'watcher'
+ fs.watchFile '/home/vojta/some.js', {some: 'options'}, callback
+ fs._touchFile '/home/vojta/some.js'
+
+ expect(callback).toHaveBeenCalled()
70 test/http.spec.coffee
@@ -0,0 +1,70 @@
+#==============================================================================
+# test/mock/http.js module
+#==============================================================================
+describe 'http', ->
+ httpMock = require '../lib/http'
+
+ #==============================================================================
+ # http.ServerResponse
+ #==============================================================================
+ describe 'ServerResponse', ->
+ response = null
+
+ beforeEach ->
+ response = new httpMock.ServerResponse
+
+ it 'should set body', ->
+ response.end 'Some Body'
+ expect(response._body).toBe 'Some Body'
+
+
+ it 'should convert body buffer to string', ->
+ response.end new Buffer 'string'
+ expect(response._body).toBe 'string'
+
+
+ it 'should set status', ->
+ response.writeHead 201
+ expect(response._status).toBe 201
+
+
+ it 'should ignore end() when already sent', ->
+ response.end 'First Body'
+ response.end 'Another Body'
+ expect(response._body).toBe 'First Body'
+
+
+ it 'should set and remove headers', ->
+ response.setHeader 'Content-Type', 'text/javascript'
+ response.setHeader 'Cache-Control', 'no-cache'
+ response.setHeader 'Content-Type', 'text/plain'
+ response.removeHeader 'Cache-Control'
+
+ expect(response._headers).toEqual {'Content-Type': 'text/plain'}
+
+
+ it 'should throw when trying to send headers twice', ->
+ response.writeHead 200
+
+ expect(-> response.writeHead 200)
+ .toThrow "Can't render headers after they are sent to the client."
+
+
+ it 'should throw when trying to set headers after sending', ->
+ response.writeHead 200
+
+ expect(-> response.setHeader 'Some', 'Value')
+ .toThrow "Can't set headers after they are sent."
+
+
+ it 'isFinished() should assert whether headers and body has been sent', ->
+ expect(response._isFinished()).toBe false
+
+ response.setHeader 'Some', 'Value'
+ expect(response._isFinished()).toBe false
+
+ response.writeHead 404
+ expect(response._isFinished()).toBe false
+
+ response.end 'Some body'
+ expect(response._isFinished()).toBe true
99 test/util.spec.coffee
@@ -0,0 +1,99 @@
+#==============================================================================
+# test/mock/util.js module
+#==============================================================================
+describe 'mock-util', ->
+ util = require '../lib/util'
+
+ #============================================================================
+ # util.predictableNextTick()
+ #============================================================================
+ describe 'predictableNextTick', ->
+ nextTick = util.predictableNextTick
+
+ it 'should be async', ->
+ spy = jasmine.createSpy 'nextTick callback'
+ nextTick spy
+
+ expect(spy).not.toHaveBeenCalled()
+ waitsFor (-> spy.callCount), 'nextTick', 100
+
+
+ it 'should behave predictable based on given pattern', ->
+ nextTick.pattern = [1, 0]
+ stressIt = ->
+ log = ''
+ runs ->
+ nextTick -> log += 1
+ nextTick -> log += 2
+ nextTick -> log += 3
+ nextTick -> log += 4
+ waitsFor (-> log.length is 4), 'all nextTicks', 100
+ runs ->
+ expect(log).toBe '2143'
+
+ # execute this test five times
+ stressIt() for i in [1..5]
+
+
+ it 'should do 021 pattern k*n fns', ->
+ nextTick.pattern = [0, 2, 1]
+ log = ''
+ nextTick -> log += 0
+ nextTick -> log += 1
+ nextTick -> log += 2
+ nextTick -> log += 3
+ nextTick -> log += 4
+ nextTick -> log += 5
+ waitsFor (-> log.length is 6), 'all nextTicks', 100
+ runs ->
+ expect(log).toBe '021354'
+
+
+ it 'should do 3021 pattern with n+1 fns', ->
+ nextTick.pattern = [3, 0, 2, 1]
+ log = ''
+ nextTick -> log += 0
+ nextTick -> log += 1
+ nextTick -> log += 2
+ nextTick -> log += 3
+ nextTick -> log += 4
+ waitsFor (-> log.length is 5), 'all nextTicks', 100
+ runs ->
+ expect(log).toBe '30214'
+
+
+ # regression
+ it 'should survive exception inside callback', ->
+ exceptionHandled = false
+ beforeExceptionSpy = jasmine.createSpy 'before exception'
+ afterExceptionSpy = jasmine.createSpy 'after exception'
+
+ nextTick beforeExceptionSpy
+ nextTick -> throw 'CALLBACK EXCEPTION'
+ nextTick afterExceptionSpy
+
+ uncaughtExceptionHandler = (err) ->
+ process.removeListener 'uncaughtException', uncaughtExceptionHandler
+ exceptionHandled = true
+
+ process.on 'uncaughtException', uncaughtExceptionHandler
+ waitsFor (-> afterExceptionSpy.callCount), 'after exception callback', 100
+ runs ->
+ expect(beforeExceptionSpy.callCount).toBe 1
+ expect(afterExceptionSpy.callCount).toBe 1
+ expect(exceptionHandled).toBe true
+
+
+ # regression
+ it 'should not ignore fn that was added into already skipped space during execution', ->
+ nextTick.pattern = [1, 0]
+ anotherCallback = jasmine.createSpy 'another later added fn'
+ callback = jasmine.createSpy 'later added fn'
+
+ nextTick ->
+ nextTick ->
+ callback()
+ nextTick anotherCallback
+
+ waitsFor (-> callback.callCount), 'later added fn to be called', 100
+ waitsFor (-> anotherCallback.callCount), 'another later added fn to be called', 100
Please sign in to comment.
Something went wrong with that request. Please try again.