1const t = require('tap') 2const fs = require('fs') 3const path = require('path') 4const { load: loadMockNpm } = require('../../fixtures/mock-npm') 5 6const completionScript = fs 7 .readFileSync(path.resolve(__dirname, '../../../lib/utils/completion.sh'), { encoding: 'utf8' }) 8 .replace(/^#!.*?\n/, '') 9 10const loadMockCompletion = async (t, o = {}) => { 11 const { globals = {}, windows, ...options } = o 12 const res = await loadMockNpm(t, { 13 command: 'completion', 14 ...options, 15 globals: (dirs) => ({ 16 'process.platform': windows ? 'win32' : 'posix', 17 'process.env.term': 'notcygwin', 18 'process.env.msystem': 'nogmingw', 19 ...(typeof globals === 'function' ? globals(dirs) : globals), 20 }), 21 }) 22 return { 23 resetGlobals: res.mockedGlobals.reset, 24 ...res, 25 } 26} 27 28const loadMockCompletionComp = async (t, word, line) => 29 loadMockCompletion(t, { 30 globals: { 31 'process.env.COMP_CWORD': word, 32 'process.env.COMP_LINE': line, 33 'process.env.COMP_POINT': line.length, 34 }, 35 }) 36 37t.test('completion', async t => { 38 t.test('completion completion', async t => { 39 const { outputs, completion } = await loadMockCompletion(t, { 40 prefixDir: { 41 '.bashrc': 'aaa', 42 '.zshrc': 'aaa', 43 }, 44 globals: ({ prefix }) => ({ 45 'process.env.HOME': prefix, 46 }), 47 }) 48 49 await completion.completion({ w: 2 }) 50 t.matchSnapshot(outputs, 'both shells') 51 }) 52 53 t.test('completion completion no known shells', async t => { 54 const { outputs, completion } = await loadMockCompletion(t, { 55 globals: ({ prefix }) => ({ 56 'process.env.HOME': prefix, 57 }), 58 }) 59 60 await completion.completion({ w: 2 }) 61 t.matchSnapshot(outputs, 'no responses') 62 }) 63 64 t.test('completion completion wrong word count', async t => { 65 const { outputs, completion } = await loadMockCompletion(t) 66 67 await completion.completion({ w: 3 }) 68 t.matchSnapshot(outputs, 'no responses') 69 }) 70 71 t.test('dump script when completion is not being attempted', async t => { 72 let errorHandler, data 73 const { completion, resetGlobals } = await loadMockCompletion(t, { 74 globals: { 75 'process.stdout.on': (event, handler) => { 76 errorHandler = handler 77 resetGlobals['process.stdout.on']() 78 }, 79 'process.stdout.write': (chunk, callback) => { 80 data = chunk 81 process.nextTick(() => { 82 callback() 83 errorHandler({ errno: 'EPIPE' }) 84 }) 85 resetGlobals['process.stdout.write']() 86 }, 87 }, 88 }) 89 90 await completion.exec() 91 t.equal(data, completionScript, 'wrote the completion script') 92 }) 93 94 t.test('dump script exits correctly when EPIPE is emitted on stdout', async t => { 95 let errorHandler, data 96 const { completion, resetGlobals } = await loadMockCompletion(t, { 97 globals: { 98 'process.stdout.on': (event, handler) => { 99 if (event === 'error') { 100 errorHandler = handler 101 } 102 resetGlobals['process.stdout.on']() 103 }, 104 'process.stdout.write': (chunk, callback) => { 105 data = chunk 106 process.nextTick(() => { 107 errorHandler({ errno: 'EPIPE' }) 108 callback() 109 }) 110 resetGlobals['process.stdout.write']() 111 }, 112 }, 113 }) 114 115 await completion.exec() 116 t.equal(data, completionScript, 'wrote the completion script') 117 }) 118 119 t.test('single command name', async t => { 120 const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm conf') 121 122 await completion.exec(['npm', 'conf']) 123 t.matchSnapshot(outputs, 'single command name') 124 }) 125 126 t.test('multiple command names', async t => { 127 const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm a') 128 129 await completion.exec(['npm', 'a']) 130 t.matchSnapshot(outputs, 'multiple command names') 131 }) 132 133 t.test('completion of invalid command name does nothing', async t => { 134 const { outputs, completion } = await loadMockCompletionComp(t, 1, 'npm compute') 135 136 await completion.exec(['npm', 'compute']) 137 t.matchSnapshot(outputs, 'no results') 138 }) 139 140 t.test('subcommand completion', async t => { 141 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access ') 142 143 await completion.exec(['npm', 'access', '']) 144 t.matchSnapshot(outputs, 'subcommands') 145 }) 146 147 t.test('filtered subcommands', async t => { 148 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm access p') 149 150 await completion.exec(['npm', 'access', 'p']) 151 t.matchSnapshot(outputs, 'filtered subcommands') 152 }) 153 154 t.test('commands with no completion', async t => { 155 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm adduser ') 156 157 // quotes around adduser are to ensure coverage when unescaping commands 158 await completion.exec(['npm', "'adduser'", '']) 159 t.matchSnapshot(outputs, 'no results') 160 }) 161 162 t.test('flags', async t => { 163 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --v') 164 165 await completion.exec(['npm', 'install', '--v']) 166 t.matchSnapshot(outputs, 'flags') 167 }) 168 169 t.test('--no- flags', async t => { 170 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm install --no-v') 171 172 await completion.exec(['npm', 'install', '--no-v']) 173 t.matchSnapshot(outputs, 'flags') 174 }) 175 176 t.test('double dashes escape from flag completion', async t => { 177 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm -- install --') 178 179 await completion.exec(['npm', '--', 'install', '--']) 180 t.matchSnapshot(outputs, 'full command list') 181 }) 182 183 t.test('completion cannot complete options that take a value in mid-command', async t => { 184 const { outputs, completion } = await loadMockCompletionComp(t, 2, 'npm --registry install') 185 186 await completion.exec(['npm', '--registry', 'install']) 187 t.matchSnapshot(outputs, 'does not try to complete option arguments in the middle of a command') 188 }) 189}) 190 191t.test('windows without bash', async t => { 192 const { outputs, completion } = await loadMockCompletion(t, { windows: true }) 193 await t.rejects( 194 completion.exec(), 195 { code: 'ENOTSUP', message: /completion supported only in MINGW/ }, 196 'returns the correct error' 197 ) 198 t.matchSnapshot(outputs, 'no output') 199}) 200