Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit, working search command

  • Loading branch information...
commit 0ec60bfd1ed40748b42602573b33982a808bffc5 0 parents
@rvagg rvagg authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules
3  bin/ender
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('../lib/main').exec(process.argv)
46 lib/args-parse.js
@@ -0,0 +1,46 @@
+function UnknownMainError (message) {
+ Error.call(this)
+ Error.captureStackTrace(this, arguments.callee)
+ this.message = message
+ this.name = 'UnknownMainError'
+}
+UnknownMainError.prototype.__proto__ = Error.prototype
+
+var nopt = require('nopt')
+
+ , knownOptions = {
+ 'output': String
+ , 'use': String
+ , 'max': Number
+ , 'sandbox': Array
+ , 'noop': Boolean
+ , 'silent': Boolean
+ , 'help': Boolean
+ , 'sans': Boolean
+ , 'debug': Boolean
+ }
+
+ , shorthandOptions = {
+ 'o': 'output'
+ , 'u': 'use'
+ , 'x': 'noop'
+ , 's': 'silent'
+ }
+
+ , knownMains = [ 'help', 'build', 'refresh', 'info', 'search', 'compile' ]
+
+ , parse = function (argv) {
+ var parsed = nopt(knownOptions, shorthandOptions, argv)
+ , main = parsed.argv.remain[0]
+ , remaining = parsed.argv.remain.slice(1)
+
+ if (knownMains.indexOf(main) == -1)
+ throw new UnknownMainError('Unknown main command "' + main + '"')
+
+ return {
+ main: main
+ , remaining: remaining
+ }
+ }
+
+module.exports.parse = parse
79 lib/main-search-output.js
@@ -0,0 +1,79 @@
+var extend = require('./util').extend
+ , searchUtil = require('./main-search-util')
+ , Output = require('./output')
+
+ , SearchOutput = extend(Output, { // inherit from Output
+
+ test: function () {
+ console.log('test', this.outfd, this.debug)
+ }
+
+ , searchInit: function () {
+ this.statusMsg('Searching NPM registry...')
+ this.log()
+ }
+
+ , searchNoResults: function () {
+ this.warnMsg('Sorry, we couldn\'t find anything. :(')
+ }
+
+ , searchError: function (err) {
+ this.repositoryError(err, 'Something went wrong searching NPM')
+ }
+
+ , searchResults: function (results) {
+ if (results.primary) {
+ this.heading('Ender tagged results:')
+ results.primary.forEach(function (item) {
+ this.processItem(item, results.terms)
+ }.bind(this))
+ }
+
+ if (results.secondary) {
+ var meta = results.secondaryTotal > results.secondary.length
+ ? results.secondary.length + ' of ' + results.secondaryTotal
+ : ''
+ this.heading('NPM general results:', meta)
+ results.secondary.forEach(function (item) {
+ this.processItem(item, results.terms)
+ }.bind(this))
+ }
+ }
+
+ , processItem: function (item, terms) {
+ var reg = new RegExp('(' + terms.map(function (item) {
+ return searchUtil.escapeRegExp(item)
+ }).join('|') + ')', 'ig')
+ , maintainers = ''
+ , title = item.name
+ , last
+
+ if (item.description)
+ title += ' - ' + item.description.substring(0, 80) + (item.description.length > 80 ? '...' : '')
+
+ this.log('+ ' + title.replace(reg, '$1'.cyan))
+
+ if (item.maintainers && item.maintainers.length) {
+ item.maintainers = item.maintainers.map(function (maintainer) {
+ return maintainer.replace(/^=/, '@')
+ })
+
+ if (item.maintainers.length > 1) {
+ last = item.maintainers.splice(-1)[0]
+ maintainers = item.maintainers.join(', ')
+ maintainers += ' & ' + last
+ } else {
+ maintainers = item.maintainers[0]
+ }
+ }
+
+ this.log(' by ' + maintainers.replace(reg, '$1'.cyan) + '\n')
+ }
+
+ , create: function (outfd, debug) {
+ return Object.create(this).init(outfd, debug)
+ }
+
+ })
+
+module.exports = SearchOutput
38 lib/main-search-util.js
@@ -0,0 +1,38 @@
+ // make a string regex friendly by escaping regex characters
+ // Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+var escapeRegExp = function (string) {
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function (match) {
+ return '\\' + match
+ })
+ }
+
+ // given a regex, an array of objects and an array of properties prioritised, populate a ranked
+ // array with objects whose properties match the regex in priority order. elements get removed
+ // from source array when they are put into the priority array.
+ // array: [ { a: '1;, b: '2' }, { a: '3', b: '4' } ]
+ // ranked: [] (effective return)
+ // priority: [ 'a', 'b' ] (properties in 'array' elements)
+ , sortByRegExp = function (regex, array, ranked, priority) {
+ for (var i = 0; i < priority.length; i++) {
+ var p = priority[i]
+ for (var j = 0; j < array.length; j++) {
+ if (typeof array[j][p] == 'string' && regex.test(array[j][p])) {
+ ranked.push(array.splice(j, 1)[0])
+ j--
+ } else if (array[j][p] && typeof array[j][p] != 'string') {
+ for (var m = 0; m < array[j][p].length; m++) {
+ if (regex.test(array[j][p][m])) {
+ ranked.push(array.splice(j, 1)[0])
+ j--
+ break
+ }
+ }
+ }
+ }
+ }
+ }
+
+module.exports = {
+ escapeRegExp: escapeRegExp
+ , sortByRegExp: sortByRegExp
+}
77 lib/main-search.js
@@ -0,0 +1,77 @@
+var repository = require('./repository')
+ , escapeRegExp = require('./main-search-util').escapeRegExp
+ , sortByRegExp = require('./main-search-util').sortByRegExp
+ , defaultMax = 8
+
+ , exec = function (args, out) {
+ var terms = args.remaining
+ , max = args.max || defaultMax
+ , handler = handle.bind(null, terms, max, out)
+
+ out && out.searchInit()
+
+ repository.setup(function (err) {
+ if (err)
+ return out && out.repositoryLoadError(err)
+
+ repository.search(terms, handler)
+ })
+ }
+
+ , handle = function (terms, max, out, err, data) {
+ var primary = []
+ , secondary = []
+
+ if (err)
+ return out && out.searchError(err)
+
+ repository.packup()
+
+ if (data) {
+ Object.keys(data).forEach(function (id) {
+ var d = data[id]
+ if (d.keywords)
+ (d.keywords.indexOf('ender') == -1 ? secondary : primary).push(d)
+ })
+ }
+
+ if (!primary.length && !secondary.length)
+ return out && out.searchNoResults()
+
+ if (primary.length)
+ primary = rankRelevance(terms, primary)
+
+ out && out.searchResults({
+ terms: terms
+ , max: max
+ , primary: primary.length ? primary : null
+ , secondaryTotal: secondary.length
+ , secondary: secondary.length && (max - primary.length > 0)
+ ? rankRelevance(terms, secondary).slice(0, max - primary.length)
+ : null
+ })
+ }
+
+ , rankRelevance = function (args, data) {
+ var sorted = []
+ , priority = [ 'name', 'keywords', 'description' ]
+ , args = args.map(function (arg) { return escapeRegExp(arg) })
+ , regex
+
+ // args as exact phrase for name
+ regexp = new RegExp('^' + args.join('\\s') + '$')
+ sortByRegExp(regexp, data, sorted, [ 'name' ])
+
+ // args as phrase anywhere
+ regexp = new RegExp('\\b' + args.join('\\s') + '\\b', 'i')
+ sortByRegExp(regexp, data, sorted, priority)
+
+ // args as keywords anywhere (ex: useful for case when express matches expresso)
+ regexp = new RegExp('\\b' + args.join('\\b\|\\b') + '\\b', 'i')
+ sortByRegExp(regexp, data, sorted, priority)
+
+ // we don't really care about relevance at this point :P
+ return sorted.concat(data)
+ }
+
+module.exports.exec = exec
11 lib/main.js
@@ -0,0 +1,11 @@
+var argsParse = require('./args-parse')
+
+ , exec = function (argv) {
+ var args = argsParse.parse(argv)
+ , exe = args && require('./main-' + args.main)
+ , out = args && require('./main-' + args.main + '-output').create(require('util'))
+
+ exe && exe.exec(args, out)
+ }
+
+module.exports.exec = exec
56 lib/output.js
@@ -0,0 +1,56 @@
+var colors = require('colors')
+
+ , Output = {
+
+ init: function (out, isDebug) {
+ this.out = out // an object with a 'print' method, like `require('util')`
+ this.isDebug = isDebug
+ return this
+ }
+
+ , print: function (string) {
+ this.out && this.out.print(string)
+ }
+
+ // generic method, like console.log, should avoid in favour of more specific 'views'
+ , log: function (string) {
+ if (typeof string != 'undefined')
+ this.print(string)
+ this.print('\n')
+ }
+
+ , debug: function (string) {
+ this.isDebug && this.print('DEBUG: ' + String(string) + '\n')
+ }
+
+ , statusMsg: function (string) {
+ this.log(string.grey)
+ }
+
+ , warnMsg: function (string) {
+ this.log(string.grey)
+ }
+
+ , repositoryError: function (err, msg) {
+ if (this.isDebug)
+ throw err
+
+ this.log(msg.red)
+ }
+
+ , repositoryLoadError: function (err) {
+ this.repositoryError(err, 'Something went wrong trying to load NPM!')
+ }
+
+ , heading: function (string, meta) {
+ this.log(string.yellow + (meta ? (' (' + meta + ')').grey : ''))
+ this.log(string.replace(/./g, '-'))
+ }
+
+ , create: function () {
+ return Object.create(this)
+ }
+
+}
+
+module.exports = Output
77 lib/repository.js
@@ -0,0 +1,77 @@
+function RepositorySetupError (message) {
+ Error.call(this)
+ Error.captureStackTrace(this, arguments.callee)
+ this.message = message
+ this.name = 'RepositorySetupError'
+}
+RepositorySetupError.prototype.__proto__ = Error.prototype
+
+var npm = require('npm')
+ , fs = require('fs')
+ , net = require('net')
+ , colors = require('colors')
+ , util = require('./util')
+
+ , isSetup = false
+ , sessionFile
+ , sessionStream
+
+ , generateTempFile = function (callback) {
+ sessionFile = util.tmpDir + '/ender_npm_' + process.pid + '.' + (+new Date())
+ fs.open(sessionFile, 'w', '0644', callback)
+ }
+
+ // must be called at the start of an NPM session
+ , setup = function (callback) {
+ if (isSetup)
+ return callback && callback()
+
+ generateTempFile(function (err, fd) {
+ if (err)
+ return callback(err)
+
+ sessionStream = new net.Stream(fd)
+ var config = {
+ logfd: sessionStream
+ , outfd: sessionStream
+ }
+
+ npm.load(config, function (err) {
+ if (!err)
+ isSetup = true
+
+ callback.apply(null, arguments)
+ })
+ })
+ }
+
+ // must be called at the end of an NPM session
+ , packup = function (wasError, callback) {
+ if (!isSetup)
+ return callback && callback()
+
+ isSetup = false
+
+ sessionStream.on('close', function () {
+ if (!wasError)
+ return fs.unlink(sessionFile, callback)
+
+ callback && callback()
+ sessionStream = null
+ })
+
+ sessionStream.destroySoon()
+ }
+
+ , search = function (keywords, callback) {
+ if (!isSetup)
+ throw new RepositorySetupError('repository.setup() has not been called')
+
+ npm.commands.search(keywords, callback)
+ }
+
+module.exports = {
+ search: search
+ , setup: setup
+ , packup: packup
+}
23 lib/util.js
@@ -0,0 +1,23 @@
+// thanks to NPM for these two, where is our process.tempDir NodeJS?
+var tmpDir = process.env.TMPDIR
+ || process.env.TMP
+ || process.env.TEMP
+ || (process.platform === "win32" ? "c:\\windows\\temp" : "/tmp")
+
+ , homeDir = process.platform === "win32"
+ ? process.env.USERPROFILE
+ : process.env.HOME
+
+ , extend = function (src, dst) {
+ Object.getOwnPropertyNames(src).forEach(function (prop) {
+ if (!(prop in dst))
+ Object.defineProperty(dst, prop, Object.getOwnPropertyDescriptor(src, prop))
+ })
+ return dst
+ }
+
+module.exports = {
+ tmpDir: tmpDir
+ , homeDir: homeDir
+ , extend: extend
+}
71 test/args-parse-test.js
@@ -0,0 +1,71 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , argsParse = require('../lib/args-parse')
+ , buildargs = function (s) {
+ return [ 'node', '/some/path/to/bin' ].concat(s.split(' '))
+ }
+
+buster.testCase('args Parser', {
+
+ 'test parse() exists': function () {
+ assert.isFunction(argsParse.parse)
+ }
+
+ , 'test parse finds main command': function () {
+ var actual = argsParse.parse(buildargs('help'))
+ assert.isString(actual.main)
+ assert.equals(actual.main, 'help')
+ }
+
+ , 'test parse finds main command with trailling cruft': function () {
+ var actual = argsParse.parse(buildargs('build --with --extra stuff here'))
+ assert.isString(actual.main)
+ assert.equals(actual.main, 'build')
+ }
+
+ , 'test parse finds main command with leading and trailling cruft': function () {
+ var actual = argsParse.parse(buildargs('--something --here info --with --extra stuff here'))
+ assert.isString(actual.main)
+ assert.equals(actual.main, 'info')
+ }
+
+ , 'test parse throws exception on no arguments': function () {
+ assert.exception(function () {
+ argsParse.parse(buildargs(''))
+ }, 'UnknownMainError')
+ }
+ , 'test parse throws exception on only dashed (--) arguments arguments': function () {
+ assert.exception(function () {
+ argsParse.parse(buildargs('--foo --bar'))
+ }, 'UnknownMainError')
+ }
+
+ , 'test parse throws exception on unknown build commands': function () {
+ assert.exception(function () {
+ argsParse.parse(buildargs('unknown'))
+ }, 'UnknownMainError')
+
+ assert.exception(function () {
+ argsParse.parse(buildargs('--foo bar --woo'))
+ }, 'UnknownMainError')
+ }
+
+ , 'test parse returns remaining non dashed arguments': function () {
+ var actual = argsParse.parse(buildargs('search --output foo bar woo hoo'))
+ assert.isArray(actual.remaining)
+ assert.equals(actual.remaining, [ 'bar', 'woo', 'hoo' ])
+ }
+
+ , 'test parse returns remaining arguments as empty array if none provided': function () {
+ var actual = argsParse.parse(buildargs('search'))
+ assert.isArray(actual.remaining)
+ assert.equals(actual.remaining.length, 0)
+ }
+
+ , 'test parse returns remaining arguments as empty array if only dashed (--) provided': function () {
+ var actual = argsParse.parse(buildargs('search --foo'))
+ assert.isArray(actual.remaining)
+ assert.equals(actual.remaining.length, 0)
+ }
+})
6 test/buster.js
@@ -0,0 +1,6 @@
+var config = module.exports
+
+config["Node tests"] = {
+ environment: "node"
+ , tests: ["*-test.js"]
+}
20 test/common.js
@@ -0,0 +1,20 @@
+var buster = require('buster')
+
+buster.assertions.add('isString', {
+ assert: function(actual) {
+ return typeof actual == 'string'
+ }
+ , assertMessage: 'expected ${0} to be a string'
+})
+buster.assertions.add('isArray', {
+ assert: function(actual) {
+ return Array.isArray(actual)
+ }
+ , assertMessage: 'expected ${0} to be an Array'
+})
+buster.assertions.add('fail', {
+ assert: function() {
+ return false
+ }
+ , assertMessage: '${0}'
+})
42 test/main-search-output-test.js
@@ -0,0 +1,42 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , SearchOutput = require('../lib/main-search-output')
+
+buster.testCase('Search output', {
+ setUp: function () {
+ // emulate `require('util')`
+ this.out = {
+ buf: ''
+ , print: function(s) {
+ this.buf += s
+ }
+ }
+ this.output = SearchOutput.create()
+ }
+
+ , 'test searchInit()': function () {
+ this.output.init(this.out)
+ this.output.searchInit()
+ assert.match(this.out.buf, 'Searching NPM registry...')
+ }
+
+ , 'test searchNoResults()': function () {
+ this.output.init(this.out)
+ this.output.searchNoResults()
+ assert.match(this.out.buf, 'Sorry, we couldn\'t find anything')
+ }
+
+ , 'test searchError() with debug=false': function () {
+ this.output.init(this.out, false)
+ this.output.searchError(new Error())
+ assert.match(this.out.buf, 'Something went wrong searching NPM')
+ }
+
+ , 'test searchError() with debug=true': function () {
+ this.output.init(this.out, true)
+ assert.exception(function () {
+ this.output.searchError(new Error())
+ })
+ }
+})
22 test/main-search-test.js
@@ -0,0 +1,22 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , search = require('../lib/main-search')
+
+buster.testCase('Search', {
+ 'test exec() calls setup(), search() and packup() on repository': function () {
+ var repository = require('../lib/repository')
+ , mock = this.mock(repository)
+ , terms = 'terms argument'
+
+ mock.expects('setup').once().callsArg(0)
+ var searchExpectation = mock.expects('search').once().callsArg(1)
+ mock.expects('packup').once()
+
+ search.exec({ remaining: terms })
+
+ assert.same(searchExpectation.args[0][0], terms)
+ assert.isFunction(searchExpectation.args[0][1]) // internal 'handle()' method
+ }
+})
+
38 test/main-search-util-test.js
@@ -0,0 +1,38 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , searchUtil = require('../lib/main-search-util')
+
+buster.testCase('Search Util', {
+ 'test escapeRegExp plain strings': function () {
+ assert.equals(searchUtil.escapeRegExp(''), '')
+ assert.equals(searchUtil.escapeRegExp('vanilla'), 'vanilla')
+ }
+
+ , 'test escapeRegExp escapable strings': function () {
+ assert.equals(searchUtil.escapeRegExp('-[]{}()*+?.\\^$|,# '), '\\-\\[\\]\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|\\,\\#\\ ')
+ }
+
+ , 'test sortByRegExp': function () {
+ var regex = /a|D|6|I$/ // match 0, 1 & 3 ('6' is ignored because it's not in priority list
+ , array = [
+ { p1: 'abc', p2: '123', p3: 'ABC' }
+ , { p1: 'cde', p2: '345', p3: 'CDE' }
+ , { p1: 'efg', p2: '567', p3: 'EFG' }
+ , { p1: 'ghi', p2: '789', p3: 'GHI' }
+ ]
+ , arrayCopy = [ array[0], array[1], array[2], array[3] ]
+ , ranked = []
+ , priority = [ 'p3', 'p1' ] // means we should get, in order: 1, 3, 0
+
+ searchUtil.sortByRegExp(regex, arrayCopy, ranked, priority)
+
+ assert.equals(ranked.length, 3)
+ assert.same(ranked[0], array[1])
+ assert.same(ranked[1], array[3])
+ assert.same(ranked[2], array[0])
+
+ assert.equals(arrayCopy.length, 1)
+ assert.same(arrayCopy[0], array[2])
+ }
+})
44 test/main-test.js
@@ -0,0 +1,44 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , main = require('../lib/main')
+
+buster.testCase('Main program', {
+ tearDown: function () {
+ //delete require.cache[require.resolve('../lib/args-parse')]
+ }
+
+ , 'test main has exec': function () {
+ assert.isFunction(main.exec)
+ }
+
+ , 'test main calls argsParse to parse arguments': function () {
+ var argsParse = require('../lib/args-parse')
+ //, search = require('../lib/main-search')
+ , expectedArgs = [ 'foo', 'bar', 'search' ]
+
+ argsParseMock = this.mock(argsParse)
+ //searchMock = this.mock(search)
+
+ argsParseMock.expects('parse').once().withArgs(expectedArgs).returns(null)
+ //searchMock.expects('exec').once()
+
+ main.exec(expectedArgs)
+ assert(true)
+ }
+
+ , 'test main loads main module as specified by args-parse': function () {
+ var argsParse = require('../lib/args-parse')
+ , search = require('../lib/main-search')
+ , expectedArgs = [ 'foo', 'bar', 'search' ]
+
+ argsParseMock = this.mock(argsParse)
+ searchMock = this.mock(search)
+
+ argsParseMock.expects('parse').once().withArgs(expectedArgs).returns({ main: 'search' })
+ searchMock.expects('exec').once()
+
+ main.exec(expectedArgs)
+ assert(true)
+ }
+})
67 test/output-test.js
@@ -0,0 +1,67 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , Output = require('../lib/output')
+
+buster.testCase('Output (base)', {
+ setUp: function () {
+ // emulate `require('util')`
+ this.out = {
+ buf: ''
+ , print: function(s) {
+ this.buf += s
+ }
+ }
+ this.output = Output.create()
+ }
+
+ , 'test log(str)': function () {
+ this.output.init(this.out)
+ this.output.log('a string')
+ assert.equals(this.out.buf, 'a string\n')
+ }
+
+ , 'test debug(str) with debug=false': function () {
+ this.output.init(this.out)
+ this.output.debug('a string')
+ assert.equals(this.out.buf, '')
+ }
+
+ , 'test debug(str) with debug=true': function () {
+ this.output.init(this.out, true)
+ this.output.debug('a string')
+ assert.equals(this.out.buf, 'DEBUG: a string\n')
+ }
+
+ , 'test repositoryLoadError() with debug=false': function () {
+ this.output.init(this.out, false)
+ this.output.repositoryLoadError(new Error())
+ assert.match(this.out.buf, 'Something went wrong trying to load NPM')
+ }
+
+ , 'test repositoryLoadError() with debug=true': function () {
+ this.output.init(this.out, true)
+ assert.exception(function () {
+ this.output.repositoryLoadError(new Error())
+ })
+ }
+
+ , 'test heading() short': function () {
+ this.output.init(this.out)
+ this.output.heading('This is a heading')
+ assert.match(this.out.buf, /This is a heading[^\n]*\n-----------------/)
+ }
+
+ , 'test heading() long': function () {
+ this.output.init(this.out)
+ this.output.heading('H1')
+ assert.match(this.out.buf, /H1[^\n]*\n--/)
+ }
+
+ , 'test heading() with meta': function () {
+ this.output.init(this.out)
+ this.output.heading('H1', 'h2')
+ assert.match(this.out.buf, /H1[^\n ]* \(h2\)[^\n]*\n--/)
+ }
+
+})
170 test/repository-test.js
@@ -0,0 +1,170 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , fs = require('fs')
+ , repository = require('../lib/repository')
+ , util = require('../lib/util')
+
+ , executeSetupPackupTempDirTest = function (execute, verify, done) {
+ fs.readdir(util.tmpDir, function (err, files) {
+ if (err) {
+ assert.fail('couldn\'t list files in temp dir: ' + util.tmpDir)
+ done()
+ } else {
+ execute(function () {
+ fs.readdir(util.tmpDir, function (err, files2) {
+ if (err) {
+ assert.fail('couldn\'t list files in temp dir: ' + util.tmpDir)
+ done()
+ } else {
+ verify(files, files2)
+ done()
+ }
+ })
+ })
+ }
+ })
+ }
+ , verifySameFiles = function (files, files2) {
+ assert.equals(files2, files)
+ }
+
+buster.testCase('Repository (NPM interface)', {
+ 'setup() and packup()': {
+ 'test setup() and packup() exist': function () {
+ assert.isFunction(repository.setup)
+ assert.isFunction(repository.packup)
+ assert.equals(1, repository.setup.length)
+ assert.equals(2, repository.packup.length)
+ }
+
+ , 'test setup() and packup() leave no temporary files': function (done) {
+ executeSetupPackupTempDirTest(
+ function (callback) {
+ repository.setup(function () {
+ repository.packup(null, callback)
+ })
+ }
+ , verifySameFiles
+ , done
+ )
+ }
+
+ , 'test multiple sequential setup()/packup() leave no temporary files': function (done) {
+ executeSetupPackupTempDirTest(
+ function (callback) {
+ repository.setup(function () {
+ repository.packup(null, function () {
+ repository.setup(function () {
+ repository.packup(null, callback)
+ })
+ })
+ })
+ }
+ , verifySameFiles
+ , done
+ )
+ }
+
+ , 'test multiple stacked setup() and packup() leave no temporary files and throw no errors': function (done) {
+ executeSetupPackupTempDirTest(
+ function (callback) {
+ repository.setup(function () {
+ repository.setup(function () {
+ repository.packup(null, function () {
+ repository.packup(null, callback)
+ })
+ })
+ })
+ }
+ , verifySameFiles
+ , done
+ )
+ }
+
+ , 'test setup creates output file given by packup(false)': function (done) {
+ executeSetupPackupTempDirTest(
+ function (callback) {
+ callback()
+ }
+ , function () {
+ assert(true)
+ }
+ , done
+ )
+ }
+ }
+
+ , 'setup()': {
+ 'test setup() calls npm.load()': function (done) {
+ var npm = require('npm')
+ , npmMock = this.mock(npm)
+ , finish = function () {
+ repository.packup()
+ done()
+ }
+
+ npmMock.expects('load').once().callsArg(1)
+ repository.setup(finish)
+ assert(true) // required, buster issue #62
+ }
+
+ , 'test setup() calls npm.load() only once regardless of how many times setup() is called': function (done) {
+ var npm = require('npm')
+ , npmMock = this.mock(npm)
+ , finish = function () {
+ repository.packup()
+ done()
+ }
+
+ npmMock.expects('load').once().callsArg(1)
+ repository.setup(function () { // mock calls this one
+ repository.setup(function () { // isSetup shortcut calls this one
+ repository.setup(finish) // isSetup shortcut calls this too
+ })
+ })
+ assert(true) // required, buster issue #62
+ }
+ }
+
+ , 'search()': {
+ setUp: function () {
+ // we have to replace npm.commands because all properties on the original object
+ // are defined with only getters (via defineProperty) so can't be mocked as they are
+ this.npm = require('npm')
+ this.npmCommandsOriginal = this.npm.commands
+ this.npm.commands = this.npmCommands = {
+ search: function () {}
+ }
+ }
+ , tearDown: function () {
+ this.npm.commands = this.npmCommandsOriginal
+ }
+
+ , 'test search() throws RepositorySetupError if setup() has not been called': function () {
+ assert.exception(repository.search, 'RepositorySetupError')
+ }
+
+ , 'test search() calls npm.commands.search()': function (done) {
+ var npm = require('npm')
+ , npmMock = this.mock(npm)
+ , npmCommandsMock = this.mock(npm.commands)
+ , keywords = 'keywords argument'
+ , finish = function () {
+ repository.packup()
+ done()
+ }
+
+ npmMock.expects('load').once().callsArg(1)
+ npmCommandsMock.expects('search').once().withArgs(keywords, finish).callsArg(1)
+
+ repository.setup(function () {
+ repository.search(keywords, finish)
+ })
+
+ assert(true) // required, buster issue #62
+ }
+ }
+
+})
+
61 test/util-test.js
@@ -0,0 +1,61 @@
+require('./common')
+var buster = require('buster')
+ , assert = buster.assert
+ , fs = require('fs')
+ , path = require('path')
+ , util = require('../lib/util')
+
+ , verifyWritable = function (name, dir, done) {
+ path.exists(dir, function (exists) {
+ if (!exists) {
+ assert.fail(name + ' directory doesn\'t exist:: ' + dir)
+ done()
+ } else {
+ var tmpFile = dir + '/ender_test.' + (+new Date)
+ fs.writeFile(tmpFile, 'Test data', function (err) {
+ if (err) {
+ assert.fail('couldn\'t write to ' + name + ' file: ' + tmpFile)
+ done()
+ } else {
+ fs.unlink(tmpFile, function (err) {
+ if (err)
+ assert.fail('couldn\'t delete ' + name + ' file: ' + tmpFile)
+ done()
+ })
+ }
+ })
+ }
+ })
+ }
+
+buster.testCase('Util', {
+ 'test util.tmp is a writeable directory': function (done) {
+ assert.isString(util.tmpDir)
+ verifyWritable('temp', util.tmpDir, done)
+ }
+
+ , 'test util.home is a writeable directory': function (done) {
+ assert.isString(util.homeDir)
+ verifyWritable('home', util.homeDir, done)
+ }
+
+ , 'test basic extend()': function () {
+ var src = { 'one': 1, 'two': 2 }
+ , dst = { 'two': 2.2, 'three': 3 }
+
+ , actual = util.extend(src, dst)
+
+ assert.same(actual, dst) // the return is just a convenience
+
+ assert.equals(Object.keys(actual).length, 3)
+ assert.equals(actual.one, src.one)
+ assert.equals(actual.two, 2.2) // didn't overwrite existing property
+ assert.equals(actual.three, 3)
+
+ // left src untouched?
+ assert.equals(Object.keys(src).length, 2)
+ assert.equals(src.one, 1)
+ assert.equals(src.two, 2)
+ }
+
+})
Please sign in to comment.
Something went wrong with that request. Please try again.