Skip to content

Commit

Permalink
#1030@major: Adds logic for catching both synchronous and asynchronou…
Browse files Browse the repository at this point in the history
…s errors in scripts, event listeners and timers. Adds a new package called "@happy-dom/uncaught-exception-observer". Adds a new virtual console that by default will output all log entries to a virtual console printer instead of using the global NodeJS console.
  • Loading branch information
capricorn86 committed Sep 6, 2023
1 parent 7d4750e commit 4570bac
Show file tree
Hide file tree
Showing 24 changed files with 1,243 additions and 119 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ This package makes it possible to use Happy DOM with [Jest](https://jestjs.io/).

### [![Published on npm](https://img.shields.io/npm/v/@happy-dom/global-registrator.svg)](https://www.npmjs.com/package/@happy-dom/global-registrator) [global-registrator](https://github.com/capricorn86/happy-dom/tree/master/packages/global-registrator)

---

### [![Published on npm](https://img.shields.io/npm/v/@happy-dom/uncaught-exception-observer.svg)](https://www.npmjs.com/package/@happy-dom/uncaught-exception-observer) [global-registrator](https://github.com/capricorn86/happy-dom/tree/master/packages/uncaught-exception-observer)

A utility that registers Happy DOM globally, which makes it possible to use Happy DOM for testing in a Node environment.

# Performance
Expand Down
8 changes: 5 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/happy-dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"test": "vitest run",
"test:ui": "vitest --ui",
"test:watch": "vitest",
"test:debug": "vitest --inspect-brk --threads false"
"test:debug": "vitest run --inspect-brk --threads false"
},
"dependencies": {
"css.escape": "^1.5.1",
Expand Down
26 changes: 13 additions & 13 deletions packages/happy-dom/src/console/VirtualConsole.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import VirtualConsolePrinter from './VirtualConsolePrinter.js';
import VirtualConsoleLogLevelEnum from './VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from './VirtualConsoleLogTypeEnum.js';
import IVirtualConsoleLogGroup from './IVirtualConsoleLogGroup.js';
import IVirtualConsolePrinter from './types/IVirtualConsolePrinter.js';
import VirtualConsoleLogLevelEnum from './enums/VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from './enums/VirtualConsoleLogTypeEnum.js';
import IVirtualConsoleLogGroup from './types/IVirtualConsoleLogGroup.js';
import * as PerfHooks from 'perf_hooks';

/**
Expand All @@ -14,7 +14,7 @@ export default class VirtualConsole implements Console {
// This is not part of the browser specs.
public Console: NodeJS.ConsoleConstructor;

private _printer: VirtualConsolePrinter;
private _printer: IVirtualConsolePrinter;
private _count: { [label: string]: number } = {};
private _time: { [label: string]: number } = {};
private _groupID = 0;
Expand All @@ -25,7 +25,7 @@ export default class VirtualConsole implements Console {
*
* @param printer Console printer.
*/
constructor(printer: VirtualConsolePrinter) {
constructor(printer: IVirtualConsolePrinter) {
this._printer = printer;
}

Expand All @@ -40,7 +40,7 @@ export default class VirtualConsole implements Console {
this._printer.print({
type: VirtualConsoleLogTypeEnum.assert,
level: VirtualConsoleLogLevelEnum.error,
message: args,
message: ['Assertion failed:', ...args],
group: this._groups[this._groups.length - 1] || null
});
}
Expand Down Expand Up @@ -165,15 +165,15 @@ export default class VirtualConsole implements Console {
this._groupID++;
const group = {
id: this._groupID,
label: label || '',
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 || ''],
message: [label || 'default'],
group
});
}
Expand All @@ -187,15 +187,15 @@ export default class VirtualConsole implements Console {
this._groupID++;
const group = {
id: this._groupID,
label: label || '',
label: label || 'default',
collapsed: true,
parent: this._groups[this._groups.length - 1] || null
};
this._groups.push(group);
this._printer.print({
type: VirtualConsoleLogTypeEnum.group,
type: VirtualConsoleLogTypeEnum.groupCollapsed,
level: VirtualConsoleLogLevelEnum.log,
message: [label || ''],
message: [label || 'default'],
group
});
}
Expand Down Expand Up @@ -335,7 +335,7 @@ export default class VirtualConsole implements Console {
this._printer.print({
type: VirtualConsoleLogTypeEnum.trace,
level: VirtualConsoleLogLevelEnum.log,
message: [...args, new Error('stack').stack],
message: [...args, new Error('stack').stack.replace('Error: stack', '')],
group: this._groups[this._groups.length - 1] || null
});
}
Expand Down
11 changes: 6 additions & 5 deletions packages/happy-dom/src/console/VirtualConsolePrinter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import IVirtualConsoleLogEntry from './IVirtualConsoleLogEntry.js';
import VirtualConsoleLogLevelEnum from './VirtualConsoleLogLevelEnum.js';
import IVirtualConsoleLogEntry from './types/IVirtualConsoleLogEntry.js';
import VirtualConsoleLogLevelEnum from './enums/VirtualConsoleLogLevelEnum.js';
import Event from '../event/Event.js';
import VirtualConsoleUtility from './VirtualConsoleUtility.js';
import VirtualConsoleLogEntryStringifier from './utilities/VirtualConsoleLogEntryStringifier.js';
import IVirtualConsolePrinter from './types/IVirtualConsolePrinter.js';

/**
* Virtual console printer.
*/
export default class VirtualConsolePrinter {
export default class VirtualConsolePrinter implements IVirtualConsolePrinter {
private _logEntries: IVirtualConsoleLogEntry[] = [];
private _listeners: {
print: Array<(event: Event) => void>;
Expand Down Expand Up @@ -98,7 +99,7 @@ export default class VirtualConsolePrinter {
let output = '';
for (const logEntry of logEntries) {
if (logEntry.level >= logLevel) {
output += VirtualConsoleUtility.stringifyMessage(logEntry.message);
output += VirtualConsoleLogEntryStringifier.toString(logEntry);
}
}
return output;
Expand Down
26 changes: 0 additions & 26 deletions packages/happy-dom/src/console/VirtualConsoleUtility.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import IVirtualConsoleLogGroup from './IVirtualConsoleLogGroup.js';
import VirtualConsoleLogLevelEnum from './VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from './VirtualConsoleLogTypeEnum.js';
import VirtualConsoleLogLevelEnum from '../enums/VirtualConsoleLogLevelEnum.js';
import VirtualConsoleLogTypeEnum from '../enums/VirtualConsoleLogTypeEnum.js';

export default interface IVirtualConsoleLogEntry {
type: VirtualConsoleLogTypeEnum;
Expand Down
58 changes: 58 additions & 0 deletions packages/happy-dom/src/console/types/IVirtualConsolePrinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import IVirtualConsoleLogEntry from './IVirtualConsoleLogEntry.js';
import VirtualConsoleLogLevelEnum from '../enums/VirtualConsoleLogLevelEnum.js';
import Event from '../../event/Event.js';

/**
* Virtual console printer.
*/
export default interface IVirtualConsolePrinter {
/**
* Writes to the output.
*
* @param logEntry Log entry.
*/
print(logEntry: IVirtualConsoleLogEntry): void;

/**
* Clears the output.
*/
clear(): void;

/**
* Adds an event listener.
*
* @param eventType Event type ("print" or "clear").
* @param listener Listener.
*/
addEventListener(eventType: 'print' | 'clear', listener: (event: Event) => void): void;

/**
* Removes an event listener.
*
* @param eventType Event type ("print" or "clear").
* @param listener Listener.
*/
removeEventListener(eventType: 'print' | 'clear', listener: (event: Event) => void): void;

/**
* Dispatches an event.
*
* @param event Event.
*/
dispatchEvent(event: Event): void;

/**
* Reads the buffer.
*
* @returns Console log entries.
*/
read(): IVirtualConsoleLogEntry[];

/**
* Returns the buffer as a string.
*
* @param [logLevel] Log level.
* @returns Buffer as a string of concatenated log entries.
*/
readAsString(logLevel: VirtualConsoleLogLevelEnum): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import IVirtualConsoleLogEntry from '../types/IVirtualConsoleLogEntry.js';
import VirtualConsoleLogTypeEnum from '../enums/VirtualConsoleLogTypeEnum.js';

/**
* Virtual console utility.
*/
export default class VirtualConsoleLogEntryStringifier {
/**
* Stringifies a log entry.
*
* @param logEntry Log entry.
* @returns Stringified message.
*/
public static toString(logEntry: IVirtualConsoleLogEntry): string {
if (this.isLogEntryCollapsed(logEntry)) {
return '';
}

const tabbing = this.getLogEntryGroupTabbing(logEntry);
let output = tabbing;
for (const part of logEntry.message) {
output += output !== '' && output !== tabbing ? ' ' : '';
if (
typeof part === 'object' &&
(part === null || part.constructor.name === 'Object' || Array.isArray(part))
) {
try {
output += JSON.stringify(part);
} catch (error) {
output += new Error('Failed to JSON stringify object in log entry.').stack.replace(
/\n at/gm,
'\n ' + tabbing + 'at'
);
}
} else if (typeof part === 'object' && part['message'] && part['stack']) {
output += part['stack'].replace(/\n at/gm, '\n ' + tabbing + 'at');
} else {
output += this.getLogEntryIcon(logEntry) + String(part);
}
}
return output + '\n';
}

/**
* Gets the log entry icon.
*
* @param logEntry Log entry.
* @returns Icon.
*/
private static getLogEntryIcon(logEntry: IVirtualConsoleLogEntry): string {
switch (logEntry.type) {
case VirtualConsoleLogTypeEnum.group:
return '▼ ';
case VirtualConsoleLogTypeEnum.groupCollapsed:
return '▶ ';
}
return '';
}

/**
* Gets the log entry group tabbing.
*
* @param logEntry Log entry.
* @returns Tabbing.
*/
private static getLogEntryGroupTabbing(logEntry: IVirtualConsoleLogEntry): string {
let tabs = '';
let group =
logEntry.type === VirtualConsoleLogTypeEnum.group ||
logEntry.type === VirtualConsoleLogTypeEnum.groupCollapsed
? logEntry.group?.parent
: logEntry.group;
while (group) {
tabs += ' ';
group = group.parent;
}
return tabs;
}

/**
* Checks if the log entry content is collapsed.
*
* @param logEntry Log entry.
* @returns True if collapsed.
*/
private static isLogEntryCollapsed(logEntry: IVirtualConsoleLogEntry): boolean {
let group =
logEntry.type === VirtualConsoleLogTypeEnum.group ||
logEntry.type === VirtualConsoleLogTypeEnum.groupCollapsed
? logEntry.group?.parent
: logEntry.group;
while (group) {
if (group.collapsed) {
return true;
}
group = group.parent;
}
return false;
}
}
Loading

0 comments on commit 4570bac

Please sign in to comment.