Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGar <gar+gh@danger.computer>2021-10-08 04:40:03 +0300
committerGar <gar+gh@danger.computer>2021-11-04 00:04:22 +0300
commit8ffeb71dfb248b4a76744bd06cd4d6100f17c8ae (patch)
treeb44d12a79dff3afe0c92df6f6c0219f6d91ad471 /test/lib/commands/view.js
parent85d59191cf681eabd8827ca58f925c1063776f61 (diff)
chore: refactor commands
This is the first phase of refactoring the internal structure of the npm commands to set us up for future changes. This iteration changes the function signature of `exec` for all the commands to be a async (no more callbacks), and also groups all the commands into their own subdirectory. It also removes the Proxy `npm.commands` object, in favor of an `npm.cmd` and `npm.exec` function that breaks up the two things that proxy was doing. Namely, getting to the attributes of a given command (`npm.cmd` now does this), and actually running the command `npm.exec` does this. PR-URL: https://github.com/npm/cli/pull/3959 Credit: @wraithgar Close: #3959 Reviewed-by: @lukekarrys
Diffstat (limited to 'test/lib/commands/view.js')
-rw-r--r--test/lib/commands/view.js660
1 files changed, 660 insertions, 0 deletions
diff --git a/test/lib/commands/view.js b/test/lib/commands/view.js
new file mode 100644
index 000000000..116930aff
--- /dev/null
+++ b/test/lib/commands/view.js
@@ -0,0 +1,660 @@
+const t = require('tap')
+
+t.cleanSnapshot = str => str.replace(/published .*? ago/g, 'published {TIME} ago')
+
+// run the same as tap does when running directly with node
+process.stdout.columns = undefined
+
+const { fake: mockNpm } = require('../../fixtures/mock-npm')
+
+let logs
+const cleanLogs = () => {
+ logs = ''
+ const fn = (...args) => {
+ logs += '\n'
+ args.map(el => logs += el)
+ }
+ console.log = fn
+}
+
+// 25 hours ago
+const yesterday = new Date(Date.now() - 1000 * 60 * 60 * 25)
+
+const packument = (nv, opts) => {
+ if (!opts.fullMetadata)
+ throw new Error('must fetch fullMetadata')
+
+ if (!opts.preferOnline)
+ throw new Error('must fetch with preferOnline')
+
+ const mocks = {
+ red: {
+ name: 'red',
+ 'dist-tags': {
+ '1.0.1': {},
+ },
+ time: {
+ unpublished: new Date(),
+ },
+ },
+ blue: {
+ name: 'blue',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ time: {
+ '1.0.0': yesterday,
+ },
+ versions: {
+ '1.0.0': {
+ name: 'blue',
+ version: '1.0.0',
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.blue.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {
+ name: 'blue',
+ version: '1.0.1',
+ dist: {
+ shasum: '124',
+ tarball: 'http://hm.blue.com/1.0.1.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ },
+ },
+ cyan: {
+ _npmUser: {
+ name: 'claudia',
+ email: 'claudia@cyan.com',
+ },
+ name: 'cyan',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ versions: {
+ '1.0.0': {
+ version: '1.0.0',
+ name: 'cyan',
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.cyan.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {},
+ },
+ },
+ brown: {
+ name: 'brown',
+ },
+ yellow: {
+ _id: 'yellow',
+ name: 'yellow',
+ author: {
+ name: 'foo',
+ email: 'foo@yellow.com',
+ twitter: 'foo',
+ },
+ readme: 'a very useful readme',
+ versions: {
+ '1.0.0': {
+ version: '1.0.0',
+ author: 'claudia',
+ readme: 'a very useful readme',
+ maintainers: [
+ { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
+ { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
+ ],
+ },
+ '1.0.1': {
+ version: '1.0.1',
+ author: 'claudia',
+ },
+ '1.0.2': {
+ version: '1.0.2',
+ author: 'claudia',
+ },
+ },
+ },
+ purple: {
+ name: 'purple',
+ versions: {
+ '1.0.0': {
+ foo: 1,
+ maintainers: [
+ { name: 'claudia' },
+ ],
+ },
+ '1.0.1': {},
+ },
+ },
+ green: {
+ _id: 'green',
+ name: 'green',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ maintainers: [
+ { name: 'claudia', email: 'c@yellow.com', twitter: 'cyellow' },
+ { name: 'isaacs', email: 'i@yellow.com', twitter: 'iyellow' },
+ ],
+ keywords: ['colors', 'green', 'crayola'],
+ versions: {
+ '1.0.0': {
+ _id: 'green',
+ version: '1.0.0',
+ description: 'green is a very important color',
+ bugs: {
+ url: 'http://bugs.green.com',
+ },
+ deprecated: true,
+ repository: {
+ url: 'http://repository.green.com',
+ },
+ license: { type: 'ACME' },
+ bin: {
+ green: 'bin/green.js',
+ },
+ dependencies: {
+ red: '1.0.0',
+ yellow: '1.0.0',
+ },
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.green.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {},
+ },
+ },
+ black: {
+ name: 'black',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ versions: {
+ '1.0.0': {
+ version: '1.0.0',
+ bugs: 'http://bugs.black.com',
+ license: {},
+ dependencies: (() => {
+ const deps = {}
+ for (let i = 0; i < 25; i++)
+ deps[i] = '1.0.0'
+
+ return deps
+ })(),
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.black.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {},
+ },
+ },
+ pink: {
+ name: 'pink',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ versions: {
+ '1.0.0': {
+ version: '1.0.0',
+ maintainers: [
+ { name: 'claudia', url: 'http://c.pink.com' },
+ { name: 'isaacs', url: 'http://i.pink.com' },
+ ],
+ repository: 'http://repository.pink.com',
+ license: {},
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.pink.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {},
+ },
+ },
+ orange: {
+ name: 'orange',
+ 'dist-tags': {
+ latest: '1.0.0',
+ },
+ versions: {
+ '1.0.0': {
+ version: '1.0.0',
+ homepage: 'http://hm.orange.com',
+ license: {},
+ dist: {
+ shasum: '123',
+ tarball: 'http://hm.orange.com/1.0.0.tgz',
+ integrity: '---',
+ fileCount: 1,
+ unpackedSize: 1,
+ },
+ },
+ '1.0.1': {},
+ },
+ },
+ }
+ if (nv.type === 'git')
+ return mocks[nv.hosted.project]
+ return mocks[nv.name]
+}
+
+t.beforeEach(cleanLogs)
+
+t.test('should log package info', async t => {
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const npm = mockNpm({
+ config: { unicode: false },
+ })
+ const view = new View(npm)
+
+ const ViewJson = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const jsonNpm = mockNpm({
+ config: {
+ json: true,
+ tag: 'latest',
+ },
+ })
+ const viewJson = new ViewJson(jsonNpm)
+
+ const ViewUnicode = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const unicodeNpm = mockNpm({
+ config: { unicode: true },
+ })
+ const viewUnicode = new ViewUnicode(unicodeNpm)
+
+ t.test('package from git', async t => {
+ await view.exec(['https://github.com/npm/green'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with license, bugs, repository and other fields', async t => {
+ await view.exec(['green@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with more than 25 deps', async t => {
+ await view.exec(['black@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with maintainers info as object', async t => {
+ await view.exec(['pink@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with homepage', async t => {
+ await view.exec(['orange@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with no versions', async t => {
+ await view.exec(['brown'])
+ t.equal(logs, '', 'no info to display')
+ })
+
+ t.test('package with no repo or homepage', async t => {
+ await view.exec(['blue@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with semver range', async t => {
+ await view.exec(['blue@^1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with no modified time', async t => {
+ await viewUnicode.exec(['cyan@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with --json and semver range', async t => {
+ await viewJson.exec(['cyan@^1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('package with --json and no versions', async t => {
+ await viewJson.exec(['brown'])
+ t.equal(logs, '', 'no info to display')
+ })
+})
+
+t.test('should log info of package in current working dir', async t => {
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'blue',
+ version: '1.0.0',
+ }, null, 2),
+ })
+
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const npm = mockNpm({
+ prefix: testDir,
+ config: {
+ tag: '1.0.0',
+ },
+ })
+ const view = new View(npm)
+
+ t.test('specific version', async t => {
+ await view.exec(['.@1.0.0'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('non-specific version', async t => {
+ await view.exec(['.'])
+ t.matchSnapshot(logs)
+ })
+})
+
+t.test('should log info by field name', async t => {
+ const ViewJson = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const jsonNpm = mockNpm({
+ config: {
+ tag: 'latest',
+ json: true,
+ },
+ })
+
+ const viewJson = new ViewJson(jsonNpm)
+
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const npm = mockNpm()
+ const view = new View(npm)
+
+ t.test('readme', async t => {
+ await view.exec(['yellow@1.0.0', 'readme'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('several fields', async t => {
+ await viewJson.exec(['yellow@1.0.0', 'name', 'version', 'foo[bar]'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('several fields with several versions', async t => {
+ await view.exec(['yellow@1.x.x', 'author'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('nested field with brackets', async t => {
+ await viewJson.exec(['orange@1.0.0', 'dist[shasum]'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('maintainers with email', async t => {
+ await viewJson.exec(['yellow@1.0.0', 'maintainers', 'name'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('maintainers with url', async t => {
+ await viewJson.exec(['pink@1.0.0', 'maintainers'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('unknown nested field ', async t => {
+ await view.exec(['yellow@1.0.0', 'dist.foobar'])
+ t.equal(logs, '', 'no info to display')
+ })
+
+ t.test('array field - 1 element', async t => {
+ await view.exec(['purple@1.0.0', 'maintainers.name'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('array field - 2 elements', async t => {
+ await view.exec(['yellow@1.x.x', 'maintainers.name'])
+ t.matchSnapshot(logs)
+ })
+})
+
+t.test('throw error if global mode', async t => {
+ const View = t.mock('../../../lib/commands/view.js')
+ const npm = mockNpm({
+ config: {
+ global: true,
+ tag: 'latest',
+ },
+ })
+ const view = new View(npm)
+ await t.rejects(
+ view.exec([]),
+ /Cannot use view command in global mode./
+ )
+})
+
+t.test('throw ENOENT error if package.json missing', async t => {
+ const testDir = t.testdir({})
+
+ const View = t.mock('../../../lib/commands/view.js')
+ const npm = mockNpm({
+ prefix: testDir,
+ })
+ const view = new View(npm)
+ await t.rejects(
+ view.exec([]),
+ { code: 'ENOENT' }
+ )
+})
+
+t.test('throw EJSONPARSE error if package.json not json', async t => {
+ const testDir = t.testdir({
+ 'package.json': 'not json, nope, not even a little bit!',
+ })
+
+ const View = t.mock('../../../lib/commands/view.js')
+ const npm = mockNpm({
+ prefix: testDir,
+ })
+ const view = new View(npm)
+ await t.rejects(
+ view.exec([]),
+ { code: 'EJSONPARSE' }
+ )
+})
+
+t.test('throw error if package.json has no name', async t => {
+ const testDir = t.testdir({
+ 'package.json': '{}',
+ })
+
+ const View = t.mock('../../../lib/commands/view.js')
+ const npm = mockNpm({
+ prefix: testDir,
+ })
+ const view = new View(npm)
+ await t.rejects(
+ view.exec([]),
+ /Invalid package.json, no "name" field/
+ )
+})
+
+t.test('throws when unpublished', async t => {
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const npm = mockNpm({
+ config: {
+ tag: '1.0.1',
+ },
+ })
+ const view = new View(npm)
+ await t.rejects(
+ view.exec(['red']),
+ { code: 'E404'}
+ )
+})
+
+t.test('workspaces', async t => {
+ t.beforeEach(() => {
+ warnMsg = undefined
+ config.json = false
+ })
+ const testDir = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'workspaces-test-package',
+ version: '1.2.3',
+ workspaces: ['test-workspace-a', 'test-workspace-b'],
+ }),
+ 'test-workspace-a': {
+ 'package.json': JSON.stringify({
+ name: 'green',
+ version: '1.2.3',
+ }),
+ },
+ 'test-workspace-b': {
+ 'package.json': JSON.stringify({
+ name: 'orange',
+ version: '1.2.3',
+ }),
+ },
+ })
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const config = {
+ unicode: false,
+ tag: 'latest',
+ }
+ let warnMsg
+ const npm = mockNpm({
+ log: {
+ warn: (msg) => {
+ warnMsg = msg
+ },
+ },
+ config,
+ localPrefix: testDir,
+ })
+ const view = new View(npm)
+
+ t.test('all workspaces', async t => {
+ await view.execWorkspaces([], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('one specific workspace', async t => {
+ await view.execWorkspaces([], ['green'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('all workspaces --json', async t => {
+ config.json = true
+ await view.execWorkspaces([], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('all workspaces single field', async t => {
+ await view.execWorkspaces(['.', 'name'], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('all workspaces nonexistent field', async t => {
+ await view.execWorkspaces(['.', 'foo'], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('all workspaces nonexistent field --json', async t => {
+ config.json = true
+ await view.execWorkspaces(['.', 'foo'], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('all workspaces single field --json', async t => {
+ config.json = true
+ await view.execWorkspaces(['.', 'name'], [])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('single workspace --json', async t => {
+ config.json = true
+ await view.execWorkspaces([], ['green'])
+ t.matchSnapshot(logs)
+ })
+
+ t.test('remote package name', async t => {
+ await view.execWorkspaces(['pink'], [])
+ t.matchSnapshot(warnMsg)
+ t.matchSnapshot(logs)
+ })
+})
+
+t.test('completion', async t => {
+ const View = t.mock('../../../lib/commands/view.js', {
+ pacote: {
+ packument,
+ },
+ })
+ const npm = mockNpm({
+ config: {
+ tag: '1.0.1',
+ },
+ })
+ const view = new View(npm)
+ const res = await view.completion({
+ conf: { argv: { remain: ['npm', 'view', 'green@1.0.0'] } },
+ })
+ t.ok(res, 'returns back fields')
+})
+
+t.test('no registry completion', async t => {
+ const View = t.mock('../../../lib/commands/view.js')
+ const npm = mockNpm({
+ config: {
+ tag: '1.0.1',
+ },
+ })
+ const view = new View(npm)
+ const res = await view.completion({conf: { argv: { remain: ['npm', 'view'] } } })
+ t.notOk(res, 'there is no package completion')
+ t.end()
+})