• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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