Skip to content

Commit dd47238

Browse files
committed
fix: consolidate split-package-names
It was only ever used by `npm edit` so it's inline now. Rewrote `npm edit` tests to be real
1 parent b48a2bf commit dd47238

File tree

10 files changed

+146
-175
lines changed

10 files changed

+146
-175
lines changed

lib/commands/edit.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,31 @@
44
const { resolve } = require('path')
55
const fs = require('graceful-fs')
66
const { spawn } = require('child_process')
7-
const splitPackageNames = require('../utils/split-package-names.js')
87
const completion = require('../utils/completion/installed-shallow.js')
98
const BaseCommand = require('../base-command.js')
109

10+
const splitPackageNames = (path) => {
11+
return path.split('/')
12+
// combine scoped parts
13+
.reduce((parts, part) => {
14+
if (parts.length === 0) {
15+
return [part]
16+
}
17+
18+
const lastPart = parts[parts.length - 1]
19+
// check if previous part is the first part of a scoped package
20+
if (lastPart[0] === '@' && !lastPart.includes('/')) {
21+
parts[parts.length - 1] += '/' + part
22+
} else {
23+
parts.push(part)
24+
}
25+
26+
return parts
27+
}, [])
28+
.join('/node_modules/')
29+
.replace(/(\/node_modules)+/, '/node_modules')
30+
}
31+
1132
class Edit extends BaseCommand {
1233
static description = 'Edit an installed package'
1334
static name = 'edit'

lib/utils/split-package-names.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

test/fixtures/tspawk.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict'
2+
3+
const spawk = require('spawk')
4+
5+
module.exports = tspawk
6+
7+
function tspawk (t) {
8+
spawk.preventUnmatched()
9+
t.teardown(function () {
10+
spawk.unload()
11+
})
12+
t.afterEach(function () {
13+
spawk.done()
14+
})
15+
return spawk
16+
}

test/lib/commands/config.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const { join } = require('path')
22
const { promisify } = require('util')
33
const fs = require('fs')
4-
const spawk = require('spawk')
4+
const tspawk = require('../../fixtures/tspawk')
55
const t = require('tap')
66

7-
spawk.preventUnmatched()
7+
const spawk = tspawk(t)
88

99
const readFile = promisify(fs.readFile)
1010

@@ -369,10 +369,6 @@ t.test('config edit', async t => {
369369
'.npmrc': 'foo=bar\nbar=baz',
370370
})
371371

372-
t.teardown(() => {
373-
spawk.clean()
374-
})
375-
376372
const EDITOR = 'vim'
377373
const editor = spawk.spawn(EDITOR).exit(0)
378374

@@ -394,10 +390,6 @@ t.test('config edit', async t => {
394390
})
395391

396392
t.test('config edit - editor exits non-0', async t => {
397-
t.teardown(() => {
398-
spawk.clean()
399-
})
400-
401393
const EDITOR = 'vim'
402394
const editor = spawk.spawn(EDITOR).exit(1)
403395

test/lib/commands/edit.js

Lines changed: 98 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,135 @@
11
const t = require('tap')
2-
const { resolve } = require('path')
3-
const { EventEmitter } = require('events')
2+
const path = require('path')
3+
const tspawk = require('../../fixtures/tspawk')
4+
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
45

5-
let editorBin = null
6-
let editorArgs = null
7-
let editorOpts = null
8-
let EDITOR_CODE = 0
9-
const childProcess = {
10-
spawn: (bin, args, opts) => {
11-
// save for assertions
12-
editorBin = bin
13-
editorArgs = args
14-
editorOpts = opts
6+
const spawk = tspawk(t)
157

16-
const editorEvents = new EventEmitter()
17-
process.nextTick(() => {
18-
editorEvents.emit('exit', EDITOR_CODE)
19-
})
20-
return editorEvents
21-
},
22-
}
8+
// TODO this ... smells. npm "script-shell" config mentions defaults but those
9+
// are handled by run-script, not npm. So for now we have to tie tests to some
10+
// pretty specific internals of runScript
11+
const makeSpawnArgs = require('@npmcli/run-script/lib/make-spawn-args.js')
2312

24-
let rebuildArgs = null
25-
let rebuildFail = null
26-
let EDITOR = 'vim'
27-
const npm = {
13+
const npmConfig = {
2814
config: {
29-
get: () => EDITOR,
15+
editor: 'testeditor',
3016
},
31-
dir: resolve(__dirname, '../../../node_modules'),
32-
exec: async (cmd, args) => {
33-
rebuildArgs = args
34-
if (rebuildFail) {
35-
throw rebuildFail
36-
}
17+
prefixDir: {
18+
node_modules: {
19+
semver: {
20+
'package.json': JSON.stringify({
21+
scripts: {
22+
install: 'testinstall',
23+
},
24+
}),
25+
node_modules: {
26+
abbrev: {},
27+
},
28+
},
29+
'@npmcli': {
30+
'scoped-package': {},
31+
},
32+
},
3733
},
3834
}
3935

40-
const gracefulFs = require('graceful-fs')
41-
const Edit = t.mock('../../../lib/commands/edit.js', {
42-
child_process: childProcess,
43-
'graceful-fs': gracefulFs,
44-
})
45-
const edit = new Edit(npm)
46-
4736
t.test('npm edit', async t => {
48-
t.teardown(() => {
49-
rebuildArgs = null
50-
editorBin = null
51-
editorArgs = null
52-
editorOpts = null
53-
})
37+
const { npm, joinedOutput } = await loadMockNpm(t, npmConfig)
5438

55-
await edit.exec(['semver'])
56-
const path = resolve(__dirname, '../../../node_modules/semver')
57-
t.strictSame(editorBin, EDITOR, 'used the correct editor')
58-
t.strictSame(editorArgs, [path], 'edited the correct directory')
59-
t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
60-
t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
39+
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
40+
const [scriptShell] = makeSpawnArgs({
41+
event: 'install',
42+
path: npm.prefix,
43+
})
44+
spawk.spawn('testeditor', [semverPath])
45+
spawk.spawn(
46+
scriptShell,
47+
args => args.includes('testinstall'),
48+
{ cwd: semverPath }
49+
)
50+
await npm.exec('edit', ['semver'])
51+
t.match(joinedOutput(), 'rebuilt dependencies successfully')
6152
})
6253

63-
t.test('rebuild fails', async t => {
64-
t.teardown(() => {
65-
rebuildFail = null
66-
rebuildArgs = null
67-
editorBin = null
68-
editorArgs = null
69-
editorOpts = null
54+
t.test('rebuild failure', async t => {
55+
const { npm } = await loadMockNpm(t, npmConfig)
56+
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
57+
const [scriptShell] = makeSpawnArgs({
58+
event: 'install',
59+
path: npm.prefix,
7060
})
61+
spawk.spawn('testeditor', [semverPath])
62+
spawk.spawn(
63+
scriptShell,
64+
args => args.includes('testinstall'),
65+
{ cwd: semverPath }
66+
).exit(1).stdout('test error')
67+
await t.rejects(
68+
npm.exec('edit', ['semver']),
69+
{ message: 'command failed' }
70+
)
71+
})
7172

72-
rebuildFail = new Error('test error')
73+
t.test('editor failure', async t => {
74+
const { npm } = await loadMockNpm(t, npmConfig)
75+
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
76+
spawk.spawn('testeditor', [semverPath]).exit(1).stdout('test editor failure')
7377
await t.rejects(
74-
edit.exec(['semver']),
75-
{ message: 'test error' }
78+
npm.exec('edit', ['semver']),
79+
{ message: 'editor process exited with code: 1' }
7680
)
77-
const path = resolve(__dirname, '../../../node_modules/semver')
78-
t.strictSame(editorBin, EDITOR, 'used the correct editor')
79-
t.strictSame(editorArgs, [path], 'edited the correct directory')
80-
t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
81-
t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
8281
})
8382

8483
t.test('npm edit editor has flags', async t => {
85-
EDITOR = 'code -w'
86-
t.teardown(() => {
87-
rebuildArgs = null
88-
editorBin = null
89-
editorArgs = null
90-
editorOpts = null
91-
EDITOR = 'vim'
84+
const { npm } = await loadMockNpm(t, {
85+
...npmConfig,
86+
config: {
87+
editor: 'testeditor --flag',
88+
},
9289
})
9390

94-
await edit.exec(['semver'])
95-
96-
const path = resolve(__dirname, '../../../node_modules/semver')
97-
t.strictSame(editorBin, 'code', 'used the correct editor')
98-
t.strictSame(editorArgs, ['-w', path], 'edited the correct directory, keeping flags')
99-
t.strictSame(editorOpts, { stdio: 'inherit' }, 'passed the correct opts')
100-
t.strictSame(rebuildArgs, [path], 'passed the correct path to rebuild')
91+
const semverPath = path.resolve(npm.prefix, 'node_modules', 'semver')
92+
const [scriptShell] = makeSpawnArgs({
93+
event: 'install',
94+
path: npm.prefix,
95+
})
96+
spawk.spawn('testeditor', ['--flag', semverPath])
97+
spawk.spawn(
98+
scriptShell,
99+
args => args.includes('testinstall'),
100+
{ cwd: semverPath }
101+
)
102+
await npm.exec('edit', ['semver'])
101103
})
102104

103105
t.test('npm edit no args', async t => {
106+
const { npm } = await loadMockNpm(t)
104107
await t.rejects(
105-
edit.exec([]),
106-
/npm edit/,
108+
npm.exec('edit', []),
109+
{ code: 'EUSAGE' },
107110
'throws usage error'
108111
)
109112
})
110113

111-
t.test('npm edit lstat error propagates', async t => {
112-
const _lstat = gracefulFs.lstat
113-
gracefulFs.lstat = (dir, cb) => {
114-
return cb(new Error('lstat failed'))
115-
}
116-
t.teardown(() => {
117-
gracefulFs.lstat = _lstat
118-
})
114+
t.test('npm edit nonexistent package', async t => {
115+
const { npm } = await loadMockNpm(t, npmConfig)
119116

120117
await t.rejects(
121-
edit.exec(['semver']),
122-
/lstat failed/,
123-
'user received correct error'
118+
npm.exec('edit', ['abbrev']),
119+
/lstat/
124120
)
125121
})
126122

127-
t.test('npm edit editor exit code error propagates', async t => {
128-
EDITOR_CODE = 137
129-
t.teardown(() => {
130-
EDITOR_CODE = 0
131-
})
123+
t.test('scoped package', async t => {
124+
const { npm } = await loadMockNpm(t, npmConfig)
125+
const scopedPath = path.resolve(npm.prefix, 'node_modules', '@npmcli', 'scoped-package')
126+
spawk.spawn('testeditor', [scopedPath])
127+
await npm.exec('edit', ['@npmcli/scoped-package'])
128+
})
132129

133-
await t.rejects(
134-
edit.exec(['semver']),
135-
/exited with code: 137/,
136-
'user received correct error'
137-
)
130+
t.test('subdependency', async t => {
131+
const { npm } = await loadMockNpm(t, npmConfig)
132+
const subdepPath = path.resolve(npm.prefix, 'node_modules', 'semver', 'node_modules', 'abbrev')
133+
spawk.spawn('testeditor', [subdepPath])
134+
await npm.exec('edit', ['semver/abbrev'])
138135
})

test/lib/commands/restart.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
const t = require('tap')
2-
const spawk = require('spawk')
2+
const tspawk = require('../../fixtures/tspawk')
33
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
44

5-
spawk.preventUnmatched()
6-
t.teardown(() => {
7-
spawk.unload()
8-
})
5+
const spawk = tspawk(t)
96

107
// TODO this ... smells. npm "script-shell" config mentions defaults but those
118
// are handled by run-script, not npm. So for now we have to tie tests to some

test/lib/commands/start.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
const t = require('tap')
2-
const spawk = require('spawk')
2+
const tspawk = require('../../fixtures/tspawk')
33
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
44

5-
spawk.preventUnmatched()
6-
t.teardown(() => {
7-
spawk.unload()
8-
})
5+
const spawk = tspawk(t)
96

107
// TODO this ... smells. npm "script-shell" config mentions defaults but those
118
// are handled by run-script, not npm. So for now we have to tie tests to some

test/lib/commands/stop.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
const t = require('tap')
2-
const spawk = require('spawk')
2+
const tspawk = require('../../fixtures/tspawk')
33
const { load: loadMockNpm } = require('../../fixtures/mock-npm')
44

5-
spawk.preventUnmatched()
6-
t.teardown(() => {
7-
spawk.unload()
8-
})
5+
const spawk = tspawk(t)
96

107
// TODO this ... smells. npm "script-shell" config mentions defaults but those
118
// are handled by run-script, not npm. So for now we have to tie tests to some

0 commit comments

Comments
 (0)