1 2const NPMLOG = require('npmlog') 3const { LEVELS } = require('proc-log') 4 5const npmEmitLog = NPMLOG.emitLog.bind(NPMLOG) 6const npmLog = NPMLOG.log.bind(NPMLOG) 7 8const merge = (...objs) => objs.reduce((acc, obj) => ({ ...acc, ...obj })) 9 10const mockLogs = (otherMocks = {}) => { 11 // Return mocks as an array with getters for each level 12 // that return an array of logged properties with the 13 // level removed. This is for convenience throughout tests 14 const logs = Object.defineProperties( 15 [], 16 ['timing', ...LEVELS].reduce((acc, level) => { 17 acc[level] = { 18 get () { 19 return this 20 .filter(([l]) => level === l) 21 .map(([l, ...args]) => args) 22 }, 23 } 24 return acc 25 }, {}) 26 ) 27 28 // the above logs array is anything logged and it not filtered by level. 29 // this display array is filtered and will not include items that 30 // would not be shown in the terminal 31 const display = Object.defineProperties( 32 [], 33 ['timing', ...LEVELS].reduce((acc, level) => { 34 acc[level] = { 35 get () { 36 return this 37 .filter(([l]) => level === l) 38 .map(([l, ...args]) => args) 39 }, 40 } 41 return acc 42 }, {}) 43 ) 44 45 const npmLogBuffer = [] 46 47 // This returns an object with mocked versions of all necessary 48 // logging modules. It mocks them with methods that add logs 49 // to an array which it also returns. The reason it also returns 50 // the mocks is that in tests the same instance of these mocks 51 // should be passed to multiple calls to t.mock. 52 // XXX: this is messy and fragile and should be removed in favor 53 // of some other way to collect and filter logs across all tests 54 const logMocks = { 55 'proc-log': merge( 56 { LEVELS }, 57 LEVELS.reduce((acc, l) => { 58 acc[l] = (...args) => { 59 // Re-emit log item for since the log file listens on these 60 process.emit('log', l, ...args) 61 // Dont add pause/resume events to the logs. Those aren't displayed 62 // and emitting them is tested in the display layer 63 if (l !== 'pause' && l !== 'resume') { 64 logs.push([l, ...args]) 65 } 66 } 67 return acc 68 }, {}), 69 otherMocks['proc-log'] 70 ), 71 // Object.assign is important here because we need to assign 72 // mocked properties directly to npmlog and then mock with that 73 // object. This is necessary so tests can still directly set 74 // `log.level = 'silent'` anywhere in the test and have that 75 // that reflected in the npmlog singleton. 76 // XXX: remove with npmlog 77 npmlog: Object.assign(NPMLOG, merge( 78 { 79 log: (level, ...args) => { 80 // timing does not exist on proclog, so if it got logged 81 // with npmlog we need to push it to our logs 82 if (level === 'timing') { 83 logs.push([level, ...args]) 84 } 85 npmLog(level, ...args) 86 }, 87 write: (msg) => { 88 // npmlog.write is what outputs to the terminal. 89 // it writes in chunks so we push each chunk to an 90 // array that we will log and zero out 91 npmLogBuffer.push(msg) 92 }, 93 emitLog: (m) => { 94 // this calls the original emitLog method 95 // which will filter based on loglevel 96 npmEmitLog(m) 97 // if anything was logged then we push to our display 98 // array which we can assert against in tests 99 if (npmLogBuffer.length) { 100 // first two parts are 'npm' and a single space 101 display.push(npmLogBuffer.slice(2)) 102 } 103 npmLogBuffer.length = 0 104 }, 105 newItem: () => { 106 return { 107 info: (...p) => { 108 logs.push(['info', ...p]) 109 }, 110 warn: (...p) => { 111 logs.push(['warn', ...p]) 112 }, 113 error: (...p) => { 114 logs.push(['error', ...p]) 115 }, 116 silly: (...p) => { 117 logs.push(['silly', ...p]) 118 }, 119 completeWork: () => {}, 120 finish: () => {}, 121 } 122 }, 123 }, 124 otherMocks.npmlog 125 )), 126 } 127 128 return { logs, logMocks, display } 129} 130 131module.exports = mockLogs 132