From deb0a936362746add87e6e24da677b3ebbdb3cce Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 20:33:41 +0000
Subject: [PATCH 1/8] Initial plan
From ee7970e8a0898992cba76972040d98e8537cb814 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 20:50:12 +0000
Subject: [PATCH 2/8] Fix ObjectInspector to display Symbol-keyed properties
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-inspector.test.tsx | 71 +++++++++++++++++++
.../react/js-inspector/object-inspector.tsx | 27 +++++++
.../js-inspector/utils/property-utils.tsx | 2 +-
pnpm-workspace.yaml | 37 ++++++----
4 files changed, 124 insertions(+), 13 deletions(-)
create mode 100644 packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
new file mode 100644
index 00000000000..0d1499fb0d4
--- /dev/null
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -0,0 +1,71 @@
+import { render } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { ObjectInspector } from "./object-inspector.js";
+
+describe("ObjectInspector", () => {
+ describe("Symbol properties", () => {
+ it("should display Symbol-keyed properties", () => {
+ const sym = Symbol("testSymbol");
+ const data = {
+ stringProp: "value1",
+ [sym]: "symbolValue",
+ };
+
+ const { container } = render();
+
+ // Check that the Symbol property is displayed
+ expect(container.textContent).toContain("Symbol(testSymbol)");
+ expect(container.textContent).toContain("symbolValue");
+ });
+
+ it("should display Symbol-keyed properties without description", () => {
+ const sym = Symbol();
+ const data = {
+ [sym]: "symbolValue",
+ };
+
+ const { container } = render();
+
+ // Check that the Symbol property is displayed (even without description)
+ expect(container.textContent).toContain("Symbol()");
+ expect(container.textContent).toContain("symbolValue");
+ });
+
+ it("should display both string and Symbol properties", () => {
+ const sym1 = Symbol("first");
+ const sym2 = Symbol("second");
+ const data = {
+ stringProp1: "value1",
+ stringProp2: "value2",
+ [sym1]: "symbolValue1",
+ [sym2]: "symbolValue2",
+ };
+
+ const { container } = render();
+
+ // Check that all properties are displayed
+ expect(container.textContent).toContain("stringProp1");
+ expect(container.textContent).toContain("value1");
+ expect(container.textContent).toContain("stringProp2");
+ expect(container.textContent).toContain("value2");
+ expect(container.textContent).toContain("Symbol(first)");
+ expect(container.textContent).toContain("symbolValue1");
+ expect(container.textContent).toContain("Symbol(second)");
+ expect(container.textContent).toContain("symbolValue2");
+ });
+
+ it("should display Symbol properties with complex values", () => {
+ const sym = Symbol("complexSymbol");
+ const data = {
+ [sym]: { nested: "value", count: 42 },
+ };
+
+ const { container } = render();
+
+ // Check that the Symbol property is displayed
+ expect(container.textContent).toContain("Symbol(complexSymbol)");
+ // The complex object should be represented (exact format may vary)
+ expect(container.textContent).toContain("Object");
+ });
+ });
+});
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
index b5bf0f4af58..17c3f7649d7 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
@@ -69,6 +69,33 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
}
}
+ // Also iterate over Symbol keys
+ const symbolKeys = Object.getOwnPropertySymbols(data);
+ for (const symbolKey of symbolKeys) {
+ if (propertyIsEnumerable.call(data, symbolKey)) {
+ const propertyValue = getPropertyValue(data, symbolKey);
+ yield {
+ name: symbolKey.toString(),
+ data: propertyValue,
+ };
+ } else if (showNonenumerable) {
+ let propertyValue;
+ try {
+ propertyValue = getPropertyValue(data, symbolKey);
+ } catch (e) {
+ // console.warn(e)
+ }
+
+ if (propertyValue !== undefined) {
+ yield {
+ name: symbolKey.toString(),
+ data: propertyValue,
+ isNonenumerable: true,
+ };
+ }
+ }
+ }
+
// [[Prototype]] of the object: `Object.getPrototypeOf(data)`
// the property name is shown as "__proto__"
if (showNonenumerable && data !== Object.prototype /* already added */) {
diff --git a/packages/html-program-viewer/src/react/js-inspector/utils/property-utils.tsx b/packages/html-program-viewer/src/react/js-inspector/utils/property-utils.tsx
index 614e3975b2c..b87dc15df2a 100644
--- a/packages/html-program-viewer/src/react/js-inspector/utils/property-utils.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/utils/property-utils.tsx
@@ -1,4 +1,4 @@
-export function getPropertyValue(object: any, propertyName: string) {
+export function getPropertyValue(object: any, propertyName: string | symbol) {
const propertyDescriptor = Object.getOwnPropertyDescriptor(object, propertyName);
if (propertyDescriptor?.get) {
try {
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e161307424c..43c2a9278ab 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,16 +1,29 @@
packages:
- - "packages/*"
- - "e2e"
- - "website"
- - "!packages/http-client-csharp/**"
- - "!packages/http-client-java/**"
- - "!packages/http-client-python/**"
+ - packages/*
+ - e2e
+ - website
+ - '!packages/http-client-csharp/**'
+ - '!packages/http-client-java/**'
+ - '!packages/http-client-python/**'
-overrides:
- "cross-spawn@>=7.0.0 <7.0.5": "^7.0.5"
- rollup: 4.49.0 # Regression in 4.50.0 https://github.com/rollup/rollup/issues/6099
+minimumReleaseAge: 2880
-# Minimum age (in minutes) for a new dependency version to be able to be used.
-minimumReleaseAge: 2880 # 2 days
minimumReleaseAgeExclude:
- - "@alloy-js/*"
+ - '@alloy-js/*'
+
+onlyBuiltDependencies:
+ - '@playwright/browser-chromium'
+ - '@scarf/scarf'
+ - '@vscode/vsce-sign'
+ - esbuild
+ - keytar
+ - sharp
+ - tree-sitter-c-sharp
+ - tree-sitter-java
+ - tree-sitter-javascript
+ - tree-sitter-python
+ - tree-sitter-typescript
+
+overrides:
+ cross-spawn@>=7.0.0 <7.0.5: ^7.0.5
+ rollup: 4.49.0
From 9386116537cb0c52aef76713f01754bcee25a831 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 20:55:47 +0000
Subject: [PATCH 3/8] Simplify ObjectInspector tests for Symbol properties
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-inspector.test.tsx | 41 +------------------
1 file changed, 2 insertions(+), 39 deletions(-)
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
index 0d1499fb0d4..bd25fe398c7 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -11,7 +11,7 @@ describe("ObjectInspector", () => {
[sym]: "symbolValue",
};
- const { container } = render();
+ const { container } = render();
// Check that the Symbol property is displayed
expect(container.textContent).toContain("Symbol(testSymbol)");
@@ -24,48 +24,11 @@ describe("ObjectInspector", () => {
[sym]: "symbolValue",
};
- const { container } = render();
+ const { container } = render();
// Check that the Symbol property is displayed (even without description)
expect(container.textContent).toContain("Symbol()");
expect(container.textContent).toContain("symbolValue");
});
-
- it("should display both string and Symbol properties", () => {
- const sym1 = Symbol("first");
- const sym2 = Symbol("second");
- const data = {
- stringProp1: "value1",
- stringProp2: "value2",
- [sym1]: "symbolValue1",
- [sym2]: "symbolValue2",
- };
-
- const { container } = render();
-
- // Check that all properties are displayed
- expect(container.textContent).toContain("stringProp1");
- expect(container.textContent).toContain("value1");
- expect(container.textContent).toContain("stringProp2");
- expect(container.textContent).toContain("value2");
- expect(container.textContent).toContain("Symbol(first)");
- expect(container.textContent).toContain("symbolValue1");
- expect(container.textContent).toContain("Symbol(second)");
- expect(container.textContent).toContain("symbolValue2");
- });
-
- it("should display Symbol properties with complex values", () => {
- const sym = Symbol("complexSymbol");
- const data = {
- [sym]: { nested: "value", count: 42 },
- };
-
- const { container } = render();
-
- // Check that the Symbol property is displayed
- expect(container.textContent).toContain("Symbol(complexSymbol)");
- // The complex object should be represented (exact format may vary)
- expect(container.textContent).toContain("Object");
- });
});
});
From 1329be4deb01afeb2c6ae6ca142121ce5d60a7a0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 20:58:28 +0000
Subject: [PATCH 4/8] Refactor to reduce code duplication and improve test
coverage
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-inspector.test.tsx | 55 +++++++++++
.../react/js-inspector/object-inspector.tsx | 92 ++++++++-----------
pnpm-workspace.yaml | 37 +++-----
3 files changed, 107 insertions(+), 77 deletions(-)
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
index bd25fe398c7..482258c2f0c 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -30,5 +30,60 @@ describe("ObjectInspector", () => {
expect(container.textContent).toContain("Symbol()");
expect(container.textContent).toContain("symbolValue");
});
+
+ it("should display both string and Symbol properties", () => {
+ const sym1 = Symbol("first");
+ const sym2 = Symbol("second");
+ const data = {
+ stringProp1: "value1",
+ stringProp2: "value2",
+ [sym1]: "symbolValue1",
+ [sym2]: "symbolValue2",
+ };
+
+ const { container } = render();
+
+ // Check that all properties are displayed
+ expect(container.textContent).toContain("stringProp1");
+ expect(container.textContent).toContain("value1");
+ expect(container.textContent).toContain("stringProp2");
+ expect(container.textContent).toContain("value2");
+ expect(container.textContent).toContain("Symbol(first)");
+ expect(container.textContent).toContain("symbolValue1");
+ expect(container.textContent).toContain("Symbol(second)");
+ expect(container.textContent).toContain("symbolValue2");
+ });
+
+ it("should display non-enumerable Symbol properties when showNonenumerable is true", () => {
+ const sym = Symbol("nonEnumSymbol");
+ const data = {};
+ // Define a non-enumerable Symbol property
+ Object.defineProperty(data, sym, {
+ value: "nonEnumValue",
+ enumerable: false,
+ });
+
+ const { container } = render();
+
+ // Check that the non-enumerable Symbol property is displayed
+ expect(container.textContent).toContain("Symbol(nonEnumSymbol)");
+ expect(container.textContent).toContain("nonEnumValue");
+ });
+
+ it("should not display non-enumerable Symbol properties when showNonenumerable is false", () => {
+ const sym = Symbol("nonEnumSymbol");
+ const data = {};
+ // Define a non-enumerable Symbol property
+ Object.defineProperty(data, sym, {
+ value: "nonEnumValue",
+ enumerable: false,
+ });
+
+ const { container } = render();
+
+ // Check that the non-enumerable Symbol property is NOT displayed
+ expect(container.textContent).not.toContain("Symbol(nonEnumSymbol)");
+ expect(container.textContent).not.toContain("nonEnumValue");
+ });
});
});
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
index 17c3f7649d7..5ef46f50613 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
@@ -8,6 +8,42 @@ import { propertyIsEnumerable } from "./utils/object-prototype.js";
import { getPropertyValue } from "./utils/property-utils.js";
const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) => {
+ // Helper function to handle property iteration for both string and Symbol keys
+ function* iterateProperties(
+ data: any,
+ keys: (string | symbol)[],
+ formatKey: (key: string | symbol) => string = (k) =>
+ typeof k === "string" ? k || `""` : k.toString(),
+ ) {
+ for (const key of keys) {
+ if (propertyIsEnumerable.call(data, key)) {
+ const propertyValue = getPropertyValue(data, key);
+ yield {
+ name: formatKey(key),
+ data: propertyValue,
+ };
+ } else if (showNonenumerable) {
+ // To work around the error (happens some time when propertyName === 'caller' || propertyName === 'arguments')
+ // 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
+ // http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access
+ let propertyValue;
+ try {
+ propertyValue = getPropertyValue(data, key);
+ } catch (e) {
+ // console.warn(e)
+ }
+
+ if (propertyValue !== undefined) {
+ yield {
+ name: formatKey(key),
+ data: propertyValue,
+ isNonenumerable: true,
+ };
+ }
+ }
+ }
+ }
+
const objectIterator = function* (data: any) {
const shouldIterate = (typeof data === "object" && data !== null) || typeof data === "function";
if (!shouldIterate) return;
@@ -33,6 +69,7 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
i++;
}
} else {
+ // String property names
const keys = Object.getOwnPropertyNames(data);
if (sortObjectKeys === true && !dataIsArray) {
// Array keys should not be sorted in alphabetical order
@@ -41,60 +78,11 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
keys.sort(sortObjectKeys);
}
- for (const propertyName of keys) {
- if (propertyIsEnumerable.call(data, propertyName)) {
- const propertyValue = getPropertyValue(data, propertyName);
- yield {
- name: propertyName || `""`,
- data: propertyValue,
- };
- } else if (showNonenumerable) {
- // To work around the error (happens some time when propertyName === 'caller' || propertyName === 'arguments')
- // 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
- // http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access
- let propertyValue;
- try {
- propertyValue = getPropertyValue(data, propertyName);
- } catch (e) {
- // console.warn(e)
- }
-
- if (propertyValue !== undefined) {
- yield {
- name: propertyName,
- data: propertyValue,
- isNonenumerable: true,
- };
- }
- }
- }
+ yield* iterateProperties(data, keys);
- // Also iterate over Symbol keys
+ // Symbol property names
const symbolKeys = Object.getOwnPropertySymbols(data);
- for (const symbolKey of symbolKeys) {
- if (propertyIsEnumerable.call(data, symbolKey)) {
- const propertyValue = getPropertyValue(data, symbolKey);
- yield {
- name: symbolKey.toString(),
- data: propertyValue,
- };
- } else if (showNonenumerable) {
- let propertyValue;
- try {
- propertyValue = getPropertyValue(data, symbolKey);
- } catch (e) {
- // console.warn(e)
- }
-
- if (propertyValue !== undefined) {
- yield {
- name: symbolKey.toString(),
- data: propertyValue,
- isNonenumerable: true,
- };
- }
- }
- }
+ yield* iterateProperties(data, symbolKeys);
// [[Prototype]] of the object: `Object.getPrototypeOf(data)`
// the property name is shown as "__proto__"
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 43c2a9278ab..e161307424c 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,29 +1,16 @@
packages:
- - packages/*
- - e2e
- - website
- - '!packages/http-client-csharp/**'
- - '!packages/http-client-java/**'
- - '!packages/http-client-python/**'
+ - "packages/*"
+ - "e2e"
+ - "website"
+ - "!packages/http-client-csharp/**"
+ - "!packages/http-client-java/**"
+ - "!packages/http-client-python/**"
-minimumReleaseAge: 2880
+overrides:
+ "cross-spawn@>=7.0.0 <7.0.5": "^7.0.5"
+ rollup: 4.49.0 # Regression in 4.50.0 https://github.com/rollup/rollup/issues/6099
+# Minimum age (in minutes) for a new dependency version to be able to be used.
+minimumReleaseAge: 2880 # 2 days
minimumReleaseAgeExclude:
- - '@alloy-js/*'
-
-onlyBuiltDependencies:
- - '@playwright/browser-chromium'
- - '@scarf/scarf'
- - '@vscode/vsce-sign'
- - esbuild
- - keytar
- - sharp
- - tree-sitter-c-sharp
- - tree-sitter-java
- - tree-sitter-javascript
- - tree-sitter-python
- - tree-sitter-typescript
-
-overrides:
- cross-spawn@>=7.0.0 <7.0.5: ^7.0.5
- rollup: 4.49.0
+ - "@alloy-js/*"
From d72b5d94d76b1a1f8e00a19cfb96c8331463af7e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 21:02:01 +0000
Subject: [PATCH 5/8] Add Symbol key sorting support for consistency
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../src/react/js-inspector/object-inspector.tsx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
index 5ef46f50613..02e90eef52a 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
@@ -82,6 +82,10 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
// Symbol property names
const symbolKeys = Object.getOwnPropertySymbols(data);
+ if (sortObjectKeys && !dataIsArray) {
+ // Sort symbols by their description for consistent display
+ symbolKeys.sort((a, b) => a.toString().localeCompare(b.toString()));
+ }
yield* iterateProperties(data, symbolKeys);
// [[Prototype]] of the object: `Object.getPrototypeOf(data)`
From 3f442312bd7783b43c58793641bed2941cdab71e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 21:14:03 +0000
Subject: [PATCH 6/8] Address PR feedback: simplify code and remove nested
describe
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-inspector.test.tsx | 138 +++++++++---------
.../react/js-inspector/object-inspector.tsx | 87 +++++------
2 files changed, 104 insertions(+), 121 deletions(-)
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
index 482258c2f0c..ad9bfbae7f0 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -1,89 +1,85 @@
import { render } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
+import { expect, it } from "vitest";
import { ObjectInspector } from "./object-inspector.js";
-describe("ObjectInspector", () => {
- describe("Symbol properties", () => {
- it("should display Symbol-keyed properties", () => {
- const sym = Symbol("testSymbol");
- const data = {
- stringProp: "value1",
- [sym]: "symbolValue",
- };
+it("should display Symbol-keyed properties", () => {
+ const sym = Symbol("testSymbol");
+ const data = {
+ stringProp: "value1",
+ [sym]: "symbolValue",
+ };
- const { container } = render();
+ const { container } = render();
- // Check that the Symbol property is displayed
- expect(container.textContent).toContain("Symbol(testSymbol)");
- expect(container.textContent).toContain("symbolValue");
- });
+ // Check that the Symbol property is displayed
+ expect(container.textContent).toContain("Symbol(testSymbol)");
+ expect(container.textContent).toContain("symbolValue");
+});
- it("should display Symbol-keyed properties without description", () => {
- const sym = Symbol();
- const data = {
- [sym]: "symbolValue",
- };
+it("should display Symbol-keyed properties without description", () => {
+ const sym = Symbol();
+ const data = {
+ [sym]: "symbolValue",
+ };
- const { container } = render();
+ const { container } = render();
- // Check that the Symbol property is displayed (even without description)
- expect(container.textContent).toContain("Symbol()");
- expect(container.textContent).toContain("symbolValue");
- });
+ // Check that the Symbol property is displayed (even without description)
+ expect(container.textContent).toContain("Symbol()");
+ expect(container.textContent).toContain("symbolValue");
+});
- it("should display both string and Symbol properties", () => {
- const sym1 = Symbol("first");
- const sym2 = Symbol("second");
- const data = {
- stringProp1: "value1",
- stringProp2: "value2",
- [sym1]: "symbolValue1",
- [sym2]: "symbolValue2",
- };
+it("should display both string and Symbol properties", () => {
+ const sym1 = Symbol("first");
+ const sym2 = Symbol("second");
+ const data = {
+ stringProp1: "value1",
+ stringProp2: "value2",
+ [sym1]: "symbolValue1",
+ [sym2]: "symbolValue2",
+ };
- const { container } = render();
+ const { container } = render();
- // Check that all properties are displayed
- expect(container.textContent).toContain("stringProp1");
- expect(container.textContent).toContain("value1");
- expect(container.textContent).toContain("stringProp2");
- expect(container.textContent).toContain("value2");
- expect(container.textContent).toContain("Symbol(first)");
- expect(container.textContent).toContain("symbolValue1");
- expect(container.textContent).toContain("Symbol(second)");
- expect(container.textContent).toContain("symbolValue2");
- });
+ // Check that all properties are displayed
+ expect(container.textContent).toContain("stringProp1");
+ expect(container.textContent).toContain("value1");
+ expect(container.textContent).toContain("stringProp2");
+ expect(container.textContent).toContain("value2");
+ expect(container.textContent).toContain("Symbol(first)");
+ expect(container.textContent).toContain("symbolValue1");
+ expect(container.textContent).toContain("Symbol(second)");
+ expect(container.textContent).toContain("symbolValue2");
+});
- it("should display non-enumerable Symbol properties when showNonenumerable is true", () => {
- const sym = Symbol("nonEnumSymbol");
- const data = {};
- // Define a non-enumerable Symbol property
- Object.defineProperty(data, sym, {
- value: "nonEnumValue",
- enumerable: false,
- });
+it("should display non-enumerable Symbol properties when showNonenumerable is true", () => {
+ const sym = Symbol("nonEnumSymbol");
+ const data = {};
+ // Define a non-enumerable Symbol property
+ Object.defineProperty(data, sym, {
+ value: "nonEnumValue",
+ enumerable: false,
+ });
- const { container } = render();
+ const { container } = render();
- // Check that the non-enumerable Symbol property is displayed
- expect(container.textContent).toContain("Symbol(nonEnumSymbol)");
- expect(container.textContent).toContain("nonEnumValue");
- });
+ // Check that the non-enumerable Symbol property is displayed
+ expect(container.textContent).toContain("Symbol(nonEnumSymbol)");
+ expect(container.textContent).toContain("nonEnumValue");
+});
- it("should not display non-enumerable Symbol properties when showNonenumerable is false", () => {
- const sym = Symbol("nonEnumSymbol");
- const data = {};
- // Define a non-enumerable Symbol property
- Object.defineProperty(data, sym, {
- value: "nonEnumValue",
- enumerable: false,
- });
+it("should not display non-enumerable Symbol properties when showNonenumerable is false", () => {
+ const sym = Symbol("nonEnumSymbol");
+ const data = {};
+ // Define a non-enumerable Symbol property
+ Object.defineProperty(data, sym, {
+ value: "nonEnumValue",
+ enumerable: false,
+ });
- const { container } = render();
+ const { container } = render();
- // Check that the non-enumerable Symbol property is NOT displayed
- expect(container.textContent).not.toContain("Symbol(nonEnumSymbol)");
- expect(container.textContent).not.toContain("nonEnumValue");
- });
- });
+ // Check that the non-enumerable Symbol property is NOT displayed
+ expect(container.textContent).not.toContain("Symbol(nonEnumSymbol)");
+ expect(container.textContent).not.toContain("nonEnumValue");
});
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
index 02e90eef52a..e817cca5903 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
@@ -8,42 +8,6 @@ import { propertyIsEnumerable } from "./utils/object-prototype.js";
import { getPropertyValue } from "./utils/property-utils.js";
const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) => {
- // Helper function to handle property iteration for both string and Symbol keys
- function* iterateProperties(
- data: any,
- keys: (string | symbol)[],
- formatKey: (key: string | symbol) => string = (k) =>
- typeof k === "string" ? k || `""` : k.toString(),
- ) {
- for (const key of keys) {
- if (propertyIsEnumerable.call(data, key)) {
- const propertyValue = getPropertyValue(data, key);
- yield {
- name: formatKey(key),
- data: propertyValue,
- };
- } else if (showNonenumerable) {
- // To work around the error (happens some time when propertyName === 'caller' || propertyName === 'arguments')
- // 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
- // http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access
- let propertyValue;
- try {
- propertyValue = getPropertyValue(data, key);
- } catch (e) {
- // console.warn(e)
- }
-
- if (propertyValue !== undefined) {
- yield {
- name: formatKey(key),
- data: propertyValue,
- isNonenumerable: true,
- };
- }
- }
- }
- }
-
const objectIterator = function* (data: any) {
const shouldIterate = (typeof data === "object" && data !== null) || typeof data === "function";
if (!shouldIterate) return;
@@ -69,24 +33,47 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
i++;
}
} else {
- // String property names
- const keys = Object.getOwnPropertyNames(data);
- if (sortObjectKeys === true && !dataIsArray) {
+ // Get all property keys (both string and Symbol)
+ const stringKeys = Object.getOwnPropertyNames(data);
+ const symbolKeys = Object.getOwnPropertySymbols(data);
+ const allKeys: (string | symbol)[] = [...stringKeys, ...symbolKeys];
+
+ if (sortObjectKeys && !dataIsArray) {
// Array keys should not be sorted in alphabetical order
- keys.sort();
- } else if (typeof sortObjectKeys === "function") {
- keys.sort(sortObjectKeys);
+ allKeys.sort((a, b) => {
+ const aStr = typeof a === "string" ? a : a.toString();
+ const bStr = typeof b === "string" ? b : b.toString();
+ return aStr.localeCompare(bStr);
+ });
}
- yield* iterateProperties(data, keys);
-
- // Symbol property names
- const symbolKeys = Object.getOwnPropertySymbols(data);
- if (sortObjectKeys && !dataIsArray) {
- // Sort symbols by their description for consistent display
- symbolKeys.sort((a, b) => a.toString().localeCompare(b.toString()));
+ for (const key of allKeys) {
+ if (propertyIsEnumerable.call(data, key)) {
+ const propertyValue = getPropertyValue(data, key);
+ yield {
+ name: typeof key === "string" ? key || `""` : key.toString(),
+ data: propertyValue,
+ };
+ } else if (showNonenumerable) {
+ // To work around the error (happens some time when propertyName === 'caller' || propertyName === 'arguments')
+ // 'caller' and 'arguments' are restricted function properties and cannot be accessed in this context
+ // http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access
+ let propertyValue;
+ try {
+ propertyValue = getPropertyValue(data, key);
+ } catch (e) {
+ // console.warn(e)
+ }
+
+ if (propertyValue !== undefined) {
+ yield {
+ name: typeof key === "string" ? key : key.toString(),
+ data: propertyValue,
+ isNonenumerable: true,
+ };
+ }
+ }
}
- yield* iterateProperties(data, symbolKeys);
// [[Prototype]] of the object: `Object.getPrototypeOf(data)`
// the property name is shown as "__proto__"
From 74c0456c32aa2951697ccdcaf0ce9eb04affd440 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 21:17:17 +0000
Subject: [PATCH 7/8] Add Symbol sorting test and fix empty string key handling
consistency
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-inspector.test.tsx | 25 +++++++++++++++++++
.../react/js-inspector/object-inspector.tsx | 2 +-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
index ad9bfbae7f0..6c7342130e0 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -83,3 +83,28 @@ it("should not display non-enumerable Symbol properties when showNonenumerable i
expect(container.textContent).not.toContain("Symbol(nonEnumSymbol)");
expect(container.textContent).not.toContain("nonEnumValue");
});
+
+it("should sort Symbol properties when sortObjectKeys is true", () => {
+ const sym1 = Symbol("zebra");
+ const sym2 = Symbol("apple");
+ const sym3 = Symbol("middle");
+ const data = {
+ [sym1]: "value1",
+ [sym2]: "value2",
+ [sym3]: "value3",
+ };
+
+ const { container } = render();
+
+ const text = container.textContent || "";
+ const appleIndex = text.indexOf("Symbol(apple)");
+ const middleIndex = text.indexOf("Symbol(middle)");
+ const zebraIndex = text.indexOf("Symbol(zebra)");
+
+ // Symbols should be sorted alphabetically by their description
+ expect(appleIndex).toBeGreaterThan(-1);
+ expect(middleIndex).toBeGreaterThan(-1);
+ expect(zebraIndex).toBeGreaterThan(-1);
+ expect(appleIndex).toBeLessThan(middleIndex);
+ expect(middleIndex).toBeLessThan(zebraIndex);
+});
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
index e817cca5903..e0b4817fea9 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.tsx
@@ -67,7 +67,7 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
if (propertyValue !== undefined) {
yield {
- name: typeof key === "string" ? key : key.toString(),
+ name: typeof key === "string" ? key || `""` : key.toString(),
data: propertyValue,
isNonenumerable: true,
};
From c64db109e256d0e1110477f92ee64b60d66768cc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 23:04:30 +0000
Subject: [PATCH 8/8] Fix Symbol properties to show in collapsed object preview
Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com>
---
.../js-inspector/object-preview.test.tsx | 49 +++++++++++++++++++
.../src/react/js-inspector/object-preview.tsx | 21 +++++---
2 files changed, 64 insertions(+), 6 deletions(-)
create mode 100644 packages/html-program-viewer/src/react/js-inspector/object-preview.test.tsx
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-preview.test.tsx b/packages/html-program-viewer/src/react/js-inspector/object-preview.test.tsx
new file mode 100644
index 00000000000..d270a83dd29
--- /dev/null
+++ b/packages/html-program-viewer/src/react/js-inspector/object-preview.test.tsx
@@ -0,0 +1,49 @@
+import { render } from "@testing-library/react";
+import { expect, it } from "vitest";
+import { ObjectPreview } from "./object-preview.js";
+
+it("should display Symbol properties in object preview", () => {
+ const sym = Symbol("testSymbol");
+ const data = {
+ stringProp: "value1",
+ [sym]: "symbolValue",
+ };
+
+ const { container } = render();
+
+ // Check that both string and Symbol properties are displayed in preview
+ expect(container.textContent).toContain("stringProp");
+ expect(container.textContent).toContain("value1");
+ expect(container.textContent).toContain("Symbol(testSymbol)");
+ expect(container.textContent).toContain("symbolValue");
+});
+
+it("should display Symbol properties without description in preview", () => {
+ const sym = Symbol();
+ const data = {
+ [sym]: "value",
+ };
+
+ const { container } = render();
+
+ expect(container.textContent).toContain("Symbol()");
+ expect(container.textContent).toContain("value");
+});
+
+it("should respect max properties limit including Symbol properties", () => {
+ const sym1 = Symbol("first");
+ const sym2 = Symbol("second");
+ const data = {
+ prop1: "val1",
+ prop2: "val2",
+ prop3: "val3",
+ prop4: "val4",
+ [sym1]: "symVal1",
+ [sym2]: "symVal2",
+ };
+
+ const { container } = render();
+
+ // Should show ellipsis when exceeding max properties (5)
+ expect(container.textContent).toContain("…");
+});
diff --git a/packages/html-program-viewer/src/react/js-inspector/object-preview.tsx b/packages/html-program-viewer/src/react/js-inspector/object-preview.tsx
index f5e0a98b6fb..98c81d6b844 100644
--- a/packages/html-program-viewer/src/react/js-inspector/object-preview.tsx
+++ b/packages/html-program-viewer/src/react/js-inspector/object-preview.tsx
@@ -54,20 +54,29 @@ export const ObjectPreview: FC = ({ data }) => {
} else {
const maxProperties = OBJECT_MAX_PROPERTIES;
const propertyNodes: ReactNode[] = [];
- for (const propertyName in object) {
- if (hasOwnProperty.call(object, propertyName)) {
+
+ // Get all property keys (both string and Symbol)
+ const stringKeys = Object.getOwnPropertyNames(object);
+ const symbolKeys = Object.getOwnPropertySymbols(object);
+ const allKeys: (string | symbol)[] = [...stringKeys, ...symbolKeys];
+ const totalProperties = allKeys.length;
+
+ for (let i = 0; i < allKeys.length; i++) {
+ const key = allKeys[i];
+ if (hasOwnProperty.call(object, key)) {
let ellipsis;
if (
propertyNodes.length === maxProperties - 1 &&
- Object.keys(object).length > maxProperties
+ totalProperties > maxProperties
) {
ellipsis = …;
}
- const propertyValue = getPropertyValue(object, propertyName);
+ const propertyValue = getPropertyValue(object, key);
+ const displayName = typeof key === "string" ? key || `""` : key.toString();
propertyNodes.push(
-
-
+
+
:
{ellipsis}