Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eldargab committed Sep 2, 2012
1 parent 95b0a9e commit 755f4d9
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
@@ -1,4 +1,4 @@
node-fake-fs
fake-fs
============

Fake node.js file system for testing
1 change: 1 addition & 0 deletions index.js
@@ -0,0 +1 @@
module.exports = require('./lib/fake-fs')
106 changes: 106 additions & 0 deletions lib/fake-fs.js
@@ -0,0 +1,106 @@
var stat = require('./stat')
var normalize = require('path').normalize

function FsError (code) {
var err = new Error(code)
err.code = code
return err
}


module.exports = Fs

function Fs (paths) {
this.root = new stat.Dir
}

Fs.prototype.dir = function (path, opts) {
return this._add(path, new stat.Dir(opts))
}

Fs.prototype.file = function (path, content, encoding) {
return this._add(path, new stat.File(content, encoding))
}

Fs.prototype._add = function (path, item) {
var segs = path.split('/')
var dir = this.root
for (var i = 0; i < segs.length - 1; i++) {
dir = dir.childs[segs[i]] || (dir.childs[segs[i]] = new stat.Dir)
if (!dir.isDirectory()) {
throw new Error('There is already ' + dir + ' defined at ' + segs.slice(i).join('/'))
}
}
dir.childs[segs[i]] = item
return this
}

Fs.prototype._itemAt = function (path) {
path = normalize(path).replace('\\', '/') // windows support
var segs = path.split('/')
var item = this.root
if (segs[0] == '.') return item
for (var i = 0; i < segs.length; i++) {
item = item.childs && item.childs[segs[i]]
if (!item) return
}
return item
}

Fs.prototype._get = function (path) {
var item = this._itemAt(path)
if (!item) throw FsError('ENOENT')
return item
}


Fs.prototype.statSync = function (path) {
return this._get(path)
}

Fs.prototype.existsSync = function (path) {
return !!this._itemAt(path)
}

Fs.prototype.readdirSync = function (dir) {
var item = this._get(dir)
if (!item.isDirectory()) throw FsError('ENOTDIR')
return Object.keys(item.childs)
}

Fs.prototype.readFileSync = function (filename, encoding) {
var item = this._get(filename)
if (item.isDirectory()) throw FsError('EISDIR')
return item.read(encoding)
}

;['readdir', 'stat'].forEach(function (meth) {
var sync = meth + 'Sync'
Fs.prototype[meth] = function (p, cb) {
var res, err
try {
res = this[sync].call(this, p)
} catch (e) {
err = e
}
cb && cb(err, res)
}
})

Fs.prototype.readFile = function (filename, encoding, cb) {
if (typeof encoding != 'string') {
cb = encoding
encoding = undefined
}
var res, err
try {
res = this.readFileSync(filename, encoding)
} catch (e) {
err = e
}
cb && cb(err, res)
}

Fs.prototype.exists = function (path, cb) {
cb && cb(this.existsSync(path))
}
68 changes: 68 additions & 0 deletions lib/stat.js
@@ -0,0 +1,68 @@
function mix (target, src) {
for (var key in src) {
target[key] = src[key]
}
}

function stat (Klass, props) {
Klass.prototype = {
isDirectory: function () {
return false
},
isFile: function () {
return false
}
}
mix(Klass.prototype, props)
return Klass
}


exports.Dir = stat(function Dir (opts) {
mix(this, opts)
this.childs = {}
}, {
isDirectory: function () {
return true
},

toString: function () {
return 'directory'
}
})


exports.File = stat(function File (content, encoding) {
if (typeof content == 'string') {
this.content = content
this.encoding = encoding
} else if (Buffer.isBuffer(content)) {
this.content = content
} else {
mix(this, content)
}
if (this.content != null &&
typeof this.content != 'string' &&
!Buffer.isBuffer(this.content)) {
throw new Error('File content can be a string or buffer')
}
}, {
isFile: function () {
return true
},

read: function (encoding) {
return encoding
? this._buffer().toString(encoding)
: this._buffer()
},

_buffer: function () {
if (Buffer.isBuffer(this.content)) return this.content
return this.content = new Buffer(this.content || '', this.encoding || 'utf8')
},

toString: function () {
return 'file'
}
})
24 changes: 24 additions & 0 deletions package.json
@@ -0,0 +1,24 @@
{
"name": "fake-fs",
"version": "0.0.0",
"description": "Fake file system for testing",
"scripts": {
"test": "mocha -R spec"
},
"repository": {
"type": "git",
"url": "git://github.com/eldargab/node-fake-fs.git"
},
"keywords": [
"fs",
"fake",
"mocks",
"test"
],
"devDependecies": {
"mocha": "1.4.2",
"sinon": "1.4.2"
},
"author": "Eldar Gabdullin <eldargab@gmail.com>",
"license": "MIT"
}
155 changes: 155 additions & 0 deletions test/fake-fs.js
@@ -0,0 +1,155 @@
var should = require('should')
var sinon = require('sinon')
var Fs = require('..')

function Cb () {
var spy = sinon.spy()

spy.error = function (err) {
this.calledOnce.should.be.true
this.firstCall.args[0].code.should.equal(err)
}

spy.result = function () {
this.calledOnce.should.be.true
should.not.exist(this.firstCall.args[0])
return this.firstCall.args[1]
}

return spy
}

describe('Fake FS', function () {
var fs, cb

beforeEach(function () {
fs = new Fs
cb = Cb()
})

describe('.dir(path, [opts])', function () {
it('Should define dir', function () {
fs.dir('a/b/c').statSync('a/b/c').isDirectory().should.be.true
})

it('Should support options', function () {
var stat = fs.dir('a', {
mtime: 10,
atime: 30
}).statSync('a')
stat.should.have.property('mtime').equal(10)
stat.should.have.property('atime').equal(30)
})

it('Should work like mkdir -p', function () {
fs.dir('a', { mtime: 100 })
fs.dir('a/b/c')
fs.statSync('a').mtime.should.equal(100)
fs.statSync('a/b').isDirectory().should.be.true
})
})

describe('.file(path, [opts | content, [encoding]]', function () {
it('Should define file', function () {
fs.file('a/b.txt').statSync('a/b.txt').isFile().should.be.true
})

it('Should work like mkdir -p for parent dir', function () {
fs.dir('a', { mtime: 100 })
fs.file('a/b.txt')
fs.statSync('a').mtime.should.equal(100)
})

it('Should support content & encoding params', function () {
fs.file('hello.txt', 'hello')
.readFileSync('hello.txt', 'utf8')
.should.equal('hello')

fs.file('bin', 'TWFu', 'base64')
.readFileSync('bin', 'utf8').should.equal('Man')

fs.file('bin2', new Buffer([10]))
fs.readFileSync('bin2')[0].should.equal(10)
})

it('Should support options param', function () {
fs.file('hello.txt', {
atime: 10,
mtime: 20,
content: 'a'
})
var stat = fs.statSync('hello.txt')
stat.should.have.property('atime').equal(10)
stat.should.have.property('mtime').equal(20)
fs.readFileSync('hello.txt')[0].should.equal(97)
})
})

describe('.stat()', function () {
it('Should return stats', function () {
fs.file('a/b/c', {ctime: 123}).stat('a/b/c', cb)
cb.result().should.have.property('ctime').equal(123)
})

it('Should throw ENOENT on non-existent path', function () {
fs.stat('undefined', cb)
cb.error('ENOENT')
})
})

describe('.readdir()', function () {
it('Should list a dir contents', function () {
fs.dir('a').file('b.txt').readdir('.', cb)
cb.result().should.eql(['a', 'b.txt'])
})

it('Should throw ENOENT on non-existent path', function () {
fs.readdir('a', cb)
cb.error('ENOENT')
})

it('Should throw ENOTDIR on non-dir', function () {
fs.file('a.txt').readdir('a.txt', cb)
cb.error('ENOTDIR')
})
})

describe('.exists()', function () {
it('Should return true on existent path', function (done) {
fs.dir('asd').exists('asd', function (exists) {
exists.should.be.true
done()
})
})

it('Should return false for non-existent path', function (done) {
fs.exists('non-existent', function (exists) {
exists.should.be.false
done()
})
})
})

describe('.readFile()', function () {
it('Should read file contents', function () {
var content = new Buffer([1, 2, 3])
fs.file('bin', content).readFile('bin', cb)
cb.result().should.equal(content)
})

it('Should decode file contents', function () {
fs.file('file.txt', new Buffer([97])).readFile('file.txt', 'ascii', cb)
cb.result().should.equal('a')
})

it('Should throw ENOENT on non-existent file', function () {
fs.readFile('foo', cb)
cb.error('ENOENT')
})

it('Should throw EISDIR on directory', function () {
fs.dir('dir').readFile('dir', cb)
cb.error('EISDIR')
})
})
})

0 comments on commit 755f4d9

Please sign in to comment.