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..6c7342130e0
--- /dev/null
+++ b/packages/html-program-viewer/src/react/js-inspector/object-inspector.test.tsx
@@ -0,0 +1,110 @@
+import { render } from "@testing-library/react";
+import { expect, it } from "vitest";
+import { ObjectInspector } from "./object-inspector.js";
+
+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 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");
+});
+
+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 b5bf0f4af58..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
@@ -33,19 +33,25 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
i++;
}
} else {
- 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);
+ });
}
- for (const propertyName of keys) {
- if (propertyIsEnumerable.call(data, propertyName)) {
- const propertyValue = getPropertyValue(data, propertyName);
+ for (const key of allKeys) {
+ if (propertyIsEnumerable.call(data, key)) {
+ const propertyValue = getPropertyValue(data, key);
yield {
- name: propertyName || `""`,
+ name: typeof key === "string" ? key || `""` : key.toString(),
data: propertyValue,
};
} else if (showNonenumerable) {
@@ -54,14 +60,14 @@ const createIterator = (showNonenumerable?: boolean, sortObjectKeys?: boolean) =
// http://stackoverflow.com/questions/31921189/caller-and-arguments-are-restricted-function-properties-and-cannot-be-access
let propertyValue;
try {
- propertyValue = getPropertyValue(data, propertyName);
+ propertyValue = getPropertyValue(data, key);
} catch (e) {
// console.warn(e)
}
if (propertyValue !== undefined) {
yield {
- name: propertyName,
+ name: typeof key === "string" ? key || `""` : key.toString(),
data: propertyValue,
isNonenumerable: true,
};
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}
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 {