xref: /third_party/node/deps/npm/test/lib/commands/view.js (revision 1cb0ef41)
1const t = require('tap')
2const { load: _loadMockNpm } = require('../../fixtures/mock-npm.js')
3
4t.cleanSnapshot = str => str
5  .replace(/(published ).*?( ago)/g, '$1{TIME}$2')
6
7// run the same as tap does when running directly with node
8process.stdout.columns = undefined
9
10// 3 days. its never yesterday and never a week ago
11const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 24 * 3)
12
13const packument = (nv, opts) => {
14  if (!opts.fullMetadata) {
15    throw new Error('must fetch fullMetadata')
16  }
17
18  if (!opts.preferOnline) {
19    throw new Error('must fetch with preferOnline')
20  }
21
22  const mocks = {
23    red: {
24      _id: 'red@1.0.1',
25      name: 'red',
26      'dist-tags': {
27        '1.0.1': {},
28      },
29      time: {
30        unpublished: {
31          time: '2012-12-20T00:00:00.000Z',
32        },
33      },
34    },
35    blue: {
36      _id: 'blue',
37      name: 'blue',
38      'dist-tags': {
39        latest: '1.0.0',
40      },
41      time: {
42        '1.0.0': yesterday,
43      },
44      versions: {
45        '1.0.0': {
46          name: 'blue',
47          version: '1.0.0',
48          dist: {
49            shasum: '123',
50            tarball: 'http://hm.blue.com/1.0.0.tgz',
51            integrity: '---',
52            fileCount: 1,
53            unpackedSize: 1,
54          },
55        },
56        '1.0.1': {
57          name: 'blue',
58          version: '1.0.1',
59          dist: {
60            shasum: '124',
61            tarball: 'http://hm.blue.com/1.0.1.tgz',
62            integrity: '---',
63            fileCount: 1,
64            unpackedSize: 1000,
65          },
66        },
67      },
68    },
69    cyan: {
70      _npmUser: {
71        name: 'claudia',
72        email: 'claudia@cyan.com',
73      },
74      name: 'cyan',
75      'dist-tags': {
76        latest: '1.0.0',
77      },
78      versions: {
79        '1.0.0': {
80          version: '1.0.0',
81          name: 'cyan',
82          dist: {
83            shasum: '123',
84            tarball: 'http://hm.cyan.com/1.0.0.tgz',
85            integrity: '---',
86            fileCount: 1,
87            unpackedSize: 1000000,
88          },
89        },
90        '1.0.1': {},
91      },
92    },
93    brown: {
94      name: 'brown',
95    },
96    yellow: {
97      _id: 'yellow',
98      name: 'yellow',
99      author: {
100        name: 'foo',
101        email: 'foo@yellow.com',
102        twitter: 'foo',
103      },
104      empty: '',
105      readme: 'a very useful readme',
106      versions: {
107        '1.0.0': {
108          version: '1.0.0',
109          author: 'claudia',
110          readme: 'a very useful readme',
111          maintainers: [
112            { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
113            { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
114          ],
115        },
116        '1.0.1': {
117          version: '1.0.1',
118          author: 'claudia',
119        },
120        '1.0.2': {
121          version: '1.0.2',
122          author: 'claudia',
123        },
124      },
125    },
126    purple: {
127      name: 'purple',
128      versions: {
129        '1.0.0': {
130          foo: 1,
131          maintainers: [
132            { name: 'claudia' },
133          ],
134        },
135        '1.0.1': {},
136      },
137    },
138    green: {
139      _id: 'green',
140      name: 'green',
141      'dist-tags': {
142        latest: '1.0.0',
143      },
144      maintainers: [
145        { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
146        { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
147      ],
148      keywords: ['colors', 'green', 'crayola'],
149      versions: {
150        '1.0.0': {
151          _id: 'green',
152          version: '1.0.0',
153          description: 'green is a very important color',
154          bugs: {
155            url: 'http://bugs.green.com',
156          },
157          deprecated: true,
158          repository: {
159            url: 'http://repository.green.com',
160          },
161          license: { type: 'ACME' },
162          bin: {
163            green: 'bin/green.js',
164          },
165          dependencies: {
166            red: '1.0.0',
167            yellow: '1.0.0',
168          },
169          dist: {
170            shasum: '123',
171            tarball: 'http://hm.green.com/1.0.0.tgz',
172            integrity: '---',
173            fileCount: 1,
174            unpackedSize: 1000000000,
175          },
176        },
177        '1.0.1': {},
178      },
179    },
180    black: {
181      name: 'black',
182      'dist-tags': {
183        latest: '1.0.0',
184      },
185      versions: {
186        '1.0.0': {
187          version: '1.0.0',
188          bugs: 'http://bugs.black.com',
189          license: {},
190          dependencies: (() => {
191            const deps = {}
192            for (let i = 0; i < 25; i++) {
193              deps[i] = '1.0.0'
194            }
195
196            return deps
197          })(),
198          dist: {
199            shasum: '123',
200            tarball: 'http://hm.black.com/1.0.0.tgz',
201            integrity: '---',
202            fileCount: 1,
203            unpackedSize: 1,
204          },
205        },
206        '1.0.1': {},
207      },
208    },
209    pink: {
210      name: 'pink',
211      'dist-tags': {
212        latest: '1.0.0',
213      },
214      versions: {
215        '1.0.0': {
216          version: '1.0.0',
217          maintainers: [
218            { name: 'claudia', url: 'http://c.pink.com' },
219            { name: 'isaacs', url: 'http://i.pink.com' },
220          ],
221          repository: 'http://repository.pink.com',
222          license: {},
223          dist: {
224            shasum: '123',
225            tarball: 'http://hm.pink.com/1.0.0.tgz',
226            integrity: '---',
227            fileCount: 1,
228            unpackedSize: 1,
229          },
230        },
231        '1.0.1': {},
232      },
233    },
234    orange: {
235      name: 'orange',
236      'dist-tags': {
237        latest: '1.0.0',
238      },
239      versions: {
240        '1.0.0': {
241          version: '1.0.0',
242          homepage: 'http://hm.orange.com',
243          license: {},
244          dist: {
245            shasum: '123',
246            tarball: 'http://hm.orange.com/1.0.0.tgz',
247            integrity: '---',
248            fileCount: 1,
249            unpackedSize: 1,
250          },
251        },
252        '1.0.1': {},
253        '100000000000000000.0.0': {
254        },
255      },
256    },
257  }
258  if (nv.type === 'git') {
259    return mocks[nv.hosted.project]
260  }
261  if (nv.raw === './blue') {
262    return mocks.blue
263  }
264  return mocks[nv.name]
265}
266
267const loadMockNpm = async function (t, opts = {}) {
268  const mockNpm = await _loadMockNpm(t, {
269    command: 'view',
270    mocks: {
271      pacote: {
272        packument,
273      },
274    },
275    ...opts,
276    config: {
277      color: 'always',
278      ...opts.config,
279    },
280  })
281  return mockNpm
282}
283
284t.test('package from git', async t => {
285  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
286  await view.exec(['https://github.com/npm/green'])
287  t.matchSnapshot(outputs.join('\n'))
288})
289
290t.test('deprecated package with license, bugs, repository and other fields', async t => {
291  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
292  await view.exec(['green@1.0.0'])
293  t.matchSnapshot(outputs.join('\n'))
294})
295
296t.test('deprecated package with unicode', async t => {
297  const { view, outputs } = await loadMockNpm(t, { config: { unicode: true } })
298  await view.exec(['green@1.0.0'])
299  t.matchSnapshot(outputs.join('\n'))
300})
301
302t.test('package with more than 25 deps', async t => {
303  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
304  await view.exec(['black@1.0.0'])
305  t.matchSnapshot(outputs.join('\n'))
306})
307
308t.test('package with maintainers info as object', async t => {
309  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
310  await view.exec(['pink@1.0.0'])
311  t.matchSnapshot(outputs.join('\n'))
312})
313
314t.test('package with homepage', async t => {
315  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
316  await view.exec(['orange@1.0.0'])
317  t.matchSnapshot(outputs.join('\n'))
318})
319
320t.test('package with invalid version', async t => {
321  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
322  await view.exec(['orange', 'versions'])
323  t.matchSnapshot(outputs.join('\n'))
324})
325
326t.test('package with no versions', async t => {
327  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
328  await view.exec(['brown'])
329  t.equal(outputs.join('\n'), '', 'no info to display')
330})
331
332t.test('package with no repo or homepage', async t => {
333  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
334  await view.exec(['blue@1.0.0'])
335  t.matchSnapshot(outputs.join('\n'))
336})
337
338t.test('package with semver range', async t => {
339  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
340  await view.exec(['blue@^1.0.0'])
341  t.matchSnapshot(outputs.join('\n'))
342})
343
344t.test('package with no modified time', async t => {
345  const { view, outputs } = await loadMockNpm(t, { config: { unicode: false } })
346  await view.exec(['cyan@1.0.0'])
347  t.matchSnapshot(outputs.join('\n'))
348})
349
350t.test('package with --json and semver range', async t => {
351  const { view, outputs } = await loadMockNpm(t, { config: { json: true } })
352  await view.exec(['cyan@^1.0.0'])
353  t.matchSnapshot(outputs.join('\n'))
354})
355
356t.test('package with --json and no versions', async t => {
357  const { view, outputs } = await loadMockNpm(t, { config: { json: true } })
358  await view.exec(['brown'])
359  t.equal(outputs.join('\n'), '', 'no info to display')
360})
361
362t.test('package in cwd', async t => {
363  const prefixDir = {
364    'package.json': JSON.stringify({
365      name: 'blue',
366      version: '1.0.0',
367    }, null, 2),
368  }
369
370  t.test('specific version', async t => {
371    const { view, outputs } = await loadMockNpm(t, { prefixDir })
372    await view.exec(['.@1.0.0'])
373    t.matchSnapshot(outputs.join('\n'))
374  })
375
376  t.test('non-specific version', async t => {
377    const { view, outputs } = await loadMockNpm(t, { prefixDir })
378    await view.exec(['.'])
379    t.matchSnapshot(outputs.join('\n'))
380  })
381
382  t.test('directory', async t => {
383    const { view, outputs } = await loadMockNpm(t, { prefixDir })
384    await view.exec(['./blue'])
385    t.matchSnapshot(outputs.join('\n'))
386  })
387})
388
389t.test('specific field names', async t => {
390  const { view, outputs } = await loadMockNpm(t, { config: { color: false } })
391  t.afterEach(() => outputs.length = 0)
392
393  t.test('readme', async t => {
394    await view.exec(['yellow@1.0.0', 'readme'])
395    t.matchSnapshot(outputs.join('\n'))
396  })
397
398  t.test('several fields', async t => {
399    await view.exec(['yellow@1.0.0', 'name', 'version', 'foo[bar]'])
400    t.matchSnapshot(outputs.join('\n'))
401  })
402
403  t.test('several fields with several versions', async t => {
404    await view.exec(['yellow@1.x.x', 'author'])
405    t.matchSnapshot(outputs.join('\n'))
406  })
407
408  t.test('nested field with brackets', async t => {
409    await view.exec(['orange@1.0.0', 'dist[shasum]'])
410    t.matchSnapshot(outputs.join('\n'))
411  })
412
413  t.test('maintainers with email', async t => {
414    await view.exec(['yellow@1.0.0', 'maintainers', 'name'])
415    t.matchSnapshot(outputs.join('\n'))
416  })
417
418  t.test('maintainers with url', async t => {
419    await view.exec(['pink@1.0.0', 'maintainers'])
420    t.matchSnapshot(outputs.join('\n'))
421  })
422
423  t.test('unknown nested field ', async t => {
424    await view.exec(['yellow@1.0.0', 'dist.foobar'])
425    t.equal(outputs.join('\n'), '', 'no info to display')
426  })
427
428  t.test('array field - 1 element', async t => {
429    await view.exec(['purple@1.0.0', 'maintainers.name'])
430    t.matchSnapshot(outputs.join('\n'))
431  })
432
433  t.test('array field - 2 elements', async t => {
434    await view.exec(['yellow@1.x.x', 'maintainers.name'])
435    t.matchSnapshot(outputs.join('\n'))
436  })
437
438  t.test('fields with empty values', async t => {
439    await view.exec(['yellow', 'empty'])
440    t.matchSnapshot(outputs.join('\n'))
441  })
442})
443
444t.test('throw error if global mode', async t => {
445  const { npm } = await loadMockNpm(t, { config: { global: true } })
446  await t.rejects(
447    npm.exec('view', []),
448    /Cannot use view command in global mode./
449  )
450})
451
452t.test('throw ENOENT error if package.json missing', async t => {
453  const { npm } = await loadMockNpm(t)
454  await t.rejects(
455    npm.exec('view', []),
456    { code: 'ENOENT' }
457  )
458})
459
460t.test('throw error if package.json has no name', async t => {
461  const { npm } = await loadMockNpm(t, {
462    prefixDir: {
463      'package.json': '{}',
464    },
465  })
466  await t.rejects(
467    npm.exec('view', []),
468    /Invalid package.json, no "name" field/
469  )
470})
471
472t.test('throws when unpublished', async t => {
473  const { npm } = await loadMockNpm(t)
474  await t.rejects(
475    npm.exec('view', ['red']),
476    { code: 'E404', pkgid: 'red@1.0.1', message: 'Unpublished on 2012-12-20T00:00:00.000Z' }
477  )
478})
479
480t.test('throws when version not matched', async t => {
481  const { npm } = await loadMockNpm(t)
482  await t.rejects(
483    npm.exec('view', ['blue@2.0.0']),
484    { code: 'E404', pkgid: 'blue@2.0.0', message: 'No match found for version 2.0.0' }
485  )
486})
487
488t.test('workspaces', async t => {
489  const prefixDir = {
490    'package.json': JSON.stringify({
491      name: 'workspaces-test-package',
492      version: '1.2.3',
493      workspaces: ['test-workspace-a', 'test-workspace-b'],
494    }),
495    'test-workspace-a': {
496      'package.json': JSON.stringify({
497        name: 'green',
498        version: '1.2.3',
499      }),
500    },
501    'test-workspace-b': {
502      'package.json': JSON.stringify({
503        name: 'orange',
504        version: '1.2.3',
505      }),
506    },
507  }
508
509  t.test('all workspaces', async t => {
510    const { view, outputs } = await loadMockNpm(t, {
511      prefixDir,
512      config: { unicode: false, workspaces: true },
513    })
514    await view.exec([])
515    t.matchSnapshot(outputs.join('\n'))
516  })
517
518  t.test('one specific workspace', async t => {
519    const { view, outputs } = await loadMockNpm(t, {
520      prefixDir,
521      config: { unicode: false, workspace: ['green'] },
522    })
523    await view.exec([])
524    t.matchSnapshot(outputs.join('\n'))
525  })
526
527  t.test('all workspaces --json', async t => {
528    const { view, outputs } = await loadMockNpm(t, {
529      prefixDir,
530      config: { unicode: false, workspaces: true, json: true },
531    })
532    await view.exec([])
533    t.matchSnapshot(outputs.join('\n'))
534  })
535
536  t.test('all workspaces single field', async t => {
537    const { view, outputs } = await loadMockNpm(t, {
538      prefixDir,
539      config: { unicode: false, workspaces: true },
540    })
541    await view.exec(['.', 'name'])
542    t.matchSnapshot(outputs.join('\n'))
543  })
544
545  t.test('all workspaces nonexistent field', async t => {
546    const { view, outputs } = await loadMockNpm(t, {
547      prefixDir,
548      config: { unicode: false, workspaces: true },
549    })
550    await view.exec(['.', 'foo'])
551    t.matchSnapshot(outputs.join('\n'))
552  })
553
554  t.test('all workspaces nonexistent field --json', async t => {
555    const { view, outputs } = await loadMockNpm(t, {
556      prefixDir,
557      config: { unicode: false, workspaces: true, json: true },
558    })
559    await view.exec(['.', 'foo'])
560    t.matchSnapshot(outputs.join('\n'))
561  })
562
563  t.test('all workspaces single field --json', async t => {
564    const { view, outputs } = await loadMockNpm(t, {
565      prefixDir,
566      config: { unicode: false, workspaces: true, json: true },
567    })
568    await view.exec(['.', 'name'])
569    t.matchSnapshot(outputs.join('\n'))
570  })
571
572  t.test('single workspace --json', async t => {
573    const { view, outputs } = await loadMockNpm(t, {
574      prefixDir,
575      config: { unicode: false, workspace: ['green'], json: true },
576    })
577    await view.exec([])
578    t.matchSnapshot(outputs.join('\n'))
579  })
580
581  t.test('remote package name', async t => {
582    const { view, logs, outputs } = await loadMockNpm(t, {
583      prefixDir,
584      config: { unicode: false, workspaces: true },
585    })
586    await view.exec(['pink'])
587    t.matchSnapshot(outputs.join('\n'))
588    t.matchSnapshot(logs.warn, 'should have warning of ignoring workspaces')
589  })
590})
591
592t.test('completion', async t => {
593  const { view } = await loadMockNpm(t, { command: 'view' })
594  const res = await view.completion({
595    conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } },
596  })
597  t.ok(res, 'returns back fields')
598})
599
600t.test('no package completion', async t => {
601  const { view } = await loadMockNpm(t, { command: 'view' })
602  const res = await view.completion({ conf: { argv: { remain: ['npm', 'view'] } } })
603  t.notOk(res, 'there is no package completion')
604  t.end()
605})
606