• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const EventEmitter = require('events')
3const tmock = require('../../fixtures/tmock')
4const mockNpm = require('../../fixtures/mock-npm')
5
6const mockOpenUrlPrompt = async (t, {
7  questionShouldResolve = true,
8  openUrlPromptInterrupted = false,
9  openerResult = null,
10  isTTY = true,
11  emitter = null,
12  url: openUrl = 'https://www.npmjs.com',
13  ...config
14}) => {
15  const mock = await mockNpm(t, {
16    globals: {
17      'process.stdin.isTTY': isTTY,
18      'process.stdout.isTTY': isTTY,
19    },
20    config,
21  })
22
23  let openerUrl = null
24  let openerOpts = null
25
26  const openUrlPrompt = tmock(t, '{LIB}/utils/open-url-prompt.js', {
27    '@npmcli/promise-spawn': {
28      open: async (url, options) => {
29        openerUrl = url
30        openerOpts = options
31        if (openerResult) {
32          throw openerResult
33        }
34      },
35    },
36    readline: {
37      createInterface: () => ({
38        question: (_q, cb) => {
39          if (questionShouldResolve === true) {
40            cb()
41          }
42        },
43        close: () => {},
44        on: (_signal, cb) => {
45          if (openUrlPromptInterrupted && _signal === 'SIGINT') {
46            cb()
47          }
48        },
49      }),
50    },
51  })
52
53  let error
54  const args = [mock.npm, openUrl, 'npm home', 'prompt']
55  if (emitter) {
56    mock.open = openUrlPrompt(...args, emitter)
57  } else {
58    await openUrlPrompt(...args).catch((er) => error = er)
59  }
60
61  return {
62    ...mock,
63    openerUrl,
64    openerOpts,
65    OUTPUT: mock.joinedOutput(),
66    emitter,
67    error,
68  }
69}
70
71t.test('does not open a url in non-interactive environments', async t => {
72  const { openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { isTTY: false })
73
74  t.equal(openerUrl, null, 'did not open')
75  t.same(openerOpts, null, 'did not open')
76})
77
78t.test('opens a url', async t => {
79  const { OUTPUT, openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { browser: true })
80
81  t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url')
82  t.same(openerOpts, { command: null }, 'passed command as null (the default)')
83  t.matchSnapshot(OUTPUT)
84})
85
86t.test('opens a url with browser string', async t => {
87  const { openerUrl, openerOpts } = await mockOpenUrlPrompt(t, { browser: 'firefox' })
88
89  t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url')
90  // FIXME: browser string is parsed as a boolean in config layer
91  // this is a bug that should be fixed or the config should not allow it
92  t.same(openerOpts, { command: null }, 'passed command as null (the default)')
93})
94
95t.test('prints json output', async t => {
96  const { OUTPUT } = await mockOpenUrlPrompt(t, { json: true })
97
98  t.matchSnapshot(OUTPUT)
99})
100
101t.test('returns error for non-https url', async t => {
102  const { error, OUTPUT, openerUrl, openerOpts } = await mockOpenUrlPrompt(t, {
103    url: 'ftp://www.npmjs.com',
104  })
105
106  t.match(error, /Invalid URL/, 'got the correct error')
107  t.equal(openerUrl, null, 'did not open')
108  t.same(openerOpts, null, 'did not open')
109  t.same(OUTPUT, '', 'printed no output')
110})
111
112t.test('does not open url if canceled', async t => {
113  const emitter = new EventEmitter()
114  const { openerUrl, openerOpts, open } = await mockOpenUrlPrompt(t, {
115    questionShouldResolve: false,
116    emitter,
117  })
118
119  emitter.emit('abort')
120
121  await open
122
123  t.equal(openerUrl, null, 'did not open')
124  t.same(openerOpts, null, 'did not open')
125})
126
127t.test('returns error when opener errors', async t => {
128  const { error, openerUrl } = await mockOpenUrlPrompt(t, {
129    openerResult: new Error('Opener failed'),
130  })
131
132  t.match(error, /Opener failed/, 'got the correct error')
133  t.equal(openerUrl, 'https://www.npmjs.com', 'did not open')
134})
135
136t.test('throws "canceled" error on SIGINT', async t => {
137  const emitter = new EventEmitter()
138  const { open } = await mockOpenUrlPrompt(t, {
139    questionShouldResolve: false,
140    openUrlPromptInterrupted: true,
141    emitter,
142  })
143
144  await t.rejects(open, /canceled/, 'message is canceled')
145})
146