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