|
| 1 | +/*global globalThis*/ |
| 2 | + |
| 3 | +const queue = []; |
| 4 | +let stack = []; |
| 5 | +let index = 0; |
| 6 | + |
| 7 | +async function process() { |
| 8 | + const id = index++; |
| 9 | + if (id === queue.length) { |
| 10 | + queue.length = index = 0; |
| 11 | + return; |
| 12 | + } |
| 13 | + const [op, name, fn, extra] = queue[id]; |
| 14 | + queue[id] = undefined; |
| 15 | + await processors[op](name, fn, extra); |
| 16 | + await process(); |
| 17 | +} |
| 18 | + |
| 19 | +const processors = { |
| 20 | + async describe(name, fn, path) { |
| 21 | + stack.push(name); |
| 22 | + log('INFO', name); |
| 23 | + await fn(); |
| 24 | + stack.pop(); |
| 25 | + }, |
| 26 | + async test(name, fn, path) { |
| 27 | + let stackBefore = stack; |
| 28 | + stack = path.concat(name); |
| 29 | + logBuffer = []; |
| 30 | + await new Promise(resolve => { |
| 31 | + let calls = 0; |
| 32 | + const done = () => { |
| 33 | + if (calls++) throw Error(`Callback called multiple times\n\t${name}`); |
| 34 | + log('INFO', `✅ ${name}`); |
| 35 | + resolve(); |
| 36 | + }; |
| 37 | + Promise.resolve(done) |
| 38 | + .then(fn) |
| 39 | + .then(() => calls || done()) |
| 40 | + .catch(err => { |
| 41 | + log('ERROR', `🚨 ${name}`); |
| 42 | + log('ERROR', '\t' + String(err.stack || err.message || err)); |
| 43 | + resolve(); |
| 44 | + }); |
| 45 | + }); |
| 46 | + for (let i=0; i<logBuffer.length; i++) log(...logBuffer[i]); |
| 47 | + logBuffer = undefined; |
| 48 | + stack = stackBefore; |
| 49 | + } |
| 50 | +}; |
| 51 | + |
| 52 | + |
| 53 | +let logBuffer; |
| 54 | + |
| 55 | +function wrap(obj, method) { |
| 56 | + obj[method] = function() { |
| 57 | + let out = ' '; |
| 58 | + for (let i=0; i<arguments.length; i++) { |
| 59 | + let val = arguments[i]; |
| 60 | + if (typeof val === 'object' && val) { |
| 61 | + val = JSON.stringify(val); |
| 62 | + } |
| 63 | + if (out) out += ' '; |
| 64 | + out += val; |
| 65 | + } |
| 66 | + if (method!=='error') out = `\u001b[37m${out}\u001b[0m`; |
| 67 | + if (logBuffer) { |
| 68 | + logBuffer.push([method.toUpperCase(), out]); |
| 69 | + } |
| 70 | + else { |
| 71 | + log(method.toUpperCase(), out); |
| 72 | + } |
| 73 | + }; |
| 74 | +} |
| 75 | +wrap(console, 'log'); |
| 76 | +wrap(console, 'info'); |
| 77 | +wrap(console, 'warn'); |
| 78 | +wrap(console, 'error'); |
| 79 | + |
| 80 | +function log(type, msg) { |
| 81 | + if (type === 'ERROR') { |
| 82 | + msg = `\u001b[31m${msg}\u001b[39m`; |
| 83 | + } |
| 84 | + if (type === 'SUCCESS') { |
| 85 | + msg = `\u001b[32m${msg}\u001b[39m`; |
| 86 | + } |
| 87 | + print(Array.from({ length: stack.length }).fill(' ').join('') + msg); |
| 88 | +} |
| 89 | + |
| 90 | +function push(op, name, fn, extra) { |
| 91 | + if (queue.push([op, name, fn, extra]) === 1) { |
| 92 | + setTimeout(process); |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +globalThis.describe = (name, fn) => { |
| 97 | + push('describe', name, fn, stack.slice()); |
| 98 | +}; |
| 99 | + |
| 100 | +globalThis.test = (name, fn) => { |
| 101 | + push('test', name, fn, stack.slice()); |
| 102 | +}; |
| 103 | + |
| 104 | +globalThis.expect = (subject) => new Expect(subject); |
| 105 | + |
| 106 | +const SUBJECT = Symbol.for('subject'); |
| 107 | +const NEGATED = Symbol.for('negated'); |
| 108 | +class Expect { |
| 109 | + constructor(subject) { |
| 110 | + this[SUBJECT] = subject; |
| 111 | + } |
| 112 | + get not() { |
| 113 | + this[NEGATED] = true; |
| 114 | + return this; |
| 115 | + } |
| 116 | + toBeGreaterThan(value) { |
| 117 | + const subject = this[SUBJECT]; |
| 118 | + const negated = this[NEGATED]; |
| 119 | + |
| 120 | + const isOver = subject > value; |
| 121 | + let msg = `Expected ${subject}${negated?' not':''} to be greater than ${value}`; |
| 122 | + if (logBuffer) { |
| 123 | + for (let i=logBuffer.length; i-- > -1; ) { |
| 124 | + if (i<0 || logBuffer[i][2] === 1) { |
| 125 | + logBuffer.splice(i+1, 0, [isOver !== negated ? 'SUCCESS' : 'ERROR', ' ' + msg, 1]); |
| 126 | + break; |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + else { |
| 131 | + log(isOver !== negated ? 'SUCCESS' : 'ERROR', ' ' + msg); |
| 132 | + } |
| 133 | + } |
| 134 | +} |
0 commit comments