• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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