1const t = require('tap') 2const localeCompare = require('@isaacs/string-locale-compare')('en') 3const { load: loadMockNpm } = require('../../fixtures/mock-npm.js') 4const { cleanCwd } = require('../../fixtures/clean-snapshot') 5 6const genManPages = (obj) => { 7 const man = {} 8 const resPages = new Set() 9 10 for (const [section, pages] of Object.entries(obj)) { 11 const num = parseInt(section, 10) 12 man[`man${num}`] = {} 13 14 const sectionPages = [] 15 for (const name of pages) { 16 man[`man${num}`][`${name}.${section}`] = `.TH "${name.toUpperCase()}" "${num}"` 17 sectionPages.push(name.replace(/^npm-/, '')) 18 } 19 20 // return a sorted list of uniq pages in order to test completion 21 for (const p of sectionPages.sort(localeCompare)) { 22 resPages.add(p) 23 } 24 } 25 26 // man directory name is hardcoded in the command 27 return { fixtures: { man }, pages: [...resPages.values()] } 28} 29 30const mockHelp = async (t, { 31 man = { 32 5: ['npmrc', 'install', 'package-json'], 33 1: ['whoami', 'install', 'star', 'unstar', 'uninstall', 'unpublish'].map(p => `npm-${p}`), 34 7: ['disputes', 'config'], 35 }, 36 browser = false, 37 woman = false, 38 exec: execArgs = null, 39 spawnErr, 40 ...opts 41} = {}) => { 42 const config = { 43 // always set viewer to test the same on all platforms 44 viewer: browser ? 'browser' : woman ? 'woman' : 'man', 45 ...opts.config, 46 } 47 48 let args = null 49 const mockSpawn = async (...a) => { 50 args = a 51 if (spawnErr) { 52 throw spawnErr 53 } 54 } 55 mockSpawn.open = async (url) => args = [cleanCwd(decodeURI(url))] 56 57 const manPages = genManPages(man) 58 59 const { npm, ...rest } = await loadMockNpm(t, { 60 npm: ({ other }) => ({ npmRoot: other }), 61 mocks: { '@npmcli/promise-spawn': mockSpawn }, 62 otherDirs: { ...manPages.fixtures }, 63 config, 64 command: 'help', 65 exec: execArgs, 66 ...opts, 67 }) 68 69 return { 70 npm, 71 manPages: manPages.pages, 72 getArgs: () => args, 73 ...rest, 74 } 75} 76 77t.test('npm help', async t => { 78 const { help, joinedOutput } = await mockHelp(t) 79 await help.exec() 80 81 t.match(joinedOutput(), 'npm <command>', 'showed npm usage') 82}) 83 84t.test('npm help completion', async t => { 85 const { help, manPages } = await mockHelp(t) 86 87 const noArgs = await help.completion({ conf: { argv: { remain: [] } } }) 88 t.strictSame(noArgs, ['help', ...manPages], 'outputs available help pages') 89 const threeArgs = await help.completion({ conf: { argv: { remain: ['one', 'two', 'three'] } } }) 90 t.strictSame(threeArgs, [], 'outputs no results when more than 2 args are provided') 91}) 92 93t.test('npm help multiple args calls search', async t => { 94 const { joinedOutput } = await mockHelp(t, { exec: ['run', 'script'] }) 95 96 t.match(joinedOutput(), 'No matches in help for: run script', 'calls help-search') 97}) 98 99t.test('npm help no matches calls search', async t => { 100 const { joinedOutput } = await mockHelp(t, { exec: ['asdfasdf'] }) 101 102 t.match(joinedOutput(), 'No matches in help for: asdfasdf', 'passed the args to help-search') 103}) 104 105t.test('npm help whoami', async t => { 106 const { getArgs } = await mockHelp(t, { exec: ['whoami'] }) 107 108 const [spawnBin, spawnArgs] = getArgs() 109 t.equal(spawnBin, 'man', 'calls man by default') 110 t.equal(spawnArgs.length, 1) 111 t.match(spawnArgs[0], /npm-whoami\.1$/) 112}) 113 114t.test('npm help 1 install', async t => { 115 const { getArgs } = await mockHelp(t, { 116 exec: ['1', 'install'], 117 browser: true, 118 }) 119 120 const [url] = getArgs() 121 t.match(url, /commands\/npm-install.html$/, 'attempts to open the correct url') 122 t.ok(url.startsWith('file:///'), 'opens with the correct uri schema') 123}) 124 125t.test('npm help 5 install', async t => { 126 const { getArgs } = await mockHelp(t, { 127 exec: ['5', 'install'], 128 browser: true, 129 }) 130 131 const [url] = getArgs() 132 t.match(url, /configuring-npm\/install.html$/, 'attempts to open the correct url') 133}) 134 135t.test('npm help 7 config', async t => { 136 const { getArgs } = await mockHelp(t, { 137 exec: ['7', 'config'], 138 browser: true, 139 }) 140 141 const [url] = getArgs() 142 t.match(url, /using-npm\/config.html$/, 'attempts to open the correct url') 143}) 144 145t.test('npm help package.json redirects to package-json', async t => { 146 const { getArgs } = await mockHelp(t, { 147 exec: ['package.json'], 148 }) 149 150 const [spawnBin, spawnArgs] = getArgs() 151 t.equal(spawnBin, 'man', 'calls man by default') 152 t.equal(spawnArgs.length, 1) 153 t.match(spawnArgs[0], /package-json\.5$/) 154}) 155 156t.test('npm help ?(un)star', async t => { 157 const { getArgs } = await mockHelp(t, { 158 exec: ['?(un)star'], 159 woman: true, 160 }) 161 162 const [spawnBin, spawnArgs] = getArgs() 163 t.equal(spawnBin, 'emacsclient', 'maps woman to emacs correctly') 164 t.equal(spawnArgs.length, 2) 165 t.match(spawnArgs[1], /^\(woman-find-file '/) 166 t.match(spawnArgs[1], /npm-star.1'\)$/) 167}) 168 169t.test('npm help un*', async t => { 170 const { getArgs } = await mockHelp(t, { 171 exec: ['un*'], 172 }) 173 174 const [spawnBin, spawnArgs] = getArgs() 175 t.equal(spawnBin, 'man', 'calls man by default') 176 t.equal(spawnArgs.length, 1) 177 t.match(spawnArgs[0], /npm-uninstall\.1$/) 178}) 179 180t.test('npm help - prefers lowest numbered npm prefixed help pages', async t => { 181 const { getArgs } = await mockHelp(t, { 182 man: { 183 6: ['npm-install'], 184 1: ['npm-install'], 185 5: ['install'], 186 7: ['npm-install'], 187 }, 188 exec: ['install'], 189 }) 190 191 const [spawnBin, spawnArgs] = getArgs() 192 t.equal(spawnBin, 'man', 'calls man by default') 193 t.equal(spawnArgs.length, 1) 194 t.match(spawnArgs[0], /npm-install\.1$/) 195}) 196 197t.test('npm help - works in the presence of strange man pages', async t => { 198 const { getArgs } = await mockHelp(t, { 199 man: { 200 '1strange': ['config'], 201 5: ['config'], 202 '6ssl': ['config'], 203 }, 204 exec: ['config'], 205 }) 206 207 const [spawnBin, spawnArgs] = getArgs() 208 t.equal(spawnBin, 'man', 'calls man by default') 209 t.equal(spawnArgs.length, 1) 210 t.match(spawnArgs[0], /config\.5$/) 211}) 212 213t.test('rejects with code', async t => { 214 const { help } = await mockHelp(t, { 215 spawnErr: Object.assign(new Error('errrrr'), { code: 'SPAWN_ERR' }), 216 }) 217 218 await t.rejects(help.exec(['whoami']), /help process exited with code: SPAWN_ERR/) 219}) 220 221t.test('rejects with no code', async t => { 222 const { help } = await mockHelp(t, { 223 spawnErr: new Error('errrrr'), 224 }) 225 226 await t.rejects(help.exec(['whoami']), /errrrr/) 227}) 228