diff --git a/.gitignore b/.gitignore index 042ccf2..c794f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules +bower_components *~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 16e814c..7f32e4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,7 @@ You can now run the following grunt tasks: * `grunt check` will run [JSHint](http://www.jshint.com/) on your code (do this before submitting a pull request) to catch errors * `grunt develop` will create a single file version of the library for testing in `dist/idbfs.js` * `grunt release` like `develop` but will also create a minified version of the library in `dist/idbfs.min.js` +* `grunt test` will run [JSHint](http://www.jshint.com/) on your code and the test suite in [PhantomJS](http://phantomjs.org/) Once you've done some hacking and you'd like to have your work merged, you'll need to make a pull request. If you're patch includes code, make sure to check that all the @@ -29,11 +30,27 @@ to the `AUTHORS` file. ## Tests -Tests are writting using [Jasmine](http://pivotal.github.io/jasmine/). You can run the tests -in your browser by opening the `tests` directory. You can also run them -[here](http://js-platform.github.io/idbfs/tests/). +Tests are writting using [Mocha](http://visionmedia.github.io/mocha/) and [Chai](http://chaijs.com/api/bdd/). +You can run the tests in your browser by opening the `tests` directory. You can also run them +[here](http://js-platform.github.io/filer/tests/). + +There are a number of configurable options for the test suite, which are set via query string params. +First, you can choose which filer source to use (i.e., src/, dist/filer.js or dist/filer.min.js). +The default is to use what is in /src, and you can switch to built versions like so: +* tests/index.html?filer-dist/filer.js +* tests/index.html?filer-dist/filer.min.js + +Second, you can specify which provider to use for all non-provider specific tests (i.e., most of the tests). +The default provider is `Memory`, and you can switch it like so: +* tests/index.html?filer-provider=memory +* tests/index.html?filer-provider=indexeddb +* tests/index.html?filer-provider=websql + +If you're writing tests, make sure you write them in the same style as existing tests, which are +provider agnostic. See `tests/lib/test-utils.js` and how it gets used in various tests as +an example. ## Communication If you'd like to talk to someone about the project, you can reach us on irc.mozilla.org in the -mofodev channel. Look for "ack" or "humph". +#filer or #mofodev channel. Look for "ack" or "humph". diff --git a/README.md b/README.md index 6aad26e..f5bb965 100644 --- a/README.md +++ b/README.md @@ -73,13 +73,14 @@ object can specify a number of optional arguments, including: * `provider`: an explicit storage provider to use for the file system's database context provider. See the section on [Storage Providers](#providers). The `callback` function indicates when the file system is ready for use. Depending on the storage provider used, this might -be right away, or could take some time. The callback should expect an `error` argument, which will be null if everything worked. -Also users should check the file system's `readyState` and `error` properties to make sure it is usable. +be right away, or could take some time. The callback should expect two arguments: first, an `error` argument, which will be +null if everything worked; second, an instance, such that you can access the newly ready FileSystem instance. Also users +should check the file system's `readyState` and `error` properties to make sure it is usable. ```javascript var fs; -function fsReady(err) { +function fsReady(err, fs) { if(err) throw err; // Safe to use fs now... } diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..24d9c70 --- /dev/null +++ b/bower.json @@ -0,0 +1,19 @@ +{ + "name": "filer", + "version": "0.0.4", + "main": "dist/filer.js", + "devDependencies": { + "mocha": "1.17.1", + "chai": "1.9.0" + }, + "ignore": [ + "build", + "examples", + "package.json", + "tests", + "gruntfile.js", + "node_modules", + "src", + "tools" + ] +} diff --git a/gruntfile.js b/gruntfile.js index 9dee2e3..1b627ad 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -28,6 +28,15 @@ module.exports = function(grunt) { ] }, + mocha: { + test: { + src: 'tests/index.html', + options: { + log: true + } + } + }, + requirejs: { develop: { options: { @@ -60,10 +69,12 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-mocha'); grunt.registerTask('develop', ['clean', 'requirejs']); grunt.registerTask('release', ['develop', 'uglify']); grunt.registerTask('check', ['jshint']); + grunt.registerTask('test', ['check', 'mocha']); grunt.registerTask('default', ['develop']); }; diff --git a/package.json b/package.json index 1248f68..e7152b3 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,15 @@ "homepage": "http://js-platform.github.io/filer", "bugs": "https://github.com/js-platform/filer/issues", "license": "BSD", + "scripts": { + "postinstall": "./node_modules/.bin/bower install" + }, "repository": { "type": "git", "url": "https://github.com/js-platform/filer.git" }, "devDependencies": { + "bower": "~1.0.0", "grunt": "~0.4.0", "grunt-contrib-clean": "~0.4.0", "grunt-contrib-requirejs": "~0.4.0", @@ -19,8 +23,8 @@ "grunt-contrib-watch": "~0.3.1", "grunt-contrib-compress": "~0.4.1", "grunt-contrib-connect": "~0.1.2", - "grunt-contrib-jasmine": "~0.3.3", "grunt-contrib-concat": "~0.1.3", - "grunt-contrib-jshint": "~0.7.1" + "grunt-contrib-jshint": "~0.7.1", + "grunt-mocha": "0.4.10" } } diff --git a/src/fs.js b/src/fs.js index 0ca1c5f..b80fe0f 100644 --- a/src/fs.js +++ b/src/fs.js @@ -1429,7 +1429,7 @@ define(function(require) { fs.readyState = FS_READY; runQueued(); } - callback(error); + callback(error, fs); } if(err) { diff --git a/src/providers/memory.js b/src/providers/memory.js index a813f24..40524c1 100644 --- a/src/providers/memory.js +++ b/src/providers/memory.js @@ -1,36 +1,69 @@ define(function(require) { var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME; + // Based on https://github.com/caolan/async/blob/master/lib/async.js + var nextTick = (function() { + if (typeof process === 'undefined' || !(process.nextTick)) { + if (typeof setImmediate === 'function') { + return function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + } else { + return function (fn) { + setTimeout(fn, 0); + }; + } + } + return process.nextTick; + }()); + + function asyncCallback(callback) { + nextTick(callback); + } + function MemoryContext(db, readOnly) { this.readOnly = readOnly; this.objectStore = db; } MemoryContext.prototype.clear = function(callback) { if(this.readOnly) { - return callback("[MemoryContext] Error: write operation on read only context"); + asyncCallback(function() { + callback("[MemoryContext] Error: write operation on read only context"); + }); + return; } var objectStore = this.objectStore; Object.keys(objectStore).forEach(function(key){ delete objectStore[key]; }); - callback(null); + asyncCallback(callback); }; MemoryContext.prototype.get = function(key, callback) { - callback(null, this.objectStore[key]); + var that = this; + asyncCallback(function() { + callback(null, that.objectStore[key]); + }); }; MemoryContext.prototype.put = function(key, value, callback) { if(this.readOnly) { - return callback("[MemoryContext] Error: write operation on read only context"); + asyncCallback(function() { + callback("[MemoryContext] Error: write operation on read only context"); + }); + return; } this.objectStore[key] = value; - callback(null); + asyncCallback(callback); }; MemoryContext.prototype.delete = function(key, callback) { if(this.readOnly) { - return callback("[MemoryContext] Error: write operation on read only context"); + asyncCallback(function() { + callback("[MemoryContext] Error: write operation on read only context"); + }); + return; } delete this.objectStore[key]; - callback(null); + asyncCallback(callback); }; @@ -43,7 +76,9 @@ define(function(require) { }; Memory.prototype.open = function(callback) { - callback(null, true); + asyncCallback(function() { + callback(null, true); + }); }; Memory.prototype.getReadOnlyContext = function() { return new MemoryContext(this.db, true); diff --git a/tests/bugs/issue105.js b/tests/bugs/issue105.js new file mode 100644 index 0000000..9c4cf42 --- /dev/null +++ b/tests/bugs/issue105.js @@ -0,0 +1,35 @@ +define(["Filer", "util"], function(Filer, util) { + + describe('trailing slashes in path names, issue 105', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should deal with trailing slashes properly, path == path/', function(done) { + var fs = util.fs(); + + fs.mkdir('/tmp', function(err) { + if(err) throw err; + + fs.mkdir('/tmp/foo', function(err) { + if(err) throw err; + + // Without trailing slash + fs.readdir('/tmp', function(err, result1) { + if(err) throw err; + expect(result1).to.exist; + expect(result1.length).to.equal(1); + + // With trailing slash + fs.readdir('/tmp/', function(err, result2) { + if(err) throw err; + expect(result2).to.exist; + expect(result2[0]).to.equal('tmp'); + expect(result1).to.deep.equal(result2); + done(); + }); + }); + }); + }); + }); + }); +}); diff --git a/tests/bugs/issue106.js b/tests/bugs/issue106.js new file mode 100644 index 0000000..57305fb --- /dev/null +++ b/tests/bugs/issue106.js @@ -0,0 +1,31 @@ +define(["Filer", "util"], function(Filer, util) { + + describe('fs.writeFile truncation - issue 106', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should truncate an existing file', function(done) { + var fs = util.fs(); + var filename = '/test'; + + fs.writeFile(filename, '1', function(err) { + if(err) throw err; + + fs.stat(filename, function(err, stats) { + if(err) throw err; + expect(stats.size).to.equal(1); + + fs.writeFile(filename, '', function(err) { + if(err) throw err; + + fs.stat(filename, function(err, stats) { + if(err) throw err; + expect(stats.size).to.equal(0); + done(); + }); + }); + }); + }); + }); + }); +}); diff --git a/tests/common.js b/tests/common.js deleted file mode 100644 index 33e72bb..0000000 --- a/tests/common.js +++ /dev/null @@ -1,39 +0,0 @@ -var TEST_DATABASE_NAME = '__test'; -var DEFAULT_TIMEOUT = 5000; - -var test_database_names = []; -window.onbeforeunload = function() { - test_database_names.forEach(function(name) { - indexedDB.deleteDatabase(name); - }); -}; - -function mk_id(length) { - var text = ''; - var tokens = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - for( var i=0; i < length; i++ ) - text += tokens.charAt(Math.floor(Math.random() * tokens.length)); - - return text; -}; - -function mk_db_name() { - var name = TEST_DATABASE_NAME + mk_id(5) + Date.now(); - test_database_names.push(name); - return name; -}; - -function typed_array_equal(left, right) { - if(left.length !== right.length) { - return false; - } - - for(var i = 0; i < left.length; ++ i) { - if(left[i] !== right[i]) { - return false; - } - } - - return true; -}; diff --git a/tests/index.html b/tests/index.html index d1c6329..5a8e535 100644 --- a/tests/index.html +++ b/tests/index.html @@ -1,16 +1,33 @@ -
-