mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
fix(console): describe Map, Set, function, DOM node, symbol and cyclic arguments (#3199)
`list_console_messages` / `get_console_message` print console arguments
through `jsonValue()`. On main a Map, Set or DOM node prints as `{}`,
and a cyclic object shows `<error: Argument N is no longer available>`.
This formats those arguments from the description and preview that CDP
already sends with them, so nothing extra is evaluated in the page.
Plain objects, arrays and primitives still use `jsonValue()`.
**User-visible behavior change (Date).** Dates now print the CDP
description instead of the `jsonValue()` ISO string. Main prints
`1970-01-01T00:00:00.000Z`, this PR prints `Thu Jan 01 1970 00:00:00
GMT+0000 (Coordinated Universal Time)`, the same text the DevTools
console shows. Main's ISO output was already valid; this is a change in
format, not a fix.
**Port origin.** `src/formatters/RemoteObjectFormatter.ts` is a
plain-text port of devtools-frontend's
`front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts`
at revision `f7e7785de52d6f5955d6a9c5956c2fe2d520d091` (the
`third_party/devtools-frontend` revision this branch uses). Intentional
differences:
- Plain text instead of Lit templates: no CSS classes, tooltips or
localized strings.
- No nodeDescription markup: DOM nodes print their description as is (no
`renderNodeTitle` spans); trusted types print their description instead
of `ClassName "value"`.
- `date` and `node` print their description instead of a property
preview.
- The `includeNullOrUndefined` option is dropped.
- Top-level strings print raw; unserializable primitives (NaN, -0, 1n)
print from `unserializableValue`.
- Upstream's inline 400-character limit is named
`MAX_DESCRIPTION_LENGTH` (same value).
**Quoting.** Values formatted from a preview show nested strings as JS
literals (`'x'`), while plain objects still go through `jsonValue()` and
print as JSON (`{"a":1}`), so one message can mix both quoting styles.
**Tests (no browser):**
- `tests/formatters/RemoteObjectFormatter.test.ts`: Map/Set entries,
cyclic objects, arrays with holes and overflow, typed arrays, promise
state, and description-only values (Date, DOM node, function, symbol,
bigint).
- `tests/formatters/ConsoleFormatter.test.ts`: Map and cyclic arguments
are formatted from their preview while a plain object keeps
`jsonValue()`; an argument whose `jsonValue()` rejects and has no
preview is reported as `<error: Argument 0 is no longer available>`.
This commit is contained in:
1 parent
ae3f61c5b4
commit
968e4e5c50
4 files changed
+583
-2
No files matched your search
@@ -11,9 +11,10 @@ import {
|
||||
} from '../devtools/DevtoolsUtils.js';
|
||||
import {UncaughtError} from '../collectors/PageCollector.js';
|
||||
import * as DevTools from '../third_party/index.js';
|
||||
import type {ConsoleMessage} from '../third_party/index.js';
|
||||
import type {ConsoleMessage, Protocol} from '../third_party/index.js';
|
||||
|
||||
import type {IssueFormatter} from './IssueFormatter.js';
|
||||
import {formatRemoteObject} from './RemoteObjectFormatter.js';
|
||||
|
||||
export interface ConsoleFormatterOptions {
|
||||
fetchDetailedData?: boolean;
|
||||
@@ -48,6 +49,23 @@ interface ConsoleMessageDetailed extends ConsoleMessageConcise {
|
||||
stackTrace?: string;
|
||||
}
|
||||
|
||||
// JSON.stringify prints Map, Set, functions and DOM nodes as `{}`, and throws
|
||||
// on symbols and cyclic objects. Those are formatted from their preview.
|
||||
function isJsonLike(remoteObject: Protocol.Runtime.RemoteObject): boolean {
|
||||
if (remoteObject.type === 'object') {
|
||||
return (
|
||||
remoteObject.subtype === undefined ||
|
||||
remoteObject.subtype === 'array' ||
|
||||
remoteObject.subtype === 'null'
|
||||
);
|
||||
}
|
||||
return (
|
||||
remoteObject.type !== 'function' &&
|
||||
remoteObject.type !== 'symbol' &&
|
||||
remoteObject.type !== 'bigint'
|
||||
);
|
||||
}
|
||||
|
||||
export class ConsoleFormatter {
|
||||
readonly #id: number;
|
||||
readonly #type: string;
|
||||
@@ -152,7 +170,19 @@ export class ConsoleFormatter {
|
||||
targetId: msg._targetId(),
|
||||
});
|
||||
}
|
||||
return await arg.jsonValue();
|
||||
if (!isJsonLike(remoteObject)) {
|
||||
return formatRemoteObject(remoteObject);
|
||||
}
|
||||
try {
|
||||
return await arg.jsonValue();
|
||||
} catch (error) {
|
||||
// JSON can't represent the value (for example, a cyclic object)
|
||||
// or the page is gone; the preview still describes it.
|
||||
if (remoteObject.preview) {
|
||||
return formatRemoteObject(remoteObject);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch {
|
||||
return `<error: Argument ${i} is no longer available>`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plain-text port of devtools-frontend's RemoteObjectPreviewFormatter
|
||||
* (front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts)
|
||||
* at revision f7e7785de52d6f5955d6a9c5956c2fe2d520d091, the
|
||||
* third_party/devtools-frontend revision this port was written against. Compare
|
||||
* with that file when syncing. It formats the preview that CDP already attaches
|
||||
* to console arguments, so nothing is evaluated in the page.
|
||||
*
|
||||
* Intentional differences from upstream:
|
||||
* - Returns plain text instead of Lit templates, so there are no CSS classes,
|
||||
* tooltips or localized strings ('empty' and 'empty × N' are English).
|
||||
* - No nodeDescription markup: DOM nodes print their description as is instead
|
||||
* of splitting it into tag, id and class spans (renderNodeTitle), and trusted
|
||||
* types print their description instead of `ClassName "value"`.
|
||||
* - 'date' and 'node' are added to the subtypes that print their description
|
||||
* instead of a property preview.
|
||||
* - The `includeNullOrUndefined` option is dropped; null and undefined
|
||||
* properties are always listed.
|
||||
* - Top-level strings print as their raw value (as the console does for string
|
||||
* arguments), and unserializable primitives (NaN, -0, 1n) print from
|
||||
* `unserializableValue`.
|
||||
* - The 400-character description limit that upstream passes to
|
||||
* trimEndWithMaxLength() inline is named MAX_DESCRIPTION_LENGTH.
|
||||
*/
|
||||
|
||||
import {DevTools} from '../third_party/index.js';
|
||||
import type {Protocol} from '../third_party/index.js';
|
||||
|
||||
const {StringUtilities} = DevTools.Platform;
|
||||
|
||||
const MAX_DESCRIPTION_LENGTH = 400;
|
||||
|
||||
const PROMISE_STATE = '[[PromiseState]]';
|
||||
const PROMISE_RESULT = '[[PromiseResult]]';
|
||||
const GENERATOR_STATE = '[[GeneratorState]]';
|
||||
const PRIMITIVE_VALUE = '[[PrimitiveValue]]';
|
||||
const WEAK_REF_TARGET = '[[WeakRefTarget]]';
|
||||
|
||||
// Subtypes whose description is shown instead of a property preview.
|
||||
const SUBTYPES_WITHOUT_VALUE_PREVIEW = new Set<string>([
|
||||
'arraybuffer',
|
||||
'dataview',
|
||||
'date',
|
||||
'error',
|
||||
'internal#entry',
|
||||
'null',
|
||||
'node',
|
||||
'regexp',
|
||||
'trustedtype',
|
||||
'webassemblymemory',
|
||||
]);
|
||||
|
||||
function sortValue(property: Protocol.Runtime.PropertyPreview): number {
|
||||
switch (property.name) {
|
||||
case PROMISE_STATE:
|
||||
return 1;
|
||||
case PROMISE_RESULT:
|
||||
return 2;
|
||||
case GENERATOR_STATE:
|
||||
case PRIMITIVE_VALUE:
|
||||
case WEAK_REF_TARGET:
|
||||
return 3;
|
||||
}
|
||||
if (property.type !== 'function' && !property.name.startsWith('#')) {
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
function formatName(name: string): string {
|
||||
return /^\s|\s$|^$|\n/.test(name) ? `"${name.replace(/\n/g, '↵')}"` : name;
|
||||
}
|
||||
|
||||
function abbreviateClassName(description: string): string {
|
||||
const parts = description.split('.');
|
||||
if (parts.length === 1 && parts[0] === 'Object') {
|
||||
return '{…}';
|
||||
}
|
||||
return parts
|
||||
.map((part, i) =>
|
||||
i < parts.length - 1 ? StringUtilities.trimMiddle(part, 3) : part,
|
||||
)
|
||||
.join('.');
|
||||
}
|
||||
|
||||
function formatPropertyValue(
|
||||
type: string,
|
||||
subtype: string | undefined,
|
||||
description = '',
|
||||
): string {
|
||||
switch (type) {
|
||||
case 'accessor':
|
||||
return '(...)';
|
||||
case 'function':
|
||||
return 'ƒ';
|
||||
case 'string':
|
||||
return StringUtilities.formatAsJSLiteral(description);
|
||||
case 'object':
|
||||
return subtype ? description : abbreviateClassName(description);
|
||||
default:
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
function arrayLength(preview: Protocol.Runtime.ObjectPreview): number {
|
||||
const match = preview.description?.match(/[[(](\d+)[\])]$/);
|
||||
return match ? Number(match[1]) : 0;
|
||||
}
|
||||
|
||||
function formatArrayItems(preview: Protocol.Runtime.ObjectPreview): string[] {
|
||||
const length = arrayLength(preview);
|
||||
const toIndex = (name: string): number => {
|
||||
const index = Number(name) >>> 0;
|
||||
return String(index) === name && index < length ? index : -1;
|
||||
};
|
||||
const indexed = preview.properties
|
||||
.filter(p => toIndex(p.name) !== -1)
|
||||
.sort((a, b) => toIndex(a.name) - toIndex(b.name));
|
||||
const others = preview.properties
|
||||
.filter(p => toIndex(p.name) === -1)
|
||||
.sort((a, b) => sortValue(a) - sortValue(b));
|
||||
|
||||
// Gaps can be shown when all properties are guaranteed to be in the preview.
|
||||
const canShowGaps = !preview.overflow;
|
||||
const gap = (count: number): string =>
|
||||
count === 1 ? 'empty' : `empty × ${count}`;
|
||||
const items: string[] = [];
|
||||
let previous = -1;
|
||||
for (const property of indexed) {
|
||||
const index = toIndex(property.name);
|
||||
const value = formatPropertyValue(
|
||||
property.type,
|
||||
property.subtype,
|
||||
property.value,
|
||||
);
|
||||
if (canShowGaps) {
|
||||
if (index - previous > 1) {
|
||||
items.push(gap(index - previous - 1));
|
||||
}
|
||||
items.push(value);
|
||||
} else {
|
||||
items.push(index === items.length ? value : `${property.name}: ${value}`);
|
||||
}
|
||||
previous = index;
|
||||
}
|
||||
if (canShowGaps && length - previous > 1) {
|
||||
items.push(gap(length - previous - 1));
|
||||
}
|
||||
for (const property of others) {
|
||||
items.push(
|
||||
`${formatName(property.name)}: ${formatPropertyValue(property.type, property.subtype, property.value)}`,
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function formatObjectItems(preview: Protocol.Runtime.ObjectPreview): string[] {
|
||||
const properties = preview.properties
|
||||
.filter(p => p.type !== 'accessor')
|
||||
.sort((a, b) => sortValue(a) - sortValue(b));
|
||||
const items: string[] = [];
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
const property = properties[i];
|
||||
const value = formatPropertyValue(
|
||||
property.type,
|
||||
property.subtype,
|
||||
property.value,
|
||||
);
|
||||
if (preview.subtype === 'promise' && property.name === PROMISE_STATE) {
|
||||
const result = properties.at(i + 1);
|
||||
if (result?.name === PROMISE_RESULT) {
|
||||
i++;
|
||||
}
|
||||
items.push(
|
||||
property.value !== 'pending' && result?.name === PROMISE_RESULT
|
||||
? `<${property.value}>: ${formatPropertyValue(result.type, result.subtype, result.value)}`
|
||||
: `<${property.value}>`,
|
||||
);
|
||||
} else if (
|
||||
preview.subtype === 'generator' &&
|
||||
property.name === GENERATOR_STATE
|
||||
) {
|
||||
items.push(`<${property.value}>`);
|
||||
} else if (property.name === PRIMITIVE_VALUE) {
|
||||
items.push(value);
|
||||
} else if (property.name === WEAK_REF_TARGET) {
|
||||
items.push(property.type === 'undefined' ? '<cleared>' : value);
|
||||
} else {
|
||||
items.push(`${formatName(property.name)}: ${value}`);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function formatEntryItems(preview: Protocol.Runtime.ObjectPreview): string[] {
|
||||
return (preview.entries ?? []).map(({key, value}) => {
|
||||
const formattedValue = formatPropertyValue(
|
||||
value.type,
|
||||
value.subtype,
|
||||
value.description,
|
||||
);
|
||||
return key
|
||||
? `${formatPropertyValue(key.type, key.subtype, key.description)} => ${formattedValue}`
|
||||
: formattedValue;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a `Runtime.ObjectPreview` the way the DevTools console previews it,
|
||||
* for example `Map(1) {1 => 2}`, `(3) [1, 2, 3]` or `{a: 1, self: {…}}`.
|
||||
*/
|
||||
function formatObjectPreview(preview: Protocol.Runtime.ObjectPreview): string {
|
||||
const description = preview.description ?? '';
|
||||
if (
|
||||
preview.type !== 'object' ||
|
||||
(preview.subtype && SUBTYPES_WITHOUT_VALUE_PREVIEW.has(preview.subtype))
|
||||
) {
|
||||
return formatPropertyValue(preview.type, preview.subtype, description);
|
||||
}
|
||||
const isArray =
|
||||
preview.subtype === 'array' || preview.subtype === 'typedarray';
|
||||
let prefix = '';
|
||||
if (isArray) {
|
||||
const length = arrayLength(preview);
|
||||
const lengthText = length > 1 ? `(${length})` : '';
|
||||
const name = description.replace(/[[(]\d+[\])]$/, '');
|
||||
prefix = name === 'Array' ? lengthText : `${name}${lengthText}`;
|
||||
} else if (description !== 'Object') {
|
||||
prefix = description;
|
||||
}
|
||||
const items = preview.entries
|
||||
? formatEntryItems(preview)
|
||||
: isArray
|
||||
? formatArrayItems(preview)
|
||||
: formatObjectItems(preview);
|
||||
if (preview.overflow) {
|
||||
items.push('…');
|
||||
}
|
||||
const body = isArray ? `[${items.join(', ')}]` : `{${items.join(', ')}}`;
|
||||
return prefix ? `${prefix} ${body}` : body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a console argument from the description and preview that CDP sends
|
||||
* with it.
|
||||
*/
|
||||
export function formatRemoteObject(
|
||||
remoteObject: Protocol.Runtime.RemoteObject,
|
||||
): string {
|
||||
if (
|
||||
remoteObject.preview &&
|
||||
remoteObject.type === 'object' &&
|
||||
remoteObject.subtype !== 'node' &&
|
||||
remoteObject.subtype !== 'trustedtype'
|
||||
) {
|
||||
return formatObjectPreview(remoteObject.preview);
|
||||
}
|
||||
if (remoteObject.type === 'string') {
|
||||
return String(remoteObject.value ?? '');
|
||||
}
|
||||
if (remoteObject.unserializableValue !== undefined) {
|
||||
return remoteObject.unserializableValue;
|
||||
}
|
||||
if (remoteObject.description !== undefined) {
|
||||
return StringUtilities.trimEndWithMaxLength(
|
||||
remoteObject.description,
|
||||
MAX_DESCRIPTION_LENGTH,
|
||||
);
|
||||
}
|
||||
return String(remoteObject.value);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import assert from 'node:assert';
|
||||
import {describe, it} from 'node:test';
|
||||
|
||||
import {SymbolizedError} from '../../src/devtools/DevtoolsUtils.js';
|
||||
@@ -850,3 +851,98 @@ describe('ConsoleFormatter', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ConsoleFormatter arguments', () => {
|
||||
it('formats non-JSON arguments from their preview and keeps JSON for plain values', async () => {
|
||||
const message = createMockMessage({
|
||||
type: () => 'log',
|
||||
text: () => 'values',
|
||||
args: () => [
|
||||
{
|
||||
jsonValue: async () => ({}),
|
||||
remoteObject: () => ({
|
||||
type: 'object',
|
||||
subtype: 'map',
|
||||
description: 'Map(1)',
|
||||
preview: {
|
||||
type: 'object',
|
||||
subtype: 'map',
|
||||
description: 'Map(1)',
|
||||
overflow: false,
|
||||
properties: [{name: 'size', type: 'number', value: '1'}],
|
||||
entries: [
|
||||
{
|
||||
key: {
|
||||
type: 'number',
|
||||
description: '1',
|
||||
overflow: false,
|
||||
properties: [],
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: '2',
|
||||
overflow: false,
|
||||
properties: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
jsonValue: async () => {
|
||||
throw new Error('Object reference chain is too long');
|
||||
},
|
||||
remoteObject: () => ({
|
||||
type: 'object',
|
||||
description: 'Object',
|
||||
preview: {
|
||||
type: 'object',
|
||||
description: 'Object',
|
||||
overflow: false,
|
||||
properties: [
|
||||
{name: 'name', type: 'string', value: 'cyc'},
|
||||
{name: 'self', type: 'object', value: 'Object'},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
jsonValue: async () => ({a: 1}),
|
||||
remoteObject: () => ({type: 'object'}),
|
||||
},
|
||||
],
|
||||
});
|
||||
const formatter = await ConsoleFormatter.from(message, {
|
||||
id: 1,
|
||||
fetchDetailedData: true,
|
||||
});
|
||||
assert.deepStrictEqual(formatter.toJSONDetailed().args, [
|
||||
'Map(1) {1 => 2}',
|
||||
"{name: 'cyc', self: {…}}",
|
||||
'{"a":1}',
|
||||
]);
|
||||
});
|
||||
|
||||
it('reports an argument without a preview whose jsonValue() fails as unavailable', async () => {
|
||||
const message = createMockMessage({
|
||||
type: () => 'log',
|
||||
text: () => 'gone',
|
||||
args: () => [
|
||||
{
|
||||
jsonValue: async () => {
|
||||
throw new Error('Execution context was destroyed');
|
||||
},
|
||||
remoteObject: () => ({type: 'object', description: 'Object'}),
|
||||
},
|
||||
],
|
||||
});
|
||||
const formatter = await ConsoleFormatter.from(message, {
|
||||
id: 1,
|
||||
fetchDetailedData: true,
|
||||
});
|
||||
assert.deepStrictEqual(formatter.toJSONDetailed().args, [
|
||||
'<error: Argument 0 is no longer available>',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import assert from 'node:assert';
|
||||
import {describe, it} from 'node:test';
|
||||
|
||||
import {formatRemoteObject} from '../../src/formatters/RemoteObjectFormatter.js';
|
||||
import type {Protocol} from '../../src/third_party/index.js';
|
||||
|
||||
function preview(
|
||||
subtype: Protocol.Runtime.ObjectPreview['subtype'],
|
||||
description: string,
|
||||
properties: Protocol.Runtime.PropertyPreview[],
|
||||
{
|
||||
overflow = false,
|
||||
entries,
|
||||
}: {overflow?: boolean; entries?: Protocol.Runtime.EntryPreview[]} = {},
|
||||
): Protocol.Runtime.RemoteObject {
|
||||
return {
|
||||
type: 'object',
|
||||
subtype,
|
||||
description,
|
||||
preview: {
|
||||
type: 'object',
|
||||
subtype,
|
||||
description,
|
||||
overflow,
|
||||
properties,
|
||||
entries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('formatRemoteObject', () => {
|
||||
it('formats Map and Set entries', () => {
|
||||
const number = (value: string): Protocol.Runtime.ObjectPreview => ({
|
||||
type: 'number',
|
||||
description: value,
|
||||
overflow: false,
|
||||
properties: [],
|
||||
});
|
||||
const map = preview(
|
||||
'map',
|
||||
'Map(1)',
|
||||
[{name: 'size', type: 'number', value: '1'}],
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
key: number('1'),
|
||||
value: {
|
||||
type: 'object',
|
||||
description: 'Object',
|
||||
overflow: false,
|
||||
properties: [{name: 'a', type: 'number', value: '2'}],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
assert.strictEqual(formatRemoteObject(map), 'Map(1) {1 => {…}}');
|
||||
|
||||
const set = preview(
|
||||
'set',
|
||||
'Set(2)',
|
||||
[{name: 'size', type: 'number', value: '2'}],
|
||||
{
|
||||
entries: [
|
||||
{value: number('3')},
|
||||
{
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'x',
|
||||
overflow: false,
|
||||
properties: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
assert.strictEqual(formatRemoteObject(set), "Set(2) {3, 'x'}");
|
||||
});
|
||||
|
||||
it('formats objects without following cycles', () => {
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview(undefined, 'Object', [
|
||||
{name: 'name', type: 'string', value: 'cyc'},
|
||||
{name: 'self', type: 'object', value: 'Object'},
|
||||
{name: 'fn', type: 'function', value: ''},
|
||||
]),
|
||||
),
|
||||
"{name: 'cyc', self: {…}, fn: ƒ}",
|
||||
);
|
||||
});
|
||||
|
||||
it('formats arrays with holes and overflow', () => {
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview('array', 'Array(3)', [
|
||||
{name: '0', type: 'number', value: '1'},
|
||||
{name: '2', type: 'number', value: '3'},
|
||||
]),
|
||||
),
|
||||
'(3) [1, empty, 3]',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview(
|
||||
'array',
|
||||
'Array(120)',
|
||||
[
|
||||
{name: '0', type: 'number', value: '0'},
|
||||
{name: '1', type: 'number', value: '1'},
|
||||
],
|
||||
{overflow: true},
|
||||
),
|
||||
),
|
||||
'(120) [0, 1, …]',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview('typedarray', 'Uint8Array(2)', [
|
||||
{name: '0', type: 'number', value: '1'},
|
||||
{name: '1', type: 'number', value: '2'},
|
||||
]),
|
||||
),
|
||||
'Uint8Array(2) [1, 2]',
|
||||
);
|
||||
});
|
||||
|
||||
it('formats promise state', () => {
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview('promise', 'Promise', [
|
||||
{name: '[[PromiseState]]', type: 'string', value: 'fulfilled'},
|
||||
{name: '[[PromiseResult]]', type: 'number', value: '5'},
|
||||
]),
|
||||
),
|
||||
'Promise {<fulfilled>: 5}',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the description for values without a property preview', () => {
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(
|
||||
preview('date', 'Thu Jan 01 1970 00:00:00 GMT+0000', []),
|
||||
),
|
||||
'Thu Jan 01 1970 00:00:00 GMT+0000',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject(preview('node', 'body', [], {overflow: true})),
|
||||
'body',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject({
|
||||
type: 'function',
|
||||
className: 'Function',
|
||||
description: 'function foo(){ return 1; }',
|
||||
}),
|
||||
'function foo(){ return 1; }',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject({type: 'symbol', description: 'Symbol(s)'}),
|
||||
'Symbol(s)',
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatRemoteObject({
|
||||
type: 'bigint',
|
||||
unserializableValue: '10n',
|
||||
description: '10n',
|
||||
}),
|
||||
'10n',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user