import IVirtualConsolePrinter from './IVirtualConsolePrinter.js'; import VirtualConsoleLogLevelEnum from './enums/VirtualConsoleLogLevelEnum.js'; import VirtualConsoleLogTypeEnum from './enums/VirtualConsoleLogTypeEnum.js'; import IVirtualConsoleLogGroup from './IVirtualConsoleLogGroup.js'; import { ConsoleConstructor } from 'console'; /** * Virtual Console. * * @see https://developer.mozilla.org/en-US/docs/Web/API/Console */ export default class VirtualConsole implements Console { // This is needed as the interface for the NodeJS Console also have a reference to the ConsoleConstructor class as a property for some reason. // This is not part of the browser specs. public Console: ConsoleConstructor; #printer: IVirtualConsolePrinter; #count: { [label: string]: number } = {}; #time: { [label: string]: number } = {}; #groupID = 0; #groups: IVirtualConsoleLogGroup[] = []; /** * Constructor. * * @param printer Console printer. */ constructor(printer: IVirtualConsolePrinter) { this.#printer = printer; } /** * Writes an error message to the console if the assertion is false. If the assertion is true, nothing happens. * * @param assertion Assertion. * @param args Arguments. */ public assert(assertion: boolean, ...args: Array): void { if (!assertion) { this.#printer.print({ type: VirtualConsoleLogTypeEnum.assert, level: VirtualConsoleLogLevelEnum.error, message: ['Assertion failed:', ...args], group: this.#groups[this.#groups.length - 1] || null }); } } /** * Clears the console. */ public clear(): void { this.#printer.clear(); } /** * Logs the number of times that this particular call to count() has been called. * * @param [label='default'] Label. */ public count(label = 'default'): void { if (!this.#count[label]) { this.#count[label] = 0; } this.#count[label]++; this.#printer.print({ type: VirtualConsoleLogTypeEnum.count, level: VirtualConsoleLogLevelEnum.info, message: [`${label}: ${this.#count[label]}`], group: this.#groups[this.#groups.length - 1] || null }); } /** * Resets the counter. * * @param [label='default'] Label. */ public countReset(label = 'default'): void { delete this.#count[label]; this.#printer.print({ type: VirtualConsoleLogTypeEnum.countReset, level: VirtualConsoleLogLevelEnum.warn, message: [`${label}: 0`], group: this.#groups[this.#groups.length - 1] || null }); } /** * Outputs a message to the web console at the "debug" log level. * * @param args Arguments. */ public debug(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.debug, level: VirtualConsoleLogLevelEnum.log, message: args, group: this.#groups[this.#groups.length - 1] || null }); } /** * Displays an interactive list of the properties of the specified JavaScript object. * * @param data Data. */ public dir(data: object): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.dir, level: VirtualConsoleLogLevelEnum.log, message: [data], group: this.#groups[this.#groups.length - 1] || null }); } /** * Displays an interactive tree of the descendant elements of the specified XML/HTML element. * * @param data Data. */ public dirxml(data: object): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.dirxml, level: VirtualConsoleLogLevelEnum.log, message: [data], group: this.#groups[this.#groups.length - 1] || null }); } /** * Outputs an error message to the console. * * @param args Arguments. */ public error(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.error, level: VirtualConsoleLogLevelEnum.error, message: args, group: this.#groups[this.#groups.length - 1] || null }); } /** * Alias for error(). * * @deprecated * @alias error() * @param args Arguments. */ public exception(...args: Array): void { this.error(...args); } /** * Creates a new inline group in the console, causing any subsequent console messages to be indented by an additional level, until console.groupEnd() is called. * * @param [label] Label. */ public group(label?: string): void { this.#groupID++; const group = { id: this.#groupID, label: label || 'default', collapsed: false, parent: this.#groups[this.#groups.length - 1] || null }; this.#groups.push(group); this.#printer.print({ type: VirtualConsoleLogTypeEnum.group, level: VirtualConsoleLogLevelEnum.log, message: [label || 'default'], group }); } /** * Creates a new inline group in the console, but prints it as collapsed, requiring the use of a disclosure button to expand it. * * @param [label] Label. */ public groupCollapsed(label?: string): void { this.#groupID++; const group = { id: this.#groupID, label: label || 'default', collapsed: true, parent: this.#groups[this.#groups.length - 1] || null }; this.#groups.push(group); this.#printer.print({ type: VirtualConsoleLogTypeEnum.groupCollapsed, level: VirtualConsoleLogLevelEnum.log, message: [label || 'default'], group }); } /** * Exits the current inline group in the console. */ public groupEnd(): void { if (this.#groups.length === 0) { return; } this.#groups.pop(); } /** * * @param args */ public info(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.info, level: VirtualConsoleLogLevelEnum.info, message: args, group: this.#groups[this.#groups.length - 1] || null }); } /** * Outputs a message to the console. * * @param args Arguments. */ public log(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.log, level: VirtualConsoleLogLevelEnum.log, message: args, group: this.#groups[this.#groups.length - 1] || null }); } /** * Starts recording a performance profile. * * TODO: Implement this. */ public profile(): void { throw new Error('Method not implemented.'); } /** * Stops recording a performance profile. * * TODO: Implement this. */ public profileEnd(): void { throw new Error('Method not implemented.'); } /** * Displays tabular data as a table. * * @param data Data. */ public table(data: { [key: string]: number | string | boolean } | string[]): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.table, level: VirtualConsoleLogLevelEnum.log, message: [data], group: this.#groups[this.#groups.length - 1] || null }); } /** * Starts a timer you can use to track how long an operation takes. * * @param [label=default] Label. */ public time(label = 'default'): void { this.#time[label] = performance.now(); } /** * Stops a timer that was previously started by calling console.time(). * The method logs the elapsed time in milliseconds. * * @param [label=default] Label. */ public timeEnd(label = 'default'): void { const time = this.#time[label]; if (time) { const duration = performance.now() - time; this.#printer.print({ type: VirtualConsoleLogTypeEnum.timeEnd, level: VirtualConsoleLogLevelEnum.info, message: [`${label}: ${duration}ms - timer ended`], group: this.#groups[this.#groups.length - 1] || null }); } } /** * Logs the current value of a timer that was previously started by calling console.time(). * The method logs the elapsed time in milliseconds. * * @param [label=default] Label. * @param [args] Arguments. */ public timeLog(label = 'default', ...args: Array): void { const time = this.#time[label]; if (time) { const duration = performance.now() - time; this.#printer.print({ type: VirtualConsoleLogTypeEnum.timeLog, level: VirtualConsoleLogLevelEnum.info, message: [`${label}: ${duration}ms`, ...args], group: this.#groups[this.#groups.length - 1] || null }); } } /** * Adds a single marker to the browser's Performance tool. * * TODO: Implement this. */ public timeStamp(): void { throw new Error('Method not implemented.'); } /** * Outputs a stack trace to the console. * * @param args Arguments. */ public trace(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.trace, level: VirtualConsoleLogLevelEnum.log, message: [...args, new Error('stack').stack.replace('Error: stack', '')], group: this.#groups[this.#groups.length - 1] || null }); } /** * Outputs a warning message to the console. * * @param args Arguments. */ public warn(...args: Array): void { this.#printer.print({ type: VirtualConsoleLogTypeEnum.warn, level: VirtualConsoleLogLevelEnum.warn, message: args, group: this.#groups[this.#groups.length - 1] || null }); } }