• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const { join, extname } = require('path')
3const MockRegistry = require('@npmcli/mock-registry')
4const { load: loadMockNpm } = require('../../fixtures/mock-npm')
5
6const jsonifyTestdir = (obj) => {
7  for (const [key, value] of Object.entries(obj || {})) {
8    if (extname(key) === '.json') {
9      obj[key] = JSON.stringify(value, null, 2) + '\n'
10    } else if (typeof value === 'object') {
11      obj[key] = jsonifyTestdir(value)
12    } else {
13      obj[key] = value.trim() + '\n'
14    }
15  }
16  return obj
17}
18
19// generic helper to call diff with a specified dir contents and registry calls
20const mockDiff = async (t, {
21  exec,
22  diff = [],
23  tarballs = {},
24  times = {},
25  ...opts
26} = {}) => {
27  const tarballFixtures = Object.entries(tarballs).reduce((acc, [spec, fixture]) => {
28    const lastAt = spec.lastIndexOf('@')
29    const name = spec.slice(0, lastAt)
30    const version = spec.slice(lastAt + 1)
31    acc[name] = acc[name] || {}
32    acc[name][version] = fixture
33    if (!acc[name][version]['package.json']) {
34      acc[name][version]['package.json'] = { name, version }
35    } else {
36      acc[name][version]['package.json'].name = name
37      acc[name][version]['package.json'].version = version
38    }
39    return acc
40  }, {})
41
42  const { prefixDir, globalPrefixDir, otherDirs, config, ...rest } = opts
43  const { npm, ...res } = await loadMockNpm(t, {
44    command: 'diff',
45    prefixDir: jsonifyTestdir(prefixDir),
46    otherDirs: jsonifyTestdir({ tarballs: tarballFixtures, ...otherDirs }),
47    globalPrefixDir: jsonifyTestdir(globalPrefixDir),
48    config: {
49      ...config,
50      diff: [].concat(diff),
51    },
52    ...rest,
53  })
54
55  const registry = new MockRegistry({
56    tap: t,
57    registry: npm.config.get('registry'),
58    strict: true,
59    debug: true,
60  })
61
62  const manifests = Object.entries(tarballFixtures).reduce((acc, [name, versions]) => {
63    acc[name] = registry.manifest({
64      name,
65      packuments: Object.keys(versions).map((version) => ({ version })),
66    })
67    return acc
68  }, {})
69
70  for (const [name, manifest] of Object.entries(manifests)) {
71    await registry.package({ manifest, times: times[name] ?? 1 })
72    for (const [version, tarballManifest] of Object.entries(manifest.versions)) {
73      await registry.tarball({
74        manifest: tarballManifest,
75        tarball: join(res.other, 'tarballs', name, version),
76      })
77    }
78  }
79
80  if (exec) {
81    await res.diff.exec(exec)
82    res.output = res.joinedOutput()
83  }
84
85  return { npm, registry, ...res }
86}
87
88// a more specific helper to call diff against a local package and a registry package
89// and assert the diff output contains the matching strings
90const assertFoo = async (t, arg) => {
91  let diff = []
92  let exec = []
93
94  if (typeof arg === 'string' || Array.isArray(arg)) {
95    diff = arg
96  } else if (arg && typeof arg === 'object') {
97    diff = arg.diff
98    exec = arg.exec
99  }
100
101  const { output } = await mockDiff(t, {
102    diff,
103    prefixDir: {
104      'package.json': { name: '@npmcli/foo', version: '1.0.0' },
105      'index.js': 'const version = "1.0.0"',
106      'a.js': 'const a = "a@1.0.0"',
107      'b.js': 'const b = "b@1.0.0"',
108    },
109    tarballs: {
110      '@npmcli/foo@0.1.0': {
111        'index.js': 'const version = "0.1.0"',
112        'a.js': 'const a = "a@0.1.0"',
113        'b.js': 'const b = "b@0.1.0"',
114      },
115    },
116    exec,
117  })
118
119  const hasFile = (f) => !exec.length || exec.some(e => e.endsWith(f))
120
121  if (hasFile('package.json')) {
122    t.match(output, /-\s*"version": "0\.1\.0"/)
123    t.match(output, /\+\s*"version": "1\.0\.0"/)
124  }
125
126  if (hasFile('index.js')) {
127    t.match(output, /-\s*const version = "0\.1\.0"/)
128    t.match(output, /\+\s*const version = "1\.0\.0"/)
129  }
130
131  if (hasFile('a.js')) {
132    t.match(output, /-\s*const a = "a@0\.1\.0"/)
133    t.match(output, /\+\s*const a = "a@1\.0\.0"/)
134  }
135
136  if (hasFile('b.js')) {
137    t.match(output, /-\s*const b = "b@0\.1\.0"/)
138    t.match(output, /\+\s*const b = "b@1\.0\.0"/)
139  }
140
141  return output
142}
143
144const rejectDiff = async (t, msg, opts) => {
145  const { npm } = await mockDiff(t, opts)
146  await t.rejects(npm.exec('diff', []), msg)
147}
148
149t.test('no args', async t => {
150  t.test('in a project dir', async t => {
151    const output = await assertFoo(t)
152    t.matchSnapshot(output)
153  })
154
155  t.test('no args, missing package.json name in cwd', async t => {
156    await rejectDiff(t, /Needs multiple arguments to compare or run from a project dir./)
157  })
158
159  t.test('no args, bad package.json in cwd', async t => {
160    await rejectDiff(t, /Needs multiple arguments to compare or run from a project dir./, {
161      prefixDir: { 'package.json': '{invalid"json' },
162    })
163  })
164})
165
166t.test('single arg', async t => {
167  t.test('spec using cwd package name', async t => {
168    await assertFoo(t, '@npmcli/foo@0.1.0')
169  })
170
171  t.test('unknown spec, no package.json', async t => {
172    await rejectDiff(t, /Needs multiple arguments to compare or run from a project dir./, {
173      diff: ['@npmcli/foo@1.0.0'],
174    })
175  })
176
177  t.test('spec using semver range', async t => {
178    await assertFoo(t, '@npmcli/foo@~0.1.0')
179  })
180
181  t.test('version', async t => {
182    await assertFoo(t, '0.1.0')
183  })
184
185  t.test('version, no package.json', async t => {
186    await rejectDiff(t, /Needs multiple arguments to compare or run from a project dir./, {
187      diff: ['0.1.0'],
188    })
189  })
190
191  t.test('version, filtering by files', async t => {
192    const output = await assertFoo(t, { diff: '0.1.0', exec: ['./a.js', './b.js'] })
193    t.matchSnapshot(output)
194  })
195
196  t.test('spec is not a dep', async t => {
197    const { output } = await mockDiff(t, {
198      diff: 'bar@1.0.0',
199      prefixDir: {
200        node_modules: {},
201        'package.json': { name: 'my-project', version: '1.0.0' },
202      },
203      tarballs: {
204        'bar@1.0.0': {},
205      },
206      exec: [],
207    })
208
209    t.match(output, /-\s*"name": "bar"/)
210    t.match(output, /\+\s*"name": "my-project"/)
211  })
212
213  t.test('unknown package name', async t => {
214    const { npm, registry } = await mockDiff(t, {
215      diff: 'bar',
216      prefixDir: {
217        'package.json': {
218          name: 'my-project',
219          dependencies: {
220            bar: '^1.0.0',
221          },
222        },
223      },
224    })
225    registry.getPackage('bar', { times: 2, code: 404 })
226    t.rejects(npm.exec('diff', []), /404 Not Found.*bar/)
227  })
228
229  t.test('unknown package name, no package.json', async t => {
230    const { npm } = await mockDiff(t, {
231      diff: 'bar',
232    })
233    t.rejects(npm.exec('diff', []),
234      /Needs multiple arguments to compare or run from a project dir./)
235  })
236
237  t.test('transform single direct dep name into spec comparison', async t => {
238    const { output } = await mockDiff(t, {
239      diff: 'bar',
240      prefixDir: {
241        node_modules: {
242          bar: {
243            'package.json': {
244              name: 'bar',
245              version: '1.0.0',
246            },
247          },
248        },
249        'package.json': {
250          name: 'my-project',
251          dependencies: {
252            bar: '^1.0.0',
253          },
254        },
255      },
256      tarballs: {
257        'bar@1.8.0': {},
258      },
259      times: { bar: 2 },
260      exec: [],
261    })
262
263    t.match(output, /-\s*"version": "1\.0\.0"/)
264    t.match(output, /\+\s*"version": "1\.8\.0"/)
265  })
266
267  t.test('global space, transform single direct dep name', async t => {
268    const { output } = await mockDiff(t, {
269      diff: 'lorem',
270      config: {
271        global: true,
272      },
273      globalPrefixDir: {
274        node_modules: {
275          lorem: {
276            'package.json': {
277              name: 'lorem',
278              version: '2.0.0',
279            },
280          },
281        },
282      },
283      prefixDir: {
284        node_modules: {
285          lorem: {
286            'package.json': {
287              name: 'lorem',
288              version: '3.0.0',
289            },
290          },
291        },
292        'package.json': {
293          name: 'my-project',
294          dependencies: {
295            lorem: '^3.0.0',
296          },
297        },
298      },
299      tarballs: {
300        'lorem@1.0.0': {},
301      },
302      times: {
303        lorem: 2,
304      },
305      exec: [],
306    })
307
308    t.match(output, 'lorem')
309    t.match(output, /-\s*"version": "2\.0\.0"/)
310    t.match(output, /\+\s*"version": "1\.0\.0"/)
311  })
312
313  t.test('transform single spec into spec comparison', async t => {
314    const { output } = await mockDiff(t, {
315      diff: 'bar@2.0.0',
316      prefixDir: {
317        node_modules: {
318          bar: {
319            'package.json': {
320              name: 'bar',
321              version: '1.0.0',
322            },
323          },
324        },
325        'package.json': {
326          name: 'my-project',
327          dependencies: {
328            bar: '^1.0.0',
329          },
330        },
331      },
332      tarballs: {
333        'bar@2.0.0': {},
334      },
335      times: {
336        lorem: 2,
337      },
338      exec: [],
339    })
340
341    t.match(output, 'bar')
342    t.match(output, /-\s*"version": "1\.0\.0"/)
343    t.match(output, /\+\s*"version": "2\.0\.0"/)
344  })
345
346  t.test('transform single spec from transitive deps', async t => {
347    const { output } = await mockDiff(t, {
348      diff: 'lorem',
349      prefixDir: {
350        node_modules: {
351          bar: {
352            'package.json': {
353              name: 'bar',
354              version: '1.0.0',
355              dependencies: {
356                lorem: '^2.0.0',
357              },
358            },
359          },
360          lorem: {
361            'package.json': {
362              name: 'lorem',
363              version: '2.0.0',
364            },
365          },
366        },
367        'package.json': {
368          name: 'my-project',
369          dependencies: {
370            bar: '^1.0.0',
371          },
372        },
373      },
374      tarballs: {
375        'lorem@2.2.2': {},
376      },
377      times: {
378        lorem: 2,
379      },
380      exec: [],
381    })
382
383    t.match(output, 'lorem')
384    t.match(output, /-\s*"version": "2\.0\.0"/)
385    t.match(output, /\+\s*"version": "2\.2\.2"/)
386  })
387
388  t.test('missing actual tree', async t => {
389    const { output } = await mockDiff(t, {
390      diff: 'lorem',
391      prefixDir: {
392        'package.json': {
393          name: 'lorem',
394          version: '2.0.0',
395        },
396      },
397      mocks: {
398        '@npmcli/arborist': class {
399          constructor () {
400            throw new Error('ERR')
401          }
402        },
403      },
404      tarballs: {
405        'lorem@2.2.2': {},
406      },
407      exec: [],
408    })
409
410    t.match(output, 'lorem')
411    t.match(output, /-\s*"version": "2\.2\.2"/)
412    t.match(output, /\+\s*"version": "2\.0\.0"/)
413  })
414
415  t.test('unknown package name', async t => {
416    const { output } = await mockDiff(t, {
417      diff: 'bar',
418      prefixDir: {
419        'package.json': { version: '1.0.0' },
420      },
421
422      tarballs: {
423        'bar@2.2.2': {},
424      },
425      exec: [],
426    })
427
428    t.match(output, 'bar')
429    t.match(output, /-\s*"version": "2\.2\.2"/)
430    t.match(output, /\+\s*"version": "1\.0\.0"/)
431  })
432
433  t.test('use project name in project dir', async t => {
434    const { output } = await mockDiff(t, {
435      diff: '@npmcli/foo',
436      prefixDir: {
437        'package.json': { name: '@npmcli/foo', version: '1.0.0' },
438      },
439      tarballs: {
440        '@npmcli/foo@2.2.2': {},
441      },
442      exec: [],
443    })
444
445    t.match(output, '@npmcli/foo')
446    t.match(output, /-\s*"version": "2\.2\.2"/)
447    t.match(output, /\+\s*"version": "1\.0\.0"/)
448  })
449
450  t.test('dir spec type', async t => {
451    const { output } = await mockDiff(t, {
452      diff: '../other/other-pkg',
453      prefixDir: {
454        'package.json': { name: '@npmcli/foo', version: '1.0.0' },
455      },
456      otherDirs: {
457        'other-pkg': {
458          'package.json': { name: '@npmcli/foo', version: '2.0.0' },
459        },
460      },
461      exec: [],
462    })
463
464    t.match(output, '@npmcli/foo')
465    t.match(output, /-\s*"version": "2\.0\.0"/)
466    t.match(output, /\+\s*"version": "1\.0\.0"/)
467  })
468
469  t.test('unsupported spec type', async t => {
470    const p = mockDiff(t, {
471      diff: 'git+https://github.com/user/foo',
472      exec: [],
473    })
474
475    await t.rejects(
476      p,
477      /Spec type git not supported./,
478      'should throw spec type not supported error.'
479    )
480  })
481})
482
483t.test('first arg is a qualified spec', async t => {
484  t.test('second arg is ALSO a qualified spec', async t => {
485    const { output } = await mockDiff(t, {
486      diff: ['bar@1.0.0', 'bar@^2.0.0'],
487      tarballs: {
488        'bar@1.0.0': {},
489        'bar@2.2.2': {},
490      },
491      times: {
492        bar: 2,
493      },
494      exec: [],
495    })
496
497    t.match(output, 'bar')
498    t.match(output, /-\s*"version": "1\.0\.0"/)
499    t.match(output, /\+\s*"version": "2\.2\.2"/)
500  })
501
502  t.test('second arg is a known dependency name', async t => {
503    const { output } = await mockDiff(t, {
504      prefixDir: {
505        node_modules: {
506          bar: {
507            'package.json': {
508              name: 'bar',
509              version: '1.0.0',
510            },
511          },
512        },
513        'package.json': {
514          name: 'my-project',
515          dependencies: {
516            bar: '^1.0.0',
517          },
518        },
519      },
520      tarballs: {
521        'bar@2.0.0': {},
522      },
523      diff: ['bar@2.0.0', 'bar'],
524      exec: [],
525    })
526
527    t.match(output, 'bar')
528    t.match(output, /-\s*"version": "2\.0\.0"/)
529    t.match(output, /\+\s*"version": "1\.0\.0"/)
530  })
531
532  t.test('second arg is a valid semver version', async t => {
533    const { output } = await mockDiff(t, {
534      tarballs: {
535        'bar@1.0.0': {},
536        'bar@2.0.0': {},
537      },
538      times: {
539        bar: 2,
540      },
541      diff: ['bar@1.0.0', '2.0.0'],
542      exec: [],
543    })
544
545    t.match(output, 'bar')
546    t.match(output, /-\s*"version": "1\.0\.0"/)
547    t.match(output, /\+\s*"version": "2\.0\.0"/)
548  })
549
550  t.test('second arg is an unknown dependency name', async t => {
551    const { output } = await mockDiff(t, {
552      tarballs: {
553        'bar@1.0.0': {},
554        'bar-fork@2.0.0': {},
555      },
556      diff: ['bar@1.0.0', 'bar-fork'],
557      exec: [],
558    })
559
560    t.match(output, /-\s*"name": "bar"/)
561    t.match(output, /\+\s*"name": "bar-fork"/)
562
563    t.match(output, /-\s*"version": "1\.0\.0"/)
564    t.match(output, /\+\s*"version": "2\.0\.0"/)
565  })
566})
567
568t.test('first arg is a known dependency name', async t => {
569  t.test('second arg is a qualified spec', async t => {
570    const { output } = await mockDiff(t, {
571      prefixDir: {
572        node_modules: {
573          bar: {
574            'package.json': {
575              name: 'bar',
576              version: '1.0.0',
577            },
578          },
579        },
580        'package.json': {
581          name: 'my-project',
582          dependencies: {
583            bar: '^1.0.0',
584          },
585        },
586      },
587      tarballs: {
588        'bar@2.0.0': {},
589      },
590      diff: ['bar', 'bar@2.0.0'],
591      exec: [],
592    })
593
594    t.match(output, 'bar')
595    t.match(output, /-\s*"version": "1\.0\.0"/)
596    t.match(output, /\+\s*"version": "2\.0\.0"/)
597  })
598
599  t.test('second arg is ALSO a known dependency', async t => {
600    const { output } = await mockDiff(t, {
601      prefixDir: {
602        node_modules: {
603          bar: {
604            'package.json': {
605              name: 'bar',
606              version: '1.0.0',
607            },
608          },
609          'bar-fork': {
610            'package.json': {
611              name: 'bar-fork',
612              version: '1.0.0',
613            },
614          },
615        },
616        'package.json': {
617          name: 'my-project',
618          dependencies: {
619            bar: '^1.0.0',
620          },
621        },
622      },
623      diff: ['bar', 'bar-fork'],
624      exec: [],
625    })
626
627    t.match(output, /-\s*"name": "bar"/)
628    t.match(output, /\+\s*"name": "bar-fork"/)
629  })
630
631  t.test('second arg is a valid semver version', async t => {
632    const { output } = await mockDiff(t, {
633      prefixDir: {
634        node_modules: {
635          bar: {
636            'package.json': {
637              name: 'bar',
638              version: '1.0.0',
639            },
640          },
641        },
642        'package.json': {
643          name: 'my-project',
644          dependencies: {
645            bar: '^1.0.0',
646          },
647        },
648      },
649      tarballs: {
650        'bar@2.0.0': {},
651      },
652      diff: ['bar', '2.0.0'],
653      exec: [],
654    })
655
656    t.match(output, 'bar')
657    t.match(output, /-\s*"version": "1\.0\.0"/)
658    t.match(output, /\+\s*"version": "2\.0\.0"/)
659  })
660
661  t.test('second arg is an unknown dependency name', async t => {
662    const { output } = await mockDiff(t, {
663      prefixDir: {
664        node_modules: {
665          bar: {
666            'package.json': {
667              name: 'bar',
668              version: '1.0.0',
669            },
670          },
671        },
672        'package.json': {
673          name: 'my-project',
674          dependencies: {
675            bar: '^1.0.0',
676          },
677        },
678      },
679      tarballs: {
680        'bar-fork@1.0.0': {},
681      },
682      diff: ['bar', 'bar-fork'],
683      exec: [],
684    })
685
686    t.match(output, /-\s*"name": "bar"/)
687    t.match(output, /\+\s*"name": "bar-fork"/)
688  })
689})
690
691t.test('first arg is a valid semver range', async t => {
692  t.test('second arg is a qualified spec', async t => {
693    const { output } = await mockDiff(t, {
694      tarballs: {
695        'bar@1.0.0': {},
696        'bar@2.0.0': {},
697      },
698      diff: ['1.0.0', 'bar@2.0.0'],
699      times: { bar: 2 },
700      exec: [],
701    })
702
703    t.match(output, 'bar')
704    t.match(output, /-\s*"version": "1\.0\.0"/)
705    t.match(output, /\+\s*"version": "2\.0\.0"/)
706  })
707
708  t.test('second arg is a known dependency', async t => {
709    const { output } = await mockDiff(t, {
710      prefixDir: {
711        node_modules: {
712          bar: {
713            'package.json': {
714              name: 'bar',
715              version: '2.0.0',
716            },
717          },
718        },
719        'package.json': {
720          name: 'my-project',
721          dependencies: {
722            bar: '^1.0.0',
723          },
724        },
725      },
726      tarballs: {
727        'bar@1.0.0': {},
728      },
729      diff: ['1.0.0', 'bar'],
730      exec: [],
731    })
732
733    t.match(output, 'bar')
734    t.match(output, /-\s*"version": "1\.0\.0"/)
735    t.match(output, /\+\s*"version": "2\.0\.0"/)
736  })
737
738  t.test('second arg is ALSO a semver version', async t => {
739    const { output } = await mockDiff(t, {
740      prefixDir: {
741        'package.json': {
742          name: 'bar',
743        },
744      },
745      tarballs: {
746        'bar@1.0.0': {},
747        'bar@2.0.0': {},
748      },
749      diff: ['1.0.0', '2.0.0'],
750      times: { bar: 2 },
751      exec: [],
752    })
753
754    t.match(output, 'bar')
755    t.match(output, /-\s*"version": "1\.0\.0"/)
756    t.match(output, /\+\s*"version": "2\.0\.0"/)
757  })
758
759  t.test('second arg is ALSO a semver version BUT cwd not a project dir', async t => {
760    const p = mockDiff(t, {
761      diff: ['1.0.0', '2.0.0'],
762      exec: [],
763    })
764    await t.rejects(
765      p,
766      /Needs to be run from a project dir in order to diff two versions./,
767      'should throw two versions need project dir error usage msg'
768    )
769  })
770
771  t.test('second arg is an unknown dependency name', async t => {
772    const { output } = await mockDiff(t, {
773      prefixDir: {
774        prefixDir: {
775          'package.json': {
776            name: 'bar',
777          },
778        },
779      },
780      tarballs: {
781        'bar@1.0.0': {},
782        'bar@2.0.0': {},
783      },
784      diff: ['1.0.0', 'bar'],
785      times: { bar: 2 },
786      exec: [],
787    })
788
789    t.match(output, 'bar')
790    t.match(output, /-\s*"version": "1\.0\.0"/)
791    t.match(output, /\+\s*"version": "2\.0\.0"/)
792  })
793
794  t.test('second arg is a qualified spec, missing actual tree', async t => {
795    const { output } = await mockDiff(t, {
796      prefixDir: {
797        'package.json': {
798          name: 'lorem',
799          version: '2.0.0',
800        },
801      },
802      mocks: {
803        '@npmcli/arborist': class {
804          constructor () {
805            throw new Error('ERR')
806          }
807        },
808      },
809      tarballs: {
810        'lorem@1.0.0': {},
811        'lorem@2.0.0': {},
812      },
813      times: { lorem: 2 },
814      diff: ['1.0.0', 'lorem@2.0.0'],
815      exec: [],
816    })
817
818    t.match(output, 'lorem')
819    t.match(output, /-\s*"version": "1\.0\.0"/)
820    t.match(output, /\+\s*"version": "2\.0\.0"/)
821  })
822})
823
824t.test('first arg is an unknown dependency name', async t => {
825  t.test('second arg is a qualified spec', async t => {
826    const { output } = await mockDiff(t, {
827      tarballs: {
828        'bar@2.0.0': {},
829        'bar@3.0.0': {},
830      },
831      times: { bar: 2 },
832      diff: ['bar', 'bar@2.0.0'],
833      exec: [],
834    })
835
836    t.match(output, 'bar')
837    t.match(output, /-\s*"version": "3\.0\.0"/)
838    t.match(output, /\+\s*"version": "2\.0\.0"/)
839  })
840
841  t.test('second arg is a known dependency', async t => {
842    const { output } = await mockDiff(t, {
843      prefixDir: {
844        node_modules: {
845          bar: {
846            'package.json': {
847              name: 'bar',
848              version: '2.0.0',
849            },
850          },
851        },
852        'package.json': {
853          name: 'my-project',
854          dependencies: {
855            bar: '^1.0.0',
856          },
857        },
858      },
859      tarballs: {
860        'bar-fork@2.0.0': {},
861      },
862      diff: ['bar-fork', 'bar'],
863      exec: [],
864    })
865
866    t.match(output, /-\s*"name": "bar-fork"/)
867    t.match(output, /\+\s*"name": "bar"/)
868  })
869
870  t.test('second arg is a valid semver version', async t => {
871    const { output } = await mockDiff(t, {
872      tarballs: {
873        'bar@1.5.0': {},
874        'bar@2.0.0': {},
875      },
876      times: { bar: 2 },
877      diff: ['bar', '^1.0.0'],
878      exec: [],
879    })
880
881    t.match(output, 'bar')
882    t.match(output, /-\s*"version": "2\.0\.0"/)
883    t.match(output, /\+\s*"version": "1\.5\.0"/)
884  })
885
886  t.test('second arg is ALSO an unknown dependency name', async t => {
887    const { output } = await mockDiff(t, {
888      prefixDir: {
889        'package.json': {
890          name: 'my-project',
891        },
892      },
893      tarballs: {
894        'bar@1.0.0': {},
895        'bar-fork@1.0.0': {},
896      },
897      diff: ['bar', 'bar-fork'],
898      exec: [],
899    })
900
901    t.match(output, /-\s*"name": "bar"/)
902    t.match(output, /\+\s*"name": "bar-fork"/)
903  })
904
905  t.test('cwd not a project dir', async t => {
906    const { output } = await mockDiff(t, {
907      tarballs: {
908        'bar@1.0.0': {},
909        'bar-fork@1.0.0': {},
910      },
911      diff: ['bar', 'bar-fork'],
912      exec: [],
913    })
914
915    t.match(output, /-\s*"name": "bar"/)
916    t.match(output, /\+\s*"name": "bar-fork"/)
917  })
918})
919
920t.test('various options', async t => {
921  const mockOptions = async (t, config) => {
922    const file = (v) => new Array(50).fill(0).map((_, i) => `${i}${i === 20 ? v : ''}`).join('\n')
923    const mock = await mockDiff(t, {
924      diff: ['bar@2.0.0', 'bar@3.0.0'],
925      config,
926      exec: [],
927      tarballs: {
928        'bar@2.0.0': { 'index.js': file('2.0.0') },
929        'bar@3.0.0': { 'index.js': file('3.0.0') },
930      },
931      times: { bar: 2 },
932    })
933
934    return mock
935  }
936
937  t.test('using --name-only option', async t => {
938    const { output } = await mockOptions(t, {
939      'diff-name-only': true,
940    })
941    t.matchSnapshot(output)
942  })
943
944  t.test('using diff option', async t => {
945    const { output } = await mockOptions(t, {
946      'diff-context': 5,
947      'diff-ignore-whitespace': true,
948      'diff-no-prefix': false,
949      'diff-drc-prefix': 'foo/',
950      'diff-fst-prefix': 'bar/',
951      'diff-text': true,
952
953    })
954    t.matchSnapshot(output)
955  })
956})
957
958t.test('too many args', async t => {
959  const { npm } = await mockDiff(t, {
960    diff: ['a', 'b', 'c'],
961  })
962
963  await t.rejects(
964    npm.exec('diff', []),
965    /Can't use more than two --diff arguments./,
966    'should throw usage error'
967  )
968})
969
970t.test('workspaces', async t => {
971  const mockWorkspaces = (t, workspaces = true, opts) => mockDiff(t, {
972    prefixDir: {
973      'package.json': {
974        name: 'workspaces-test',
975        version: '1.2.3',
976        workspaces: ['workspace-a', 'workspace-b', 'workspace-c'],
977      },
978      'workspace-a': {
979        'package.json': {
980          name: 'workspace-a',
981          version: '1.2.3-a',
982        },
983      },
984      'workspace-b': {
985        'package.json': {
986          name: 'workspace-b',
987          version: '1.2.3-b',
988        },
989      },
990      'workspace-c': {
991        'package.json': {
992          name: 'workspace-c',
993          version: '1.2.3-c',
994        },
995      },
996    },
997    exec: [],
998    config: workspaces === true ? { workspaces } : { workspace: workspaces },
999    ...opts,
1000  })
1001
1002  t.test('all workspaces', async t => {
1003    const { output } = await mockWorkspaces(t, true, {
1004      tarballs: {
1005        'workspace-a@2.0.0-a': {},
1006        'workspace-b@2.0.0-b': {},
1007        'workspace-c@2.0.0-c': {},
1008      },
1009    })
1010
1011    t.match(output, '"name": "workspace-a"')
1012    t.match(output, /-\s*"version": "2\.0\.0-a"/)
1013    t.match(output, /\+\s*"version": "1\.2\.3-a"/)
1014
1015    t.match(output, '"name": "workspace-b"')
1016    t.match(output, /-\s*"version": "2\.0\.0-b"/)
1017    t.match(output, /\+\s*"version": "1\.2\.3-b"/)
1018
1019    t.match(output, '"name": "workspace-c"')
1020    t.match(output, /-\s*"version": "2\.0\.0-c"/)
1021    t.match(output, /\+\s*"version": "1\.2\.3-c"/)
1022  })
1023
1024  t.test('one workspace', async t => {
1025    const { output } = await mockWorkspaces(t, 'workspace-a', {
1026      tarballs: {
1027        'workspace-a@2.0.0-a': {},
1028      },
1029    })
1030
1031    t.match(output, '"name": "workspace-a"')
1032    t.match(output, /-\s*"version": "2\.0\.0-a"/)
1033    t.match(output, /\+\s*"version": "1\.2\.3-a"/)
1034
1035    t.notMatch(output, '"name": "workspace-b"')
1036    t.notMatch(output, '"name": "workspace-c"')
1037  })
1038
1039  t.test('invalid workspace', async t => {
1040    const p = mockWorkspaces(t, 'workspace-x')
1041    await t.rejects(p, /No workspaces found/)
1042    await t.rejects(p, /workspace-x/)
1043  })
1044})
1045