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