Skip to content

Commit 3a6dd51

Browse files
committed
refactor edit, add tests
1 parent d6817f0 commit 3a6dd51

File tree

2 files changed

+151
-44
lines changed

2 files changed

+151
-44
lines changed

lib/edit.js

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,36 @@
11
// npm edit <pkg>
22
// open the package folder in the $EDITOR
33

4-
module.exports = edit
5-
edit.usage = 'npm edit <pkg>[/<subpkg>...]'
4+
const { resolve } = require('path')
5+
const fs = require('graceful-fs')
6+
const { spawn } = require('child_process')
7+
const npm = require('./npm.js')
8+
const usageUtil = require('./utils/usage.js')
9+
const splitPackageNames = require('./utils/split-package-names.js')
610

7-
edit.completion = require('./utils/completion/installed-shallow.js')
8-
9-
var npm = require('./npm.js')
10-
var path = require('path')
11-
var fs = require('graceful-fs')
12-
var editor = require('editor')
13-
var noProgressTillDone = require('./utils/no-progress-while-running').tillDone
11+
const usage = usageUtil('edit', 'npm edit <pkg>[/<subpkg>...]')
12+
const completion = require('./utils/completion/installed-shallow.js')
1413

1514
function edit (args, cb) {
16-
var p = args[0]
17-
if (args.length !== 1 || !p)
18-
return cb(edit.usage)
19-
var e = npm.config.get('editor')
20-
if (!e) {
21-
return cb(new Error(
22-
"No editor set. Set the 'editor' config, or $EDITOR environ."
23-
))
24-
}
25-
p = p.split('/')
26-
// combine scoped parts
27-
.reduce(function (parts, part) {
28-
if (parts.length === 0)
29-
return [part]
30-
31-
var lastPart = parts[parts.length - 1]
32-
// check if previous part is the first part of a scoped package
33-
if (lastPart[0] === '@' && !lastPart.includes('/'))
34-
parts[parts.length - 1] += '/' + part
35-
else
36-
parts.push(part)
37-
38-
return parts
39-
}, [])
40-
.join('/node_modules/')
41-
.replace(/(\/node_modules)+/, '/node_modules')
42-
var f = path.resolve(npm.dir, p)
43-
fs.lstat(f, function (er) {
44-
if (er)
45-
return cb(er)
46-
editor(f, { editor: e }, noProgressTillDone(function (er) {
47-
if (er)
48-
return cb(er)
49-
npm.commands.rebuild(args, cb)
50-
}))
15+
if (args.length !== 1)
16+
return cb(usage)
17+
18+
const path = splitPackageNames(args[0])
19+
const dir = resolve(npm.dir, path)
20+
21+
fs.lstat(dir, (err) => {
22+
if (err)
23+
return cb(err)
24+
25+
const [bin, ...args] = npm.config.get('editor').split(/\s+/)
26+
const editor = spawn(bin, [...args, dir], { stdio: 'inherit' })
27+
editor.on('exit', (code) => {
28+
if (code)
29+
return cb(new Error(`editor process exited with code: ${code}`))
30+
31+
npm.commands.rebuild([dir], cb)
32+
})
5133
})
5234
}
35+
36+
module.exports = Object.assign(edit, { completion, usage })

test/lib/edit.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const { test } = require('tap')
2+
const { resolve } = require('path')
3+
const requireInject = require('require-inject')
4+
const { EventEmitter } = require('events')
5+
6+
let editorBin = null
7+
let editorArgs = null
8+
let editorOpts = null
9+
let EDITOR_CODE = 0
10+
const childProcess = {
11+
spawn: (bin, args, opts) => {
12+
// save for assertions
13+
editorBin = bin
14+
editorArgs = args
15+
editorOpts = opts
16+
17+
const editorEvents = new EventEmitter()
18+
process.nextTick(() => {
19+
editorEvents.emit('exit', EDITOR_CODE)
20+
})
21+
return editorEvents
22+
},
23+
}
24+
25+
let rebuildArgs = null
26+
let EDITOR = 'vim'
27+
const npm = {
28+
config: {
29+
get: () => EDITOR,
30+
},
31+
dir: resolve(__dirname, '../../node_modules'),
32+
commands: {
33+
rebuild: (args, cb) => {
34+
rebuildArgs = args
35+
return cb()
36+
},
37+
},
38+
}
39+
40+
const gracefulFs = require('graceful-fs')
41+
const edit = requireInject('../../lib/edit.js', {
42+
'../../lib/npm.js': npm,
43+
child_process: childProcess,
44+
'graceful-fs': gracefulFs,
45+
})
46+
47+
test('npm edit', t => {
48+
t.teardown(() => {
49+
rebuildArgs = null
50+
editorBin = null
51+
editorArgs = null
52+
editorOpts = null
53+
})
54+
55+
return edit(['semver'], (err) => {
56+
if (err)
57+
throw err
58+
59+
const path = resolve(__dirname, '../../node_modules/semver')
60+
t.strictSame(editorBin, EDITOR, 'used the correct editor')
61+
t.strictSame(editorArgs, [path], 'edited the correct directory')
62+
t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
63+
t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
64+
t.end()
65+
})
66+
})
67+
68+
test('npm edit editor has flags', t => {
69+
EDITOR = 'code -w'
70+
t.teardown(() => {
71+
rebuildArgs = null
72+
editorBin = null
73+
editorArgs = null
74+
editorOpts = null
75+
EDITOR = 'vim'
76+
})
77+
78+
return edit(['semver'], (err) => {
79+
if (err)
80+
throw err
81+
82+
const path = resolve(__dirname, '../../node_modules/semver')
83+
t.strictSame(editorBin, 'code', 'used the correct editor')
84+
t.strictSame(editorArgs, ['-w', path], 'edited the correct directory, keeping flags')
85+
t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
86+
t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
87+
t.end()
88+
})
89+
})
90+
91+
test('npm edit no args', t => {
92+
return edit([], (err) => {
93+
t.match(err, /npm edit/, 'throws usage error')
94+
t.end()
95+
})
96+
})
97+
98+
test('npm edit lstat error propagates', t => {
99+
const _lstat = gracefulFs.lstat
100+
gracefulFs.lstat = (dir, cb) => {
101+
return cb(new Error('lstat failed'))
102+
}
103+
t.teardown(() => {
104+
gracefulFs.lstat = _lstat
105+
})
106+
107+
return edit(['semver'], (err) => {
108+
t.match(err, /lstat failed/, 'user received correct error')
109+
t.end()
110+
})
111+
})
112+
113+
test('npm edit editor exit code error propagates', t => {
114+
EDITOR_CODE = 137
115+
t.teardown(() => {
116+
EDITOR_CODE = 0
117+
})
118+
119+
return edit(['semver'], (err) => {
120+
t.match(err, /exited with code: 137/, 'user received correct error')
121+
t.end()
122+
})
123+
})

0 commit comments

Comments
 (0)