From 592dacad39fa0b7146b33353aeca0c24606ac959 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 09:50:47 -0700 Subject: [PATCH 01/60] Start adding changes to address json fields --- client/src/components/DynamicJsonForm.tsx | 54 +++++++++++++++++++---- client/src/components/JsonEditor.tsx | 42 +++++++++++++++--- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index a15b57e..709b0d4 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -41,9 +41,6 @@ const DynamicJsonForm = ({ onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const [isJsonMode, setIsJsonMode] = useState(false); - const [jsonError, setJsonError] = useState(); - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => { switch (propSchema.type) { case "string": @@ -69,6 +66,40 @@ const DynamicJsonForm = ({ } }; + const [isJsonMode, setIsJsonMode] = useState(false); + const [jsonError, setJsonError] = useState(); + // Add state for storing raw JSON value + const [rawJsonValue, setRawJsonValue] = useState( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2) + ); + + const validateJsonBeforeSubmit = () => { + if (isJsonMode && rawJsonValue) { + try { + const parsed = JSON.parse(rawJsonValue); + onChange(parsed); + setJsonError(undefined); + return true; + } catch (err) { + setJsonError(err instanceof Error ? err.message : "Invalid JSON"); + return false; + } + } + return true; + }; + + const handleSwitchToFormMode = () => { + if (isJsonMode) { + if (validateJsonBeforeSubmit()) { + setIsJsonMode(false); + } + } else { + // Update raw JSON value when switching to JSON mode + setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setIsJsonMode(true); + } + }; + const renderFormFields = ( propSchema: JsonSchemaType, currentValue: JsonValue, @@ -329,7 +360,7 @@ const DynamicJsonForm = ({ @@ -337,13 +368,18 @@ const DynamicJsonForm = ({ {isJsonMode ? ( { + setRawJsonValue(newValue); try { - onChange(JSON.parse(newValue)); - setJsonError(undefined); - } catch (err) { - setJsonError(err instanceof Error ? err.message : "Invalid JSON"); + if (/^\s*[{[].*[}\]]\s*$/.test(newValue)) { + const parsed = JSON.parse(newValue); + onChange(parsed); + setJsonError(undefined); + } + } catch { + // Don't set an error during typing - that will happen when the user + // tries to save or submit the form } }} error={jsonError} diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 2fb7e26..9109088 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -1,3 +1,4 @@ +import { useState, useEffect } from "react"; import Editor from "react-simple-code-editor"; import Prism from "prismjs"; import "prismjs/components/prism-json"; @@ -10,34 +11,63 @@ interface JsonEditorProps { error?: string; } -const JsonEditor = ({ value, onChange, error }: JsonEditorProps) => { +const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) => { + const [editorContent, setEditorContent] = useState(value); + const [internalError, setInternalError] = useState(undefined); + + useEffect(() => { + setEditorContent(value); + }, [value]); + const formatJson = (json: string): string => { try { + // Handle empty arrays and objects specifically + if (json.trim() === '[]') return '[]'; + if (json.trim() === '{}') return '{}'; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; } }; + const handleEditorChange = (newContent: string) => { + setEditorContent(newContent); + setInternalError(undefined); + onChange(newContent); + }; + + const handleFormatJson = () => { + try { + const formatted = formatJson(editorContent); + setEditorContent(formatted); + onChange(formatted); + setInternalError(undefined); + } catch (err) { + setInternalError(err instanceof Error ? err.message : "Invalid JSON"); + } + }; + + const displayError = internalError || externalError; + return (
Prism.highlight(code, Prism.languages.json, "json") } @@ -51,7 +81,7 @@ const JsonEditor = ({ value, onChange, error }: JsonEditorProps) => { className="w-full" />
- {error &&

{error}

} + {displayError &&

{displayError}

}
); }; From 0e29e2c1cf2884df1c7c33f1bb76e8da9e1a332e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 19:34:33 -0700 Subject: [PATCH 02/60] Resolve issues where JSON fields are not being rendered in form mode --- client/src/components/DynamicJsonForm.tsx | 107 +++++++++++++++------- 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 709b0d4..d76b151 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -73,25 +73,24 @@ const DynamicJsonForm = ({ JSON.stringify(value ?? generateDefaultValue(schema), null, 2) ); - const validateJsonBeforeSubmit = () => { - if (isJsonMode && rawJsonValue) { - try { - const parsed = JSON.parse(rawJsonValue); - onChange(parsed); - setJsonError(undefined); - return true; - } catch (err) { - setJsonError(err instanceof Error ? err.message : "Invalid JSON"); - return false; - } + // Update rawJsonValue when value prop changes + useEffect(() => { + if (!isJsonMode) { + setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); } - return true; - }; + }, [value, schema, isJsonMode]); const handleSwitchToFormMode = () => { if (isJsonMode) { - if (validateJsonBeforeSubmit()) { + // When switching to Form mode, ensure we have valid JSON + try { + const parsed = JSON.parse(rawJsonValue); + // Update the parent component's state with the parsed value + onChange(parsed); + // Switch to form mode setIsJsonMode(false); + } catch (err) { + setJsonError(err instanceof Error ? err.message : "Invalid JSON"); } } else { // Update raw JSON value when switching to JSON mode @@ -160,23 +159,50 @@ const DynamicJsonForm = ({ className="w-4 h-4" /> ); - case "object": - if (!propSchema.properties) return null; - return ( -
- {Object.entries(propSchema.properties).map(([key, prop]) => ( -
- - {renderFormFields( - prop, - (currentValue as JsonObject)?.[key], - [...path, key], - depth + 1, - )} -
- ))} -
- ); + case "object": { + // Handle case where we have a value but no schema properties + const objectValue = currentValue as JsonObject || {}; + + // If we have schema properties, use them to render fields + if (propSchema.properties) { + return ( +
+ {Object.entries(propSchema.properties).map(([key, prop]) => ( +
+ + {renderFormFields( + prop, + objectValue[key], + [...path, key], + depth + 1, + )} +
+ ))} +
+ ); + } + // If we have a value but no schema properties, render fields based on the value + else if (Object.keys(objectValue).length > 0) { + return ( +
+ {Object.entries(objectValue).map(([key, value]) => ( +
+ + + handleFieldChange([...path, key], e.target.value) + } + /> +
+ ))} +
+ ); + } + // If we have neither schema properties nor value, return null + return null; + } case "array": { const arrayValue = Array.isArray(currentValue) ? currentValue : []; if (!propSchema.items) return null; @@ -385,7 +411,22 @@ const DynamicJsonForm = ({ error={jsonError} /> ) : ( - renderFormFields(schema, value) + // If schema type is object but value is not an object or is empty, and we have actual JSON data, + // render a simple representation of the JSON data + schema.type === "object" && + (typeof value !== "object" || value === null || Object.keys(value).length === 0) && + rawJsonValue && + rawJsonValue !== "{}" ? ( +
+

Form view not available for this JSON structure. Using simplified view:

+
+              {rawJsonValue}
+            
+

Use JSON mode for full editing capabilities.

+
+ ) : ( + renderFormFields(schema, value) + ) )} ); From 0b105b29c1678c36a0a61f77559ccfe5280c741c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 26 Feb 2025 19:55:01 -0700 Subject: [PATCH 03/60] Extract functions --- client/src/components/DynamicJsonForm.tsx | 134 +------------------ client/src/utils/jsonPathUtils.ts | 152 ++++++++++++++++++++++ client/src/utils/schemaUtils.ts | 72 ++++++++++ 3 files changed, 227 insertions(+), 131 deletions(-) create mode 100644 client/src/utils/jsonPathUtils.ts create mode 100644 client/src/utils/schemaUtils.ts diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index d76b151..4f986a8 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -3,6 +3,8 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import JsonEditor from "./JsonEditor"; +import { updateValueAtPath, JsonObject } from "@/utils/jsonPathUtils"; +import { generateDefaultValue, formatFieldLabel } from "@/utils/schemaUtils"; export type JsonValue = | string @@ -19,8 +21,6 @@ export type JsonSchemaType = { items?: JsonSchemaType; }; -type JsonObject = { [key: string]: JsonValue }; - interface DynamicJsonFormProps { schema: JsonSchemaType; value: JsonValue; @@ -28,12 +28,6 @@ interface DynamicJsonFormProps { maxDepth?: number; } -const formatFieldLabel = (key: string): string => { - return key - .replace(/([A-Z])/g, " $1") // Insert space before capital letters - .replace(/_/g, " ") // Replace underscores with spaces - .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter -}; const DynamicJsonForm = ({ schema, @@ -41,30 +35,6 @@ const DynamicJsonForm = ({ onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const generateDefaultValue = (propSchema: JsonSchemaType): JsonValue => { - switch (propSchema.type) { - case "string": - return ""; - case "number": - case "integer": - return 0; - case "boolean": - return false; - case "array": - return []; - case "object": { - const obj: JsonObject = {}; - if (propSchema.properties) { - Object.entries(propSchema.properties).forEach(([key, prop]) => { - obj[key] = generateDefaultValue(prop); - }); - } - return obj; - } - default: - return null; - } - }; const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); @@ -272,106 +242,8 @@ const DynamicJsonForm = ({ return; } - const updateArray = ( - array: JsonValue[], - path: string[], - value: JsonValue, - ): JsonValue[] => { - const [index, ...restPath] = path; - const arrayIndex = Number(index); - - // Validate array index - if (isNaN(arrayIndex)) { - console.error(`Invalid array index: ${index}`); - return array; - } - - // Check array bounds - if (arrayIndex < 0) { - console.error(`Array index out of bounds: ${arrayIndex} < 0`); - return array; - } - - const newArray = [...array]; - - if (restPath.length === 0) { - newArray[arrayIndex] = value; - } else { - // Ensure index position exists - if (arrayIndex >= array.length) { - console.warn(`Extending array to index ${arrayIndex}`); - newArray.length = arrayIndex + 1; - newArray.fill(null, array.length, arrayIndex); - } - newArray[arrayIndex] = updateValue( - newArray[arrayIndex], - restPath, - value, - ); - } - return newArray; - }; - - const updateObject = ( - obj: JsonObject, - path: string[], - value: JsonValue, - ): JsonObject => { - const [key, ...restPath] = path; - - // Validate object key - if (typeof key !== "string") { - console.error(`Invalid object key: ${key}`); - return obj; - } - - const newObj = { ...obj }; - - if (restPath.length === 0) { - newObj[key] = value; - } else { - // Ensure key exists - if (!(key in newObj)) { - console.warn(`Creating new key in object: ${key}`); - newObj[key] = {}; - } - newObj[key] = updateValue(newObj[key], restPath, value); - } - return newObj; - }; - - const updateValue = ( - current: JsonValue, - path: string[], - value: JsonValue, - ): JsonValue => { - if (path.length === 0) return value; - - try { - if (!current) { - current = !isNaN(Number(path[0])) ? [] : {}; - } - - // Type checking - if (Array.isArray(current)) { - return updateArray(current, path, value); - } else if (typeof current === "object" && current !== null) { - return updateObject(current, path, value); - } else { - console.error( - `Cannot update path ${path.join(".")} in non-object/array value:`, - current, - ); - return current; - } - } catch (error) { - console.error(`Error updating value at path ${path.join(".")}:`, error); - return current; - } - }; - try { - const newValue = updateValue(value, path, fieldValue); + const newValue = updateValueAtPath(value, path, fieldValue); onChange(newValue); } catch (error) { console.error("Failed to update form value:", error); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts new file mode 100644 index 0000000..bbfe5ef --- /dev/null +++ b/client/src/utils/jsonPathUtils.ts @@ -0,0 +1,152 @@ +import { JsonValue } from "../components/DynamicJsonForm"; + +export type JsonObject = { [key: string]: JsonValue }; + +/** + * Updates a value at a specific path in a nested JSON structure + * @param obj The original JSON value + * @param path Array of keys/indices representing the path to the value + * @param value The new value to set + * @returns A new JSON value with the updated path + */ +export function updateValueAtPath( + obj: JsonValue, + path: string[], + value: JsonValue +): JsonValue { + if (path.length === 0) return value; + + // Initialize if null/undefined + if (obj === null || obj === undefined) { + obj = !isNaN(Number(path[0])) ? [] : {}; + } + + // Handle arrays + if (Array.isArray(obj)) { + return updateArray(obj, path, value); + } + // Handle objects + else if (typeof obj === "object" && obj !== null) { + return updateObject(obj as JsonObject, path, value); + } + // Cannot update primitives + else { + console.error( + `Cannot update path ${path.join(".")} in non-object/array value:`, + obj + ); + return obj; + } +} + +/** + * Updates an array at a specific path + */ +function updateArray( + array: JsonValue[], + path: string[], + value: JsonValue +): JsonValue[] { + const [index, ...restPath] = path; + const arrayIndex = Number(index); + + // Validate array index + if (isNaN(arrayIndex)) { + console.error(`Invalid array index: ${index}`); + return array; + } + + // Check array bounds + if (arrayIndex < 0) { + console.error(`Array index out of bounds: ${arrayIndex} < 0`); + return array; + } + + const newArray = [...array]; + + if (restPath.length === 0) { + newArray[arrayIndex] = value; + } else { + // Ensure index position exists + if (arrayIndex >= array.length) { + console.warn(`Extending array to index ${arrayIndex}`); + newArray.length = arrayIndex + 1; + newArray.fill(null, array.length, arrayIndex); + } + newArray[arrayIndex] = updateValueAtPath( + newArray[arrayIndex], + restPath, + value + ); + } + return newArray; +} + +/** + * Updates an object at a specific path + */ +function updateObject( + obj: JsonObject, + path: string[], + value: JsonValue +): JsonObject { + const [key, ...restPath] = path; + + // Validate object key + if (typeof key !== "string") { + console.error(`Invalid object key: ${key}`); + return obj; + } + + const newObj = { ...obj }; + + if (restPath.length === 0) { + newObj[key] = value; + } else { + // Ensure key exists + if (!(key in newObj)) { + console.warn(`Creating new key in object: ${key}`); + newObj[key] = {}; + } + newObj[key] = updateValueAtPath(newObj[key], restPath, value); + } + return newObj; +} + +/** + * Gets a value at a specific path in a nested JSON structure + * @param obj The JSON value to traverse + * @param path Array of keys/indices representing the path to the value + * @param defaultValue Value to return if path doesn't exist + * @returns The value at the path, or defaultValue if not found + */ +export function getValueAtPath( + obj: JsonValue, + path: string[], + defaultValue: JsonValue = null +): JsonValue { + if (path.length === 0) return obj; + + const [first, ...rest] = path; + + if (obj === null || obj === undefined) { + return defaultValue; + } + + if (Array.isArray(obj)) { + const index = Number(first); + if (isNaN(index) || index < 0 || index >= obj.length) { + return defaultValue; + } + return getValueAtPath(obj[index], rest, defaultValue); + } + + if (typeof obj === "object" && obj !== null) { + if (!(first in obj)) { + return defaultValue; + } + return getValueAtPath((obj as JsonObject)[first], rest, defaultValue); + } + + return defaultValue; +} diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts new file mode 100644 index 0000000..feee4de --- /dev/null +++ b/client/src/utils/schemaUtils.ts @@ -0,0 +1,72 @@ +import { JsonValue, JsonSchemaType } from "../components/DynamicJsonForm"; +import { JsonObject } from "./jsonPathUtils"; + +/** + * Generates a default value based on a JSON schema type + * @param schema The JSON schema definition + * @returns A default value matching the schema type + */ +export function generateDefaultValue(schema: JsonSchemaType): JsonValue { + switch (schema.type) { + case "string": + return ""; + case "number": + case "integer": + return 0; + case "boolean": + return false; + case "array": + return []; + case "object": { + const obj: JsonObject = {}; + if (schema.properties) { + Object.entries(schema.properties).forEach(([key, prop]) => { + obj[key] = generateDefaultValue(prop); + }); + } + return obj; + } + default: + return null; + } +} + +/** + * Formats a field key into a human-readable label + * @param key The field key to format + * @returns A formatted label string + */ +export function formatFieldLabel(key: string): string { + return key + .replace(/([A-Z])/g, " $1") // Insert space before capital letters + .replace(/_/g, " ") // Replace underscores with spaces + .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter +} + +/** + * Validates if a value conforms to a JSON schema + * @param value The value to validate + * @param schema The JSON schema to validate against + * @returns True if valid, false otherwise + */ +export function validateValueAgainstSchema( + value: JsonValue, + schema: JsonSchemaType +): boolean { + // Basic type validation + switch (schema.type) { + case "string": + return typeof value === "string"; + case "number": + case "integer": + return typeof value === "number"; + case "boolean": + return typeof value === "boolean"; + case "array": + return Array.isArray(value); + case "object": + return typeof value === "object" && value !== null && !Array.isArray(value); + default: + return true; + } +} From 07474796945d792c572932d1d6035d6b9fd9c8d7 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:40:01 -0700 Subject: [PATCH 04/60] Handle edge case and add tests for functions --- client/jest.config.cjs | 32 + client/package.json | 8 +- client/src/utils/__mocks__/DynamicJsonForm.ts | 18 + .../src/utils/__tests__/jsonPathUtils.test.ts | 169 + .../src/utils/__tests__/schemaUtils.test.ts | 135 + client/src/utils/jsonPathUtils.ts | 30 +- client/tsconfig.jest.json | 10 + package-lock.json | 3381 ++++++++++++++++- 8 files changed, 3744 insertions(+), 39 deletions(-) create mode 100644 client/jest.config.cjs create mode 100644 client/src/utils/__mocks__/DynamicJsonForm.ts create mode 100644 client/src/utils/__tests__/jsonPathUtils.test.ts create mode 100644 client/src/utils/__tests__/schemaUtils.test.ts create mode 100644 client/tsconfig.jest.json diff --git a/client/jest.config.cjs b/client/jest.config.cjs new file mode 100644 index 0000000..bf6a1fc --- /dev/null +++ b/client/jest.config.cjs @@ -0,0 +1,32 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + '^../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts', + '^../../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts' + }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + useESM: true, + jsx: 'react-jsx', + tsconfig: 'tsconfig.jest.json' + }] + }, + extensionsToTreatAsEsm: ['.ts', '.tsx'], + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + // Exclude directories and files that don't need to be tested + testPathIgnorePatterns: [ + '/node_modules/', + '/dist/', + '/bin/', + '\\.config\\.(js|ts|cjs|mjs)$' + ], + // Exclude the same patterns from coverage reports + coveragePathIgnorePatterns: [ + '/node_modules/', + '/dist/', + '/bin/', + '\\.config\\.(js|ts|cjs|mjs)$' + ] +}; diff --git a/client/package.json b/client/package.json index 4e95b83..fc125ba 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "jest --config jest.config.cjs", + "test:watch": "jest --config jest.config.cjs --watch" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", @@ -45,6 +47,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", @@ -55,8 +58,11 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", "globals": "^15.9.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", + "ts-jest": "^29.2.6", "typescript": "^5.5.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" diff --git a/client/src/utils/__mocks__/DynamicJsonForm.ts b/client/src/utils/__mocks__/DynamicJsonForm.ts new file mode 100644 index 0000000..83a98e8 --- /dev/null +++ b/client/src/utils/__mocks__/DynamicJsonForm.ts @@ -0,0 +1,18 @@ +// Mock for DynamicJsonForm that only exports the types needed for tests +export type JsonValue = + | string + | number + | boolean + | null + | JsonValue[] + | { [key: string]: JsonValue }; + +export type JsonSchemaType = { + type: "string" | "number" | "integer" | "boolean" | "array" | "object"; + description?: string; + properties?: Record; + items?: JsonSchemaType; +}; + +// Default export is not used in the tests +export default {}; diff --git a/client/src/utils/__tests__/jsonPathUtils.test.ts b/client/src/utils/__tests__/jsonPathUtils.test.ts new file mode 100644 index 0000000..bca9661 --- /dev/null +++ b/client/src/utils/__tests__/jsonPathUtils.test.ts @@ -0,0 +1,169 @@ +import { updateValueAtPath, getValueAtPath } from '../jsonPathUtils'; +import { JsonValue } from '../../components/DynamicJsonForm'; + +describe('updateValueAtPath', () => { + // Basic functionality tests + test('returns the new value when path is empty', () => { + expect(updateValueAtPath({ foo: 'bar' }, [], 'newValue')).toBe('newValue'); + }); + + test('initializes an empty object when input is null/undefined and path starts with a string', () => { + expect(updateValueAtPath(null as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + expect(updateValueAtPath(undefined as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + }); + + test('initializes an empty array when input is null/undefined and path starts with a number', () => { + expect(updateValueAtPath(null as any, ['0'], 'bar')).toEqual(['bar']); + expect(updateValueAtPath(undefined as any, ['0'], 'bar')).toEqual(['bar']); + }); + + // Object update tests + test('updates a simple object property', () => { + const obj = { name: 'John', age: 30 }; + expect(updateValueAtPath(obj, ['age'], 31)).toEqual({ name: 'John', age: 31 }); + }); + + test('updates a nested object property', () => { + const obj = { user: { name: 'John', address: { city: 'New York' } } }; + expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( + { user: { name: 'John', address: { city: 'Boston' } } } + ); + }); + + test('creates missing object properties', () => { + const obj = { user: { name: 'John' } }; + expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( + { user: { name: 'John', address: { city: 'Boston' } } } + ); + }); + + // Array update tests + test('updates an array item', () => { + const arr = [1, 2, 3, 4]; + expect(updateValueAtPath(arr, ['2'], 5)).toEqual([1, 2, 5, 4]); + }); + + test('extends an array when index is out of bounds', () => { + const arr = [1, 2, 3]; + const result = updateValueAtPath(arr, ['5'], 'new') as JsonValue[]; + + // Check overall array structure + expect(result).toEqual([1, 2, 3, null, null, 'new']); + + // Explicitly verify that indices 3 and 4 contain null, not undefined + expect(result[3]).toBe(null); + expect(result[4]).toBe(null); + + // Verify these aren't "holes" in the array (important distinction) + expect(3 in result).toBe(true); + expect(4 in result).toBe(true); + + // Verify the array has the correct length + expect(result.length).toBe(6); + + // Verify the array doesn't have holes by checking every index exists + expect(result.every((_, index: number) => index in result)).toBe(true); + }); + + test('updates a nested array item', () => { + const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; + expect(updateValueAtPath(obj, ['users', '1', 'name'], 'Janet')).toEqual( + { users: [{ name: 'John' }, { name: 'Janet' }] } + ); + }); + + // Error handling tests + test('returns original value when trying to update a primitive with a path', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const result = updateValueAtPath('string', ['foo'], 'bar'); + expect(result).toBe('string'); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('returns original array when index is invalid', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const arr = [1, 2, 3]; + expect(updateValueAtPath(arr, ['invalid'], 4)).toEqual(arr); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('returns original array when index is negative', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(); + const arr = [1, 2, 3]; + expect(updateValueAtPath(arr, ['-1'], 4)).toEqual(arr); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); + + test('handles sparse arrays correctly by filling holes with null', () => { + // Create a sparse array by deleting an element + const sparseArr = [1, 2, 3]; + delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3] + + // Update a value beyond the array length + const result = updateValueAtPath(sparseArr, ['5'], 'new') as JsonValue[]; + + // Check overall array structure + expect(result).toEqual([1, null, 3, null, null, 'new']); + + // Explicitly verify that index 1 (the hole) contains null, not undefined + expect(result[1]).toBe(null); + + // Verify this isn't a hole in the array + expect(1 in result).toBe(true); + + // Verify all indices contain null (not undefined) + expect(result[1]).not.toBe(undefined); + expect(result[3]).toBe(null); + expect(result[4]).toBe(null); + }); +}); + +describe('getValueAtPath', () => { + test('returns the original value when path is empty', () => { + const obj = { foo: 'bar' }; + expect(getValueAtPath(obj, [])).toBe(obj); + }); + + test('returns the value at a simple path', () => { + const obj = { name: 'John', age: 30 }; + expect(getValueAtPath(obj, ['name'])).toBe('John'); + }); + + test('returns the value at a nested path', () => { + const obj = { user: { name: 'John', address: { city: 'New York' } } }; + expect(getValueAtPath(obj, ['user', 'address', 'city'])).toBe('New York'); + }); + + test('returns default value when path does not exist', () => { + const obj = { user: { name: 'John' } }; + expect(getValueAtPath(obj, ['user', 'address', 'city'], 'Unknown')).toBe('Unknown'); + }); + + test('returns default value when input is null/undefined', () => { + expect(getValueAtPath(null as any, ['foo'], 'default')).toBe('default'); + expect(getValueAtPath(undefined as any, ['foo'], 'default')).toBe('default'); + }); + + test('handles array indices correctly', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['1'])).toBe('b'); + }); + + test('returns default value for out of bounds array indices', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['5'], 'default')).toBe('default'); + }); + + test('returns default value for invalid array indices', () => { + const arr = ['a', 'b', 'c']; + expect(getValueAtPath(arr, ['invalid'], 'default')).toBe('default'); + }); + + test('navigates through mixed object and array paths', () => { + const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; + expect(getValueAtPath(obj, ['users', '1', 'name'])).toBe('Jane'); + }); +}); diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts new file mode 100644 index 0000000..beb267e --- /dev/null +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -0,0 +1,135 @@ +import { generateDefaultValue, formatFieldLabel, validateValueAgainstSchema } from '../schemaUtils'; +import { JsonSchemaType } from '../../components/DynamicJsonForm'; + +describe('generateDefaultValue', () => { + test('generates default string', () => { + expect(generateDefaultValue({ type: 'string' })).toBe(''); + }); + + test('generates default number', () => { + expect(generateDefaultValue({ type: 'number' })).toBe(0); + }); + + test('generates default integer', () => { + expect(generateDefaultValue({ type: 'integer' })).toBe(0); + }); + + test('generates default boolean', () => { + expect(generateDefaultValue({ type: 'boolean' })).toBe(false); + }); + + test('generates default array', () => { + expect(generateDefaultValue({ type: 'array' })).toEqual([]); + }); + + test('generates default empty object', () => { + expect(generateDefaultValue({ type: 'object' })).toEqual({}); + }); + + test('generates default null for unknown types', () => { + expect(generateDefaultValue({ type: 'unknown' as any })).toBe(null); + }); + + test('generates object with properties', () => { + const schema: JsonSchemaType = { + type: 'object', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + isActive: { type: 'boolean' } + } + }; + expect(generateDefaultValue(schema)).toEqual({ + name: '', + age: 0, + isActive: false + }); + }); + + test('handles nested objects', () => { + const schema: JsonSchemaType = { + type: 'object', + properties: { + user: { + type: 'object', + properties: { + name: { type: 'string' }, + address: { + type: 'object', + properties: { + city: { type: 'string' } + } + } + } + } + } + }; + expect(generateDefaultValue(schema)).toEqual({ + user: { + name: '', + address: { + city: '' + } + } + }); + }); +}); + +describe('formatFieldLabel', () => { + test('formats camelCase', () => { + expect(formatFieldLabel('firstName')).toBe('First Name'); + }); + + test('formats snake_case', () => { + expect(formatFieldLabel('first_name')).toBe('First name'); + }); + + test('formats single word', () => { + expect(formatFieldLabel('name')).toBe('Name'); + }); + + test('formats mixed case with underscores', () => { + expect(formatFieldLabel('user_firstName')).toBe('User first Name'); + }); + + test('handles empty string', () => { + expect(formatFieldLabel('')).toBe(''); + }); +}); + +describe('validateValueAgainstSchema', () => { + test('validates string type', () => { + expect(validateValueAgainstSchema('test', { type: 'string' })).toBe(true); + expect(validateValueAgainstSchema(123, { type: 'string' })).toBe(false); + }); + + test('validates number type', () => { + expect(validateValueAgainstSchema(123, { type: 'number' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'number' })).toBe(false); + }); + + test('validates integer type', () => { + expect(validateValueAgainstSchema(123, { type: 'integer' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'integer' })).toBe(false); + }); + + test('validates boolean type', () => { + expect(validateValueAgainstSchema(true, { type: 'boolean' })).toBe(true); + expect(validateValueAgainstSchema('test', { type: 'boolean' })).toBe(false); + }); + + test('validates array type', () => { + expect(validateValueAgainstSchema([], { type: 'array' })).toBe(true); + expect(validateValueAgainstSchema({}, { type: 'array' })).toBe(false); + }); + + test('validates object type', () => { + expect(validateValueAgainstSchema({}, { type: 'object' })).toBe(true); + expect(validateValueAgainstSchema([], { type: 'object' })).toBe(false); + expect(validateValueAgainstSchema('test', { type: 'object' })).toBe(false); + }); + + test('returns true for unknown types', () => { + expect(validateValueAgainstSchema('anything', { type: 'unknown' as any })).toBe(true); + }); +}); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index bbfe5ef..ef5a18e 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -62,22 +62,27 @@ function updateArray( return array; } - const newArray = [...array]; + // Create a dense copy of the array, filling holes with null + let newArray: JsonValue[] = []; + for (let i = 0; i < array.length; i++) { + newArray[i] = i in array ? array[i] : null; + } + + // If the desired index is out of bounds, build a new array explicitly filled with nulls + if (arrayIndex >= newArray.length) { + console.warn(`Extending array to index ${arrayIndex}`); + const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null); + // Copy over the existing elements (now guaranteed to be dense) + for (let i = 0; i < newArray.length; i++) { + extendedArray[i] = newArray[i]; + } + newArray = extendedArray; + } if (restPath.length === 0) { newArray[arrayIndex] = value; } else { - // Ensure index position exists - if (arrayIndex >= array.length) { - console.warn(`Extending array to index ${arrayIndex}`); - newArray.length = arrayIndex + 1; - newArray.fill(null, array.length, arrayIndex); - } - newArray[arrayIndex] = updateValueAtPath( - newArray[arrayIndex], - restPath, - value - ); + newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value); } return newArray; } @@ -105,7 +110,6 @@ function updateObject( } else { // Ensure key exists if (!(key in newObj)) { - console.warn(`Creating new key in object: ${key}`); newObj[key] = {}; } newObj[key] = updateValueAtPath(newObj[key], restPath, value); diff --git a/client/tsconfig.jest.json b/client/tsconfig.jest.json new file mode 100644 index 0000000..137fc0a --- /dev/null +++ b/client/tsconfig.jest.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "jsx": "react-jsx", + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "node" + }, + "include": ["src"] +} diff --git a/package-lock.json b/package-lock.json index eb11de6..53cb556 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", @@ -71,8 +72,11 @@ "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", "globals": "^15.9.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.47", "tailwindcss": "^3.4.13", + "ts-jest": "^29.2.6", "typescript": "^5.5.3", "typescript-eslint": "^8.7.0", "vite": "^5.4.8" @@ -296,6 +300,245 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", @@ -386,6 +629,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1153,6 +1403,437 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2289,6 +2970,43 @@ "win32" ] }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -2422,6 +3140,16 @@ "@types/send": "*" } }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -2429,6 +3157,56 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2540,6 +3318,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.13", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", @@ -2550,6 +3342,23 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", @@ -2807,6 +3616,14 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2832,6 +3649,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2854,6 +3682,19 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2871,6 +3712,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2945,6 +3802,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -2983,6 +3854,122 @@ "postcss": "^8.1.0" } }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3110,6 +4097,36 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3138,6 +4155,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3148,6 +4178,16 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -3206,6 +4246,16 @@ "node": ">=8" } }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -3242,6 +4292,29 @@ "node": ">= 6" } }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3277,6 +4350,24 @@ "node": ">=6" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3295,6 +4386,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3391,6 +4495,28 @@ "node": ">= 0.10" } }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3423,6 +4549,33 @@ "node": ">=4" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3430,6 +4583,21 @@ "devOptional": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -3447,6 +4615,28 @@ } } }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3454,6 +4644,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3471,6 +4671,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3490,6 +4700,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -3511,12 +4731,50 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3529,6 +4787,22 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.65", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", @@ -3536,6 +4810,19 @@ "dev": true, "license": "ISC" }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3551,14 +4838,34 @@ "node": ">= 0.8" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3572,6 +4879,34 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -3640,6 +4975,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "9.15.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", @@ -3771,6 +5128,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3835,6 +5206,63 @@ "node": ">=18.0.0" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -3954,6 +5382,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3967,6 +5405,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4066,6 +5537,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4098,6 +5585,13 @@ "node": ">= 0.6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4141,16 +5635,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4168,6 +5667,42 @@ "node": ">=6" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", @@ -4251,17 +5786,24 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4290,10 +5832,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4302,11 +5844,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -4326,6 +5872,26 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4342,6 +5908,45 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -4381,6 +5986,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4391,6 +6016,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4415,6 +6052,13 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4460,6 +6104,16 @@ "node": ">=8" } }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4481,12 +6135,129 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -4502,6 +6273,677 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -4530,6 +6972,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -4550,6 +7038,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4587,6 +7082,26 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4638,6 +7153,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4676,12 +7198,60 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "license": "ISC" }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4700,6 +7270,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4764,6 +7341,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4836,6 +7423,13 @@ "node": ">= 0.6" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -4862,6 +7456,26 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4904,6 +7518,32 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4954,6 +7594,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4973,6 +7623,38 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4992,6 +7674,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", @@ -5086,6 +7778,75 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postcss": { "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", @@ -5267,6 +8028,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -5276,6 +8065,20 @@ "node": ">=6" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5289,6 +8092,19 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5299,6 +8115,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -5314,6 +8147,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5395,6 +8235,13 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5528,6 +8375,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5545,6 +8399,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5565,6 +8442,16 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5671,6 +8558,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -5908,6 +8808,33 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5917,6 +8844,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-rx": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.2.tgz", @@ -5927,6 +8865,36 @@ "rxjs": "^7.8.1" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5936,6 +8904,20 @@ "node": ">= 0.8" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5990,6 +8972,26 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6052,6 +9054,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", @@ -6108,6 +9117,43 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6129,6 +9175,13 @@ "node": ">=0.8" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6150,6 +9203,35 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -6178,6 +9260,68 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/ts-jest": { + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.1", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -6266,6 +9410,29 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6325,6 +9492,16 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6375,6 +9552,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", @@ -6439,6 +9627,21 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6938,6 +10141,89 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6998,6 +10284,34 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -7019,6 +10333,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From d4a64fb5d8938472f3d5395f758e5baf4388e759 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:52:19 -0700 Subject: [PATCH 05/60] Add client tests to workflow --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 572e79e..d63f0e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,12 @@ jobs: # Working around https://github.com/npm/cli/issues/4828 # - run: npm ci - run: npm install --no-package-lock + + - name: Run client tests + run: | + cd inspector/client + npm test + - run: npm run build publish: From 90ce62804010d445ff8b5bb9c54829dd781fd244 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 06:54:36 -0700 Subject: [PATCH 06/60] Test workflow in my branch --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d63f0e4..c3fefbe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,8 +1,6 @@ on: + # TODO: Revert to only running on main branch pushes after testing push: - branches: - - main - pull_request: release: types: [published] From 426fb87640b8493c68533e56ba8ecbfaaf3eaed2 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:15:31 -0700 Subject: [PATCH 07/60] Remove comment ans trigger workflow run --- client/src/components/DynamicJsonForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 4f986a8..784cfc0 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -247,7 +247,6 @@ const DynamicJsonForm = ({ onChange(newValue); } catch (error) { console.error("Failed to update form value:", error); - // Keep the original value unchanged onChange(value); } }; From 238c22830b4fc82a6b9e3704f374226156466ef6 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:21:04 -0700 Subject: [PATCH 08/60] Fix formatting --- .github/workflows/main.yml | 4 +- client/jest.config.cjs | 47 +++-- client/src/components/DynamicJsonForm.tsx | 63 +++--- client/src/components/JsonEditor.tsx | 30 +-- .../src/utils/__tests__/jsonPathUtils.test.ts | 179 ++++++++++-------- .../src/utils/__tests__/schemaUtils.test.ts | 152 ++++++++------- client/src/utils/jsonPathUtils.ts | 46 +++-- client/src/utils/schemaUtils.ts | 8 +- 8 files changed, 283 insertions(+), 246 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c3fefbe..d1160c9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,12 +23,12 @@ jobs: # Working around https://github.com/npm/cli/issues/4828 # - run: npm ci - run: npm install --no-package-lock - + - name: Run client tests run: | cd inspector/client npm test - + - run: npm run build publish: diff --git a/client/jest.config.cjs b/client/jest.config.cjs index bf6a1fc..3830e79 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,32 +1,37 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'jsdom', + preset: "ts-jest", + testEnvironment: "jsdom", moduleNameMapper: { - '^@/(.*)$': '/src/$1', - '^../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts', - '^../../components/DynamicJsonForm$': '/src/utils/__mocks__/DynamicJsonForm.ts' + "^@/(.*)$": "/src/$1", + "^../components/DynamicJsonForm$": + "/src/utils/__mocks__/DynamicJsonForm.ts", + "^../../components/DynamicJsonForm$": + "/src/utils/__mocks__/DynamicJsonForm.ts", }, transform: { - '^.+\\.tsx?$': ['ts-jest', { - useESM: true, - jsx: 'react-jsx', - tsconfig: 'tsconfig.jest.json' - }] + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + jsx: "react-jsx", + tsconfig: "tsconfig.jest.json", + }, + ], }, - extensionsToTreatAsEsm: ['.ts', '.tsx'], - testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + extensionsToTreatAsEsm: [".ts", ".tsx"], + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", // Exclude directories and files that don't need to be tested testPathIgnorePatterns: [ - '/node_modules/', - '/dist/', - '/bin/', - '\\.config\\.(js|ts|cjs|mjs)$' + "/node_modules/", + "/dist/", + "/bin/", + "\\.config\\.(js|ts|cjs|mjs)$", ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ - '/node_modules/', - '/dist/', - '/bin/', - '\\.config\\.(js|ts|cjs|mjs)$' - ] + "/node_modules/", + "/dist/", + "/bin/", + "\\.config\\.(js|ts|cjs|mjs)$", + ], }; diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 784cfc0..9e865f9 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -28,25 +28,25 @@ interface DynamicJsonFormProps { maxDepth?: number; } - const DynamicJsonForm = ({ schema, value, onChange, maxDepth = 3, }: DynamicJsonFormProps) => { - const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); // Add state for storing raw JSON value const [rawJsonValue, setRawJsonValue] = useState( - JSON.stringify(value ?? generateDefaultValue(schema), null, 2) + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); // Update rawJsonValue when value prop changes useEffect(() => { if (!isJsonMode) { - setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setRawJsonValue( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), + ); } }, [value, schema, isJsonMode]); @@ -64,7 +64,9 @@ const DynamicJsonForm = ({ } } else { // Update raw JSON value when switching to JSON mode - setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2)); + setRawJsonValue( + JSON.stringify(value ?? generateDefaultValue(schema), null, 2), + ); setIsJsonMode(true); } }; @@ -131,8 +133,8 @@ const DynamicJsonForm = ({ ); case "object": { // Handle case where we have a value but no schema properties - const objectValue = currentValue as JsonObject || {}; - + const objectValue = (currentValue as JsonObject) || {}; + // If we have schema properties, use them to render fields if (propSchema.properties) { return ( @@ -150,7 +152,7 @@ const DynamicJsonForm = ({ ))} ); - } + } // If we have a value but no schema properties, render fields based on the value else if (Object.keys(objectValue).length > 0) { return ( @@ -161,7 +163,7 @@ const DynamicJsonForm = ({ + onChange={(e) => handleFieldChange([...path, key], e.target.value) } /> @@ -254,11 +256,7 @@ const DynamicJsonForm = ({ return (
-
@@ -281,23 +279,28 @@ const DynamicJsonForm = ({ }} error={jsonError} /> - ) : ( - // If schema type is object but value is not an object or is empty, and we have actual JSON data, - // render a simple representation of the JSON data - schema.type === "object" && - (typeof value !== "object" || value === null || Object.keys(value).length === 0) && - rawJsonValue && + ) : // If schema type is object but value is not an object or is empty, and we have actual JSON data, + // render a simple representation of the JSON data + schema.type === "object" && + (typeof value !== "object" || + value === null || + Object.keys(value).length === 0) && + rawJsonValue && rawJsonValue !== "{}" ? ( -
-

Form view not available for this JSON structure. Using simplified view:

-
-              {rawJsonValue}
-            
-

Use JSON mode for full editing capabilities.

-
- ) : ( - renderFormFields(schema, value) - ) +
+

+ Form view not available for this JSON structure. Using simplified + view: +

+
+            {rawJsonValue}
+          
+

+ Use JSON mode for full editing capabilities. +

+
+ ) : ( + renderFormFields(schema, value) )}
); diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 9109088..87a6367 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -11,10 +11,16 @@ interface JsonEditorProps { error?: string; } -const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) => { +const JsonEditor = ({ + value, + onChange, + error: externalError, +}: JsonEditorProps) => { const [editorContent, setEditorContent] = useState(value); - const [internalError, setInternalError] = useState(undefined); - + const [internalError, setInternalError] = useState( + undefined, + ); + useEffect(() => { setEditorContent(value); }, [value]); @@ -22,8 +28,8 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) const formatJson = (json: string): string => { try { // Handle empty arrays and objects specifically - if (json.trim() === '[]') return '[]'; - if (json.trim() === '{}') return '{}'; + if (json.trim() === "[]") return "[]"; + if (json.trim() === "{}") return "{}"; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; @@ -52,17 +58,15 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) return (
-
- {displayError &&

{displayError}

} + {displayError && ( +

{displayError}

+ )}
); }; diff --git a/client/src/utils/__tests__/jsonPathUtils.test.ts b/client/src/utils/__tests__/jsonPathUtils.test.ts index bca9661..b6ef7e9 100644 --- a/client/src/utils/__tests__/jsonPathUtils.test.ts +++ b/client/src/utils/__tests__/jsonPathUtils.test.ts @@ -1,119 +1,126 @@ -import { updateValueAtPath, getValueAtPath } from '../jsonPathUtils'; -import { JsonValue } from '../../components/DynamicJsonForm'; +import { updateValueAtPath, getValueAtPath } from "../jsonPathUtils"; +import { JsonValue } from "../../components/DynamicJsonForm"; -describe('updateValueAtPath', () => { +describe("updateValueAtPath", () => { // Basic functionality tests - test('returns the new value when path is empty', () => { - expect(updateValueAtPath({ foo: 'bar' }, [], 'newValue')).toBe('newValue'); + test("returns the new value when path is empty", () => { + expect(updateValueAtPath({ foo: "bar" }, [], "newValue")).toBe("newValue"); }); - test('initializes an empty object when input is null/undefined and path starts with a string', () => { - expect(updateValueAtPath(null as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); - expect(updateValueAtPath(undefined as any, ['foo'], 'bar')).toEqual({ foo: 'bar' }); + test("initializes an empty object when input is null/undefined and path starts with a string", () => { + expect(updateValueAtPath(null as any, ["foo"], "bar")).toEqual({ + foo: "bar", + }); + expect(updateValueAtPath(undefined as any, ["foo"], "bar")).toEqual({ + foo: "bar", + }); }); - test('initializes an empty array when input is null/undefined and path starts with a number', () => { - expect(updateValueAtPath(null as any, ['0'], 'bar')).toEqual(['bar']); - expect(updateValueAtPath(undefined as any, ['0'], 'bar')).toEqual(['bar']); + test("initializes an empty array when input is null/undefined and path starts with a number", () => { + expect(updateValueAtPath(null as any, ["0"], "bar")).toEqual(["bar"]); + expect(updateValueAtPath(undefined as any, ["0"], "bar")).toEqual(["bar"]); }); // Object update tests - test('updates a simple object property', () => { - const obj = { name: 'John', age: 30 }; - expect(updateValueAtPath(obj, ['age'], 31)).toEqual({ name: 'John', age: 31 }); + test("updates a simple object property", () => { + const obj = { name: "John", age: 30 }; + expect(updateValueAtPath(obj, ["age"], 31)).toEqual({ + name: "John", + age: 31, + }); }); - test('updates a nested object property', () => { - const obj = { user: { name: 'John', address: { city: 'New York' } } }; - expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( - { user: { name: 'John', address: { city: 'Boston' } } } - ); + test("updates a nested object property", () => { + const obj = { user: { name: "John", address: { city: "New York" } } }; + expect( + updateValueAtPath(obj, ["user", "address", "city"], "Boston"), + ).toEqual({ user: { name: "John", address: { city: "Boston" } } }); }); - test('creates missing object properties', () => { - const obj = { user: { name: 'John' } }; - expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual( - { user: { name: 'John', address: { city: 'Boston' } } } - ); + test("creates missing object properties", () => { + const obj = { user: { name: "John" } }; + expect( + updateValueAtPath(obj, ["user", "address", "city"], "Boston"), + ).toEqual({ user: { name: "John", address: { city: "Boston" } } }); }); // Array update tests - test('updates an array item', () => { + test("updates an array item", () => { const arr = [1, 2, 3, 4]; - expect(updateValueAtPath(arr, ['2'], 5)).toEqual([1, 2, 5, 4]); + expect(updateValueAtPath(arr, ["2"], 5)).toEqual([1, 2, 5, 4]); }); - test('extends an array when index is out of bounds', () => { + test("extends an array when index is out of bounds", () => { const arr = [1, 2, 3]; - const result = updateValueAtPath(arr, ['5'], 'new') as JsonValue[]; - + const result = updateValueAtPath(arr, ["5"], "new") as JsonValue[]; + // Check overall array structure - expect(result).toEqual([1, 2, 3, null, null, 'new']); - + expect(result).toEqual([1, 2, 3, null, null, "new"]); + // Explicitly verify that indices 3 and 4 contain null, not undefined expect(result[3]).toBe(null); expect(result[4]).toBe(null); - + // Verify these aren't "holes" in the array (important distinction) expect(3 in result).toBe(true); expect(4 in result).toBe(true); - + // Verify the array has the correct length expect(result.length).toBe(6); - + // Verify the array doesn't have holes by checking every index exists expect(result.every((_, index: number) => index in result)).toBe(true); }); - test('updates a nested array item', () => { - const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; - expect(updateValueAtPath(obj, ['users', '1', 'name'], 'Janet')).toEqual( - { users: [{ name: 'John' }, { name: 'Janet' }] } - ); + test("updates a nested array item", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(updateValueAtPath(obj, ["users", "1", "name"], "Janet")).toEqual({ + users: [{ name: "John" }, { name: "Janet" }], + }); }); // Error handling tests - test('returns original value when trying to update a primitive with a path', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); - const result = updateValueAtPath('string', ['foo'], 'bar'); - expect(result).toBe('string'); + test("returns original value when trying to update a primitive with a path", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); + const result = updateValueAtPath("string", ["foo"], "bar"); + expect(result).toBe("string"); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('returns original array when index is invalid', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); + test("returns original array when index is invalid", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); const arr = [1, 2, 3]; - expect(updateValueAtPath(arr, ['invalid'], 4)).toEqual(arr); + expect(updateValueAtPath(arr, ["invalid"], 4)).toEqual(arr); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('returns original array when index is negative', () => { - const spy = jest.spyOn(console, 'error').mockImplementation(); + test("returns original array when index is negative", () => { + const spy = jest.spyOn(console, "error").mockImplementation(); const arr = [1, 2, 3]; - expect(updateValueAtPath(arr, ['-1'], 4)).toEqual(arr); + expect(updateValueAtPath(arr, ["-1"], 4)).toEqual(arr); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); - test('handles sparse arrays correctly by filling holes with null', () => { + test("handles sparse arrays correctly by filling holes with null", () => { // Create a sparse array by deleting an element const sparseArr = [1, 2, 3]; delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3] - + // Update a value beyond the array length - const result = updateValueAtPath(sparseArr, ['5'], 'new') as JsonValue[]; - + const result = updateValueAtPath(sparseArr, ["5"], "new") as JsonValue[]; + // Check overall array structure - expect(result).toEqual([1, null, 3, null, null, 'new']); - + expect(result).toEqual([1, null, 3, null, null, "new"]); + // Explicitly verify that index 1 (the hole) contains null, not undefined expect(result[1]).toBe(null); - + // Verify this isn't a hole in the array expect(1 in result).toBe(true); - + // Verify all indices contain null (not undefined) expect(result[1]).not.toBe(undefined); expect(result[3]).toBe(null); @@ -121,49 +128,53 @@ describe('updateValueAtPath', () => { }); }); -describe('getValueAtPath', () => { - test('returns the original value when path is empty', () => { - const obj = { foo: 'bar' }; +describe("getValueAtPath", () => { + test("returns the original value when path is empty", () => { + const obj = { foo: "bar" }; expect(getValueAtPath(obj, [])).toBe(obj); }); - test('returns the value at a simple path', () => { - const obj = { name: 'John', age: 30 }; - expect(getValueAtPath(obj, ['name'])).toBe('John'); + test("returns the value at a simple path", () => { + const obj = { name: "John", age: 30 }; + expect(getValueAtPath(obj, ["name"])).toBe("John"); }); - test('returns the value at a nested path', () => { - const obj = { user: { name: 'John', address: { city: 'New York' } } }; - expect(getValueAtPath(obj, ['user', 'address', 'city'])).toBe('New York'); + test("returns the value at a nested path", () => { + const obj = { user: { name: "John", address: { city: "New York" } } }; + expect(getValueAtPath(obj, ["user", "address", "city"])).toBe("New York"); }); - test('returns default value when path does not exist', () => { - const obj = { user: { name: 'John' } }; - expect(getValueAtPath(obj, ['user', 'address', 'city'], 'Unknown')).toBe('Unknown'); + test("returns default value when path does not exist", () => { + const obj = { user: { name: "John" } }; + expect(getValueAtPath(obj, ["user", "address", "city"], "Unknown")).toBe( + "Unknown", + ); }); - test('returns default value when input is null/undefined', () => { - expect(getValueAtPath(null as any, ['foo'], 'default')).toBe('default'); - expect(getValueAtPath(undefined as any, ['foo'], 'default')).toBe('default'); + test("returns default value when input is null/undefined", () => { + expect(getValueAtPath(null as any, ["foo"], "default")).toBe("default"); + expect(getValueAtPath(undefined as any, ["foo"], "default")).toBe( + "default", + ); }); - test('handles array indices correctly', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['1'])).toBe('b'); + test("handles array indices correctly", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["1"])).toBe("b"); }); - test('returns default value for out of bounds array indices', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['5'], 'default')).toBe('default'); + test("returns default value for out of bounds array indices", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["5"], "default")).toBe("default"); }); - test('returns default value for invalid array indices', () => { - const arr = ['a', 'b', 'c']; - expect(getValueAtPath(arr, ['invalid'], 'default')).toBe('default'); + test("returns default value for invalid array indices", () => { + const arr = ["a", "b", "c"]; + expect(getValueAtPath(arr, ["invalid"], "default")).toBe("default"); }); - test('navigates through mixed object and array paths', () => { - const obj = { users: [{ name: 'John' }, { name: 'Jane' }] }; - expect(getValueAtPath(obj, ['users', '1', 'name'])).toBe('Jane'); + test("navigates through mixed object and array paths", () => { + const obj = { users: [{ name: "John" }, { name: "Jane" }] }; + expect(getValueAtPath(obj, ["users", "1", "name"])).toBe("Jane"); }); }); diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index beb267e..0a10af4 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,135 +1,141 @@ -import { generateDefaultValue, formatFieldLabel, validateValueAgainstSchema } from '../schemaUtils'; -import { JsonSchemaType } from '../../components/DynamicJsonForm'; +import { + generateDefaultValue, + formatFieldLabel, + validateValueAgainstSchema, +} from "../schemaUtils"; +import { JsonSchemaType } from "../../components/DynamicJsonForm"; -describe('generateDefaultValue', () => { - test('generates default string', () => { - expect(generateDefaultValue({ type: 'string' })).toBe(''); +describe("generateDefaultValue", () => { + test("generates default string", () => { + expect(generateDefaultValue({ type: "string" })).toBe(""); }); - test('generates default number', () => { - expect(generateDefaultValue({ type: 'number' })).toBe(0); + test("generates default number", () => { + expect(generateDefaultValue({ type: "number" })).toBe(0); }); - test('generates default integer', () => { - expect(generateDefaultValue({ type: 'integer' })).toBe(0); + test("generates default integer", () => { + expect(generateDefaultValue({ type: "integer" })).toBe(0); }); - test('generates default boolean', () => { - expect(generateDefaultValue({ type: 'boolean' })).toBe(false); + test("generates default boolean", () => { + expect(generateDefaultValue({ type: "boolean" })).toBe(false); }); - test('generates default array', () => { - expect(generateDefaultValue({ type: 'array' })).toEqual([]); + test("generates default array", () => { + expect(generateDefaultValue({ type: "array" })).toEqual([]); }); - test('generates default empty object', () => { - expect(generateDefaultValue({ type: 'object' })).toEqual({}); + test("generates default empty object", () => { + expect(generateDefaultValue({ type: "object" })).toEqual({}); }); - test('generates default null for unknown types', () => { - expect(generateDefaultValue({ type: 'unknown' as any })).toBe(null); + test("generates default null for unknown types", () => { + expect(generateDefaultValue({ type: "unknown" as any })).toBe(null); }); - test('generates object with properties', () => { + test("generates object with properties", () => { const schema: JsonSchemaType = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - age: { type: 'number' }, - isActive: { type: 'boolean' } - } + name: { type: "string" }, + age: { type: "number" }, + isActive: { type: "boolean" }, + }, }; expect(generateDefaultValue(schema)).toEqual({ - name: '', + name: "", age: 0, - isActive: false + isActive: false, }); }); - test('handles nested objects', () => { + test("handles nested objects", () => { const schema: JsonSchemaType = { - type: 'object', + type: "object", properties: { user: { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, + name: { type: "string" }, address: { - type: 'object', + type: "object", properties: { - city: { type: 'string' } - } - } - } - } - } + city: { type: "string" }, + }, + }, + }, + }, + }, }; expect(generateDefaultValue(schema)).toEqual({ user: { - name: '', + name: "", address: { - city: '' - } - } + city: "", + }, + }, }); }); }); -describe('formatFieldLabel', () => { - test('formats camelCase', () => { - expect(formatFieldLabel('firstName')).toBe('First Name'); +describe("formatFieldLabel", () => { + test("formats camelCase", () => { + expect(formatFieldLabel("firstName")).toBe("First Name"); }); - test('formats snake_case', () => { - expect(formatFieldLabel('first_name')).toBe('First name'); + test("formats snake_case", () => { + expect(formatFieldLabel("first_name")).toBe("First name"); }); - test('formats single word', () => { - expect(formatFieldLabel('name')).toBe('Name'); + test("formats single word", () => { + expect(formatFieldLabel("name")).toBe("Name"); }); - test('formats mixed case with underscores', () => { - expect(formatFieldLabel('user_firstName')).toBe('User first Name'); + test("formats mixed case with underscores", () => { + expect(formatFieldLabel("user_firstName")).toBe("User first Name"); }); - test('handles empty string', () => { - expect(formatFieldLabel('')).toBe(''); + test("handles empty string", () => { + expect(formatFieldLabel("")).toBe(""); }); }); -describe('validateValueAgainstSchema', () => { - test('validates string type', () => { - expect(validateValueAgainstSchema('test', { type: 'string' })).toBe(true); - expect(validateValueAgainstSchema(123, { type: 'string' })).toBe(false); +describe("validateValueAgainstSchema", () => { + test("validates string type", () => { + expect(validateValueAgainstSchema("test", { type: "string" })).toBe(true); + expect(validateValueAgainstSchema(123, { type: "string" })).toBe(false); }); - test('validates number type', () => { - expect(validateValueAgainstSchema(123, { type: 'number' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'number' })).toBe(false); + test("validates number type", () => { + expect(validateValueAgainstSchema(123, { type: "number" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "number" })).toBe(false); }); - test('validates integer type', () => { - expect(validateValueAgainstSchema(123, { type: 'integer' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'integer' })).toBe(false); + test("validates integer type", () => { + expect(validateValueAgainstSchema(123, { type: "integer" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "integer" })).toBe(false); }); - test('validates boolean type', () => { - expect(validateValueAgainstSchema(true, { type: 'boolean' })).toBe(true); - expect(validateValueAgainstSchema('test', { type: 'boolean' })).toBe(false); + test("validates boolean type", () => { + expect(validateValueAgainstSchema(true, { type: "boolean" })).toBe(true); + expect(validateValueAgainstSchema("test", { type: "boolean" })).toBe(false); }); - test('validates array type', () => { - expect(validateValueAgainstSchema([], { type: 'array' })).toBe(true); - expect(validateValueAgainstSchema({}, { type: 'array' })).toBe(false); + test("validates array type", () => { + expect(validateValueAgainstSchema([], { type: "array" })).toBe(true); + expect(validateValueAgainstSchema({}, { type: "array" })).toBe(false); }); - test('validates object type', () => { - expect(validateValueAgainstSchema({}, { type: 'object' })).toBe(true); - expect(validateValueAgainstSchema([], { type: 'object' })).toBe(false); - expect(validateValueAgainstSchema('test', { type: 'object' })).toBe(false); + test("validates object type", () => { + expect(validateValueAgainstSchema({}, { type: "object" })).toBe(true); + expect(validateValueAgainstSchema([], { type: "object" })).toBe(false); + expect(validateValueAgainstSchema("test", { type: "object" })).toBe(false); }); - test('returns true for unknown types', () => { - expect(validateValueAgainstSchema('anything', { type: 'unknown' as any })).toBe(true); + test("returns true for unknown types", () => { + expect( + validateValueAgainstSchema("anything", { type: "unknown" as any }), + ).toBe(true); }); }); diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index ef5a18e..25b1e0a 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -10,9 +10,9 @@ export type JsonObject = { [key: string]: JsonValue }; * @returns A new JSON value with the updated path */ export function updateValueAtPath( - obj: JsonValue, - path: string[], - value: JsonValue + obj: JsonValue, + path: string[], + value: JsonValue, ): JsonValue { if (path.length === 0) return value; @@ -24,16 +24,16 @@ export function updateValueAtPath( // Handle arrays if (Array.isArray(obj)) { return updateArray(obj, path, value); - } + } // Handle objects else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); - } + } // Cannot update primitives else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, - obj + obj, ); return obj; } @@ -43,9 +43,9 @@ export function updateValueAtPath( * Updates an array at a specific path */ function updateArray( - array: JsonValue[], - path: string[], - value: JsonValue + array: JsonValue[], + path: string[], + value: JsonValue, ): JsonValue[] { const [index, ...restPath] = path; const arrayIndex = Number(index); @@ -82,7 +82,11 @@ function updateArray( if (restPath.length === 0) { newArray[arrayIndex] = value; } else { - newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value); + newArray[arrayIndex] = updateValueAtPath( + newArray[arrayIndex], + restPath, + value, + ); } return newArray; } @@ -91,9 +95,9 @@ function updateArray( * Updates an object at a specific path */ function updateObject( - obj: JsonObject, - path: string[], - value: JsonValue + obj: JsonObject, + path: string[], + value: JsonValue, ): JsonObject { const [key, ...restPath] = path; @@ -125,18 +129,18 @@ function updateObject( * @returns The value at the path, or defaultValue if not found */ export function getValueAtPath( - obj: JsonValue, - path: string[], - defaultValue: JsonValue = null + obj: JsonValue, + path: string[], + defaultValue: JsonValue = null, ): JsonValue { if (path.length === 0) return obj; - + const [first, ...rest] = path; - + if (obj === null || obj === undefined) { return defaultValue; } - + if (Array.isArray(obj)) { const index = Number(first); if (isNaN(index) || index < 0 || index >= obj.length) { @@ -144,13 +148,13 @@ export function getValueAtPath( } return getValueAtPath(obj[index], rest, defaultValue); } - + if (typeof obj === "object" && obj !== null) { if (!(first in obj)) { return defaultValue; } return getValueAtPath((obj as JsonObject)[first], rest, defaultValue); } - + return defaultValue; } diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index feee4de..b3c8e48 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -50,8 +50,8 @@ export function formatFieldLabel(key: string): string { * @returns True if valid, false otherwise */ export function validateValueAgainstSchema( - value: JsonValue, - schema: JsonSchemaType + value: JsonValue, + schema: JsonSchemaType, ): boolean { // Basic type validation switch (schema.type) { @@ -65,7 +65,9 @@ export function validateValueAgainstSchema( case "array": return Array.isArray(value); case "object": - return typeof value === "object" && value !== null && !Array.isArray(value); + return ( + typeof value === "object" && value !== null && !Array.isArray(value) + ); default: return true; } From 8ac7ef0985d7bda5ee996cdf1b789699e22a07d2 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:23:40 -0700 Subject: [PATCH 09/60] Fix path to client --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d1160c9..9cb4bf5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: - name: Run client tests run: | - cd inspector/client + cd client npm test - run: npm run build From 720480cbbbae517c0f147ab1ed6abee408ccf76b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:28:13 -0700 Subject: [PATCH 10/60] Remove console.warn and extra comments to reduce code noise --- client/src/utils/jsonPathUtils.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index 25b1e0a..16a4ee9 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -16,20 +16,16 @@ export function updateValueAtPath( ): JsonValue { if (path.length === 0) return value; - // Initialize if null/undefined if (obj === null || obj === undefined) { obj = !isNaN(Number(path[0])) ? [] : {}; } - // Handle arrays if (Array.isArray(obj)) { return updateArray(obj, path, value); } - // Handle objects else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); } - // Cannot update primitives else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, @@ -50,27 +46,22 @@ function updateArray( const [index, ...restPath] = path; const arrayIndex = Number(index); - // Validate array index if (isNaN(arrayIndex)) { console.error(`Invalid array index: ${index}`); return array; } - // Check array bounds if (arrayIndex < 0) { console.error(`Array index out of bounds: ${arrayIndex} < 0`); return array; } - // Create a dense copy of the array, filling holes with null let newArray: JsonValue[] = []; for (let i = 0; i < array.length; i++) { newArray[i] = i in array ? array[i] : null; } - // If the desired index is out of bounds, build a new array explicitly filled with nulls if (arrayIndex >= newArray.length) { - console.warn(`Extending array to index ${arrayIndex}`); const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null); // Copy over the existing elements (now guaranteed to be dense) for (let i = 0; i < newArray.length; i++) { From d1f5b3b93383dc133f7319e33895e54caf18e75f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:30:38 -0700 Subject: [PATCH 11/60] Fix formatting --- client/src/utils/jsonPathUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/utils/jsonPathUtils.ts b/client/src/utils/jsonPathUtils.ts index 16a4ee9..ac99940 100644 --- a/client/src/utils/jsonPathUtils.ts +++ b/client/src/utils/jsonPathUtils.ts @@ -22,11 +22,9 @@ export function updateValueAtPath( if (Array.isArray(obj)) { return updateArray(obj, path, value); - } - else if (typeof obj === "object" && obj !== null) { + } else if (typeof obj === "object" && obj !== null) { return updateObject(obj as JsonObject, path, value); - } - else { + } else { console.error( `Cannot update path ${path.join(".")} in non-object/array value:`, obj, From abd4877dae0e1d9e90cd02ef9e8abdcc2cc7809f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:37:47 -0700 Subject: [PATCH 12/60] Revert to only run on main --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cb4bf5..fb0aa6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,8 @@ on: - # TODO: Revert to only running on main branch pushes after testing push: + branches: + - main + pull_request: release: types: [published] From 6ec82e21b1f0e19f01fd5883723b7915c30025fa Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 07:48:19 -0700 Subject: [PATCH 13/60] Remove some fluff --- client/src/components/JsonEditor.tsx | 3 --- package-lock.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/components/JsonEditor.tsx b/client/src/components/JsonEditor.tsx index 87a6367..0d2596e 100644 --- a/client/src/components/JsonEditor.tsx +++ b/client/src/components/JsonEditor.tsx @@ -27,9 +27,6 @@ const JsonEditor = ({ const formatJson = (json: string): string => { try { - // Handle empty arrays and objects specifically - if (json.trim() === "[]") return "[]"; - if (json.trim() === "{}") return "{}"; return JSON.stringify(JSON.parse(json), null, 2); } catch { return json; diff --git a/package-lock.json b/package-lock.json index a83799d..90af99a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,8 +35,8 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", From 44982e6c9747e543251f819a0eda47b007b057d1 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 21:33:37 -0700 Subject: [PATCH 14/60] Default to nulls and update tests --- client/package.json | 5 +- client/src/components/DynamicJsonForm.tsx | 71 +++++++++++++++---- .../src/utils/__tests__/schemaUtils.test.ts | 32 +++++---- client/src/utils/schemaUtils.ts | 33 +++++++-- package-lock.json | 12 ++++ 5 files changed, 120 insertions(+), 33 deletions(-) diff --git a/client/package.json b/client/package.json index 4735ffa..038a02a 100644 --- a/client/package.json +++ b/client/package.json @@ -24,8 +24,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.4.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", @@ -37,8 +37,8 @@ "clsx": "^2.1.1", "cmdk": "^1.0.4", "lucide-react": "^0.447.0", - "prismjs": "^1.29.0", "pkce-challenge": "^4.1.0", + "prismjs": "^1.29.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", @@ -57,6 +57,7 @@ "@types/serve-handler": "^6.1.4", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", + "co": "^4.6.0", "eslint": "^9.11.1", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 9e865f9..5237513 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -11,12 +11,15 @@ export type JsonValue = | number | boolean | null + | undefined | JsonValue[] | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object"; + type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; description?: string; + required?: boolean; + default?: JsonValue; properties?: Record; items?: JsonSchemaType; }; @@ -105,21 +108,61 @@ const DynamicJsonForm = ({ switch (propSchema.type) { case "string": + return ( + { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + handleFieldChange(path, val); + } + }} + placeholder={propSchema.description} + required={propSchema.required} + /> + ); case "number": + return ( + { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = Number(val); + if (!isNaN(num)) { + handleFieldChange(path, num); + } + } + }} + placeholder={propSchema.description} + required={propSchema.required} + /> + ); case "integer": return ( - handleFieldChange( - path, - propSchema.type === "string" - ? e.target.value - : Number(e.target.value), - ) - } + type="number" + step="1" + value={(currentValue as number)?.toString() ?? ""} + onChange={(e) => { + const val = e.target.value; + if (!val && !propSchema.required) { + handleFieldChange(path, undefined); + } else { + const num = Number(val); + if (!isNaN(num) && Number.isInteger(num)) { + handleFieldChange(path, num); + } + } + }} placeholder={propSchema.description} + required={propSchema.required} /> ); case "boolean": @@ -129,6 +172,7 @@ const DynamicJsonForm = ({ checked={(currentValue as boolean) ?? false} onChange={(e) => handleFieldChange(path, e.target.checked)} className="w-4 h-4" + required={propSchema.required} /> ); case "object": { @@ -216,9 +260,12 @@ const DynamicJsonForm = ({ variant="outline" size="sm" onClick={() => { + const defaultValue = generateDefaultValue( + propSchema.items as JsonSchemaType + ); handleFieldChange(path, [ ...arrayValue, - generateDefaultValue(propSchema.items as JsonSchemaType), + defaultValue ?? null ]); }} title={ diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 0a10af4..a238798 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -7,40 +7,42 @@ import { JsonSchemaType } from "../../components/DynamicJsonForm"; describe("generateDefaultValue", () => { test("generates default string", () => { - expect(generateDefaultValue({ type: "string" })).toBe(""); + expect(generateDefaultValue({ type: "string", required: true })).toBe(""); }); test("generates default number", () => { - expect(generateDefaultValue({ type: "number" })).toBe(0); + expect(generateDefaultValue({ type: "number", required: true })).toBe(0); }); test("generates default integer", () => { - expect(generateDefaultValue({ type: "integer" })).toBe(0); + expect(generateDefaultValue({ type: "integer", required: true })).toBe(0); }); test("generates default boolean", () => { - expect(generateDefaultValue({ type: "boolean" })).toBe(false); + expect(generateDefaultValue({ type: "boolean", required: true })).toBe(false); }); test("generates default array", () => { - expect(generateDefaultValue({ type: "array" })).toEqual([]); + expect(generateDefaultValue({ type: "array", required: true })).toEqual([]); }); test("generates default empty object", () => { - expect(generateDefaultValue({ type: "object" })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: true })).toEqual({}); }); test("generates default null for unknown types", () => { - expect(generateDefaultValue({ type: "unknown" as any })).toBe(null); + // @ts-expect-error Testing with invalid type + expect(generateDefaultValue({ type: "unknown", required: true })).toBe(null); }); test("generates object with properties", () => { const schema: JsonSchemaType = { type: "object", + required: true, properties: { - name: { type: "string" }, - age: { type: "number" }, - isActive: { type: "boolean" }, + name: { type: "string", required: true }, + age: { type: "number", required: true }, + isActive: { type: "boolean", required: true }, }, }; expect(generateDefaultValue(schema)).toEqual({ @@ -53,15 +55,18 @@ describe("generateDefaultValue", () => { test("handles nested objects", () => { const schema: JsonSchemaType = { type: "object", + required: true, properties: { user: { type: "object", + required: true, properties: { - name: { type: "string" }, + name: { type: "string", required: true }, address: { type: "object", + required: true, properties: { - city: { type: "string" }, + city: { type: "string", required: true }, }, }, }, @@ -135,7 +140,8 @@ describe("validateValueAgainstSchema", () => { test("returns true for unknown types", () => { expect( - validateValueAgainstSchema("anything", { type: "unknown" as any }), + // @ts-expect-error Testing with invalid type + validateValueAgainstSchema("anything", { type: "unknown" }), ).toBe(true); }); }); diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index b3c8e48..9c438e7 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -4,9 +4,19 @@ import { JsonObject } from "./jsonPathUtils"; /** * Generates a default value based on a JSON schema type * @param schema The JSON schema definition - * @returns A default value matching the schema type + * @returns A default value matching the schema type, or null for non-required fields */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { + + if ("default" in schema) { + // Ensure we don't return undefined even if schema.default is undefined + return schema.default === undefined ? null : schema.default; + } + + if (!schema.required) { + return null; + } + switch (schema.type) { case "string": return ""; @@ -18,12 +28,15 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { case "array": return []; case "object": { + if (!schema.properties) return {}; + const obj: JsonObject = {}; - if (schema.properties) { - Object.entries(schema.properties).forEach(([key, prop]) => { - obj[key] = generateDefaultValue(prop); + Object.entries(schema.properties) + .filter(([, prop]) => prop.required) + .forEach(([key, prop]) => { + const value = generateDefaultValue(prop); + obj[key] = value; }); - } return obj; } default: @@ -53,13 +66,19 @@ export function validateValueAgainstSchema( value: JsonValue, schema: JsonSchemaType, ): boolean { + // Handle undefined values for non-required fields + if (value === undefined && !schema.required) { + return true; + } + // Basic type validation switch (schema.type) { case "string": return typeof value === "string"; case "number": - case "integer": return typeof value === "number"; + case "integer": + return typeof value === "number" && Number.isInteger(value); case "boolean": return typeof value === "boolean"; case "array": @@ -68,6 +87,8 @@ export function validateValueAgainstSchema( return ( typeof value === "object" && value !== null && !Array.isArray(value) ); + case "null": + return value === null; default: return true; } diff --git a/package-lock.json b/package-lock.json index 90af99a..a7df022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "@types/serve-handler": "^6.1.4", "@vitejs/plugin-react": "^4.3.2", "autoprefixer": "^10.4.20", + "co": "^4.6.0", "eslint": "^9.11.1", "eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-refresh": "^0.4.12", @@ -4800,6 +4801,17 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", From 82bbe58a46aba04c87d903621a9f808d94bb2917 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Thu, 27 Feb 2025 22:08:04 -0700 Subject: [PATCH 15/60] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 13 ++++++++++--- client/src/utils/__tests__/schemaUtils.test.ts | 12 +++++++++--- client/src/utils/schemaUtils.ts | 3 +-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 5237513..15cfde6 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -16,7 +16,14 @@ export type JsonValue = | { [key: string]: JsonValue }; export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null"; + type: + | "string" + | "number" + | "integer" + | "boolean" + | "array" + | "object" + | "null"; description?: string; required?: boolean; default?: JsonValue; @@ -261,11 +268,11 @@ const DynamicJsonForm = ({ size="sm" onClick={() => { const defaultValue = generateDefaultValue( - propSchema.items as JsonSchemaType + propSchema.items as JsonSchemaType, ); handleFieldChange(path, [ ...arrayValue, - defaultValue ?? null + defaultValue ?? null, ]); }} title={ diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index a238798..4eba268 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -19,7 +19,9 @@ describe("generateDefaultValue", () => { }); test("generates default boolean", () => { - expect(generateDefaultValue({ type: "boolean", required: true })).toBe(false); + expect(generateDefaultValue({ type: "boolean", required: true })).toBe( + false, + ); }); test("generates default array", () => { @@ -27,12 +29,16 @@ describe("generateDefaultValue", () => { }); test("generates default empty object", () => { - expect(generateDefaultValue({ type: "object", required: true })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: true })).toEqual( + {}, + ); }); test("generates default null for unknown types", () => { // @ts-expect-error Testing with invalid type - expect(generateDefaultValue({ type: "unknown", required: true })).toBe(null); + expect(generateDefaultValue({ type: "unknown", required: true })).toBe( + null, + ); }); test("generates object with properties", () => { diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 9c438e7..ab4c920 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -7,7 +7,6 @@ import { JsonObject } from "./jsonPathUtils"; * @returns A default value matching the schema type, or null for non-required fields */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { - if ("default" in schema) { // Ensure we don't return undefined even if schema.default is undefined return schema.default === undefined ? null : schema.default; @@ -29,7 +28,7 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { return []; case "object": { if (!schema.properties) return {}; - + const obj: JsonObject = {}; Object.entries(schema.properties) .filter(([, prop]) => prop.required) From a1eb343b7924253b090ea4ce2fbf8f31b327381e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 06:26:04 -0700 Subject: [PATCH 16/60] Remove unused function plus tests --- .../src/utils/__tests__/schemaUtils.test.ts | 41 ------------------- client/src/utils/schemaUtils.ts | 38 ----------------- 2 files changed, 79 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 4eba268..20e7832 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,7 +1,6 @@ import { generateDefaultValue, formatFieldLabel, - validateValueAgainstSchema, } from "../schemaUtils"; import { JsonSchemaType } from "../../components/DynamicJsonForm"; @@ -111,43 +110,3 @@ describe("formatFieldLabel", () => { expect(formatFieldLabel("")).toBe(""); }); }); - -describe("validateValueAgainstSchema", () => { - test("validates string type", () => { - expect(validateValueAgainstSchema("test", { type: "string" })).toBe(true); - expect(validateValueAgainstSchema(123, { type: "string" })).toBe(false); - }); - - test("validates number type", () => { - expect(validateValueAgainstSchema(123, { type: "number" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "number" })).toBe(false); - }); - - test("validates integer type", () => { - expect(validateValueAgainstSchema(123, { type: "integer" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "integer" })).toBe(false); - }); - - test("validates boolean type", () => { - expect(validateValueAgainstSchema(true, { type: "boolean" })).toBe(true); - expect(validateValueAgainstSchema("test", { type: "boolean" })).toBe(false); - }); - - test("validates array type", () => { - expect(validateValueAgainstSchema([], { type: "array" })).toBe(true); - expect(validateValueAgainstSchema({}, { type: "array" })).toBe(false); - }); - - test("validates object type", () => { - expect(validateValueAgainstSchema({}, { type: "object" })).toBe(true); - expect(validateValueAgainstSchema([], { type: "object" })).toBe(false); - expect(validateValueAgainstSchema("test", { type: "object" })).toBe(false); - }); - - test("returns true for unknown types", () => { - expect( - // @ts-expect-error Testing with invalid type - validateValueAgainstSchema("anything", { type: "unknown" }), - ).toBe(true); - }); -}); diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index ab4c920..88a9b53 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -54,41 +54,3 @@ export function formatFieldLabel(key: string): string { .replace(/_/g, " ") // Replace underscores with spaces .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter } - -/** - * Validates if a value conforms to a JSON schema - * @param value The value to validate - * @param schema The JSON schema to validate against - * @returns True if valid, false otherwise - */ -export function validateValueAgainstSchema( - value: JsonValue, - schema: JsonSchemaType, -): boolean { - // Handle undefined values for non-required fields - if (value === undefined && !schema.required) { - return true; - } - - // Basic type validation - switch (schema.type) { - case "string": - return typeof value === "string"; - case "number": - return typeof value === "number"; - case "integer": - return typeof value === "number" && Number.isInteger(value); - case "boolean": - return typeof value === "boolean"; - case "array": - return Array.isArray(value); - case "object": - return ( - typeof value === "object" && value !== null && !Array.isArray(value) - ); - case "null": - return value === null; - default: - return true; - } -} From 0e50b68f96b1c1c693d2a63edaba1dbe96a1af46 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 06:30:23 -0700 Subject: [PATCH 17/60] Fix formatting --- client/src/utils/__tests__/schemaUtils.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 20e7832..cd9c288 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -1,7 +1,4 @@ -import { - generateDefaultValue, - formatFieldLabel, -} from "../schemaUtils"; +import { generateDefaultValue, formatFieldLabel } from "../schemaUtils"; import { JsonSchemaType } from "../../components/DynamicJsonForm"; describe("generateDefaultValue", () => { From 36aa7316ea224ee7362a707a635e1bb360587b44 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 07:31:26 -0700 Subject: [PATCH 18/60] Fix issue where array type defaults to object --- client/src/components/ToolsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 777db80..71c2920 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -214,7 +214,7 @@ const ToolsTab = ({ description: prop.description, items: prop.items, }} - value={(params[key] as JsonValue) ?? {}} + value={(params[key] as JsonValue) ?? (prop.type === "array" ? [] : {})} onChange={(newValue: JsonValue) => { setParams({ ...params, From e7f55f083faca237330ec097afb7209794ee1397 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 07:34:01 -0700 Subject: [PATCH 19/60] Fix formatting --- client/src/components/ToolsTab.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 71c2920..810242f 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -214,7 +214,10 @@ const ToolsTab = ({ description: prop.description, items: prop.items, }} - value={(params[key] as JsonValue) ?? (prop.type === "array" ? [] : {})} + value={ + (params[key] as JsonValue) ?? + (prop.type === "array" ? [] : {}) + } onChange={(newValue: JsonValue) => { setParams({ ...params, From b01e38665957c40b06de8fb059613123031ea439 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 09:06:51 -0700 Subject: [PATCH 20/60] Always use JSON mode if the schema type is object and has no properties --- .gitignore | 1 + client/src/components/DynamicJsonForm.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index 0f4928e..9f1d63b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ server/build client/dist client/tsconfig.app.tsbuildinfo client/tsconfig.node.tsbuildinfo +.vscode diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 15cfde6..a57ccba 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -307,6 +307,16 @@ const DynamicJsonForm = ({ } }; + const shouldUseJsonMode = + schema.type === "object" && + (!schema.properties || Object.keys(schema.properties).length === 0); + + useEffect(() => { + if (shouldUseJsonMode && !isJsonMode) { + setIsJsonMode(true); + } + }, [shouldUseJsonMode, isJsonMode]); + return (
From 06773bb6dd09f570d73fc1188502721764e73506 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Fri, 28 Feb 2025 09:13:33 -0700 Subject: [PATCH 21/60] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index a57ccba..8e6f8c2 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -307,8 +307,8 @@ const DynamicJsonForm = ({ } }; - const shouldUseJsonMode = - schema.type === "object" && + const shouldUseJsonMode = + schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0); useEffect(() => { From f9b105c0efe274259ac3a405958f9699611ced95 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 4 Mar 2025 20:54:13 -0700 Subject: [PATCH 22/60] Use debounce instead --- client/src/components/DynamicJsonForm.tsx | 38 ++++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 8e6f8c2..0e2ee48 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -51,6 +51,28 @@ const DynamicJsonForm = ({ JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); + // Create a ref to store the timeout ID + const timeoutRef = useRef>(); + + // Create a debounced function to update parent state + const debouncedUpdateParent = useCallback((jsonString: string) => { + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set a new timeout + timeoutRef.current = setTimeout(() => { + try { + const parsed = JSON.parse(jsonString); + onChange(parsed); + setJsonError(undefined); + } catch { + // Don't set error during normal typing + } + }, 300); + }, [onChange, setJsonError]); + // Update rawJsonValue when value prop changes useEffect(() => { if (!isJsonMode) { @@ -329,17 +351,11 @@ const DynamicJsonForm = ({ { + // Always update local state setRawJsonValue(newValue); - try { - if (/^\s*[{[].*[}\]]\s*$/.test(newValue)) { - const parsed = JSON.parse(newValue); - onChange(parsed); - setJsonError(undefined); - } - } catch { - // Don't set an error during typing - that will happen when the user - // tries to save or submit the form - } + + // Use the debounced function to attempt parsing and updating parent + debouncedUpdateParent(newValue); }} error={jsonError} /> From 00836dbf9edce12ef64f1cd6add968b1b4102d3f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 4 Mar 2025 20:55:57 -0700 Subject: [PATCH 23/60] Fix formatting --- client/src/components/DynamicJsonForm.tsx | 37 ++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 0e2ee48..430b7ba 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -55,23 +55,26 @@ const DynamicJsonForm = ({ const timeoutRef = useRef>(); // Create a debounced function to update parent state - const debouncedUpdateParent = useCallback((jsonString: string) => { - // Clear any existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Set a new timeout - timeoutRef.current = setTimeout(() => { - try { - const parsed = JSON.parse(jsonString); - onChange(parsed); - setJsonError(undefined); - } catch { - // Don't set error during normal typing + const debouncedUpdateParent = useCallback( + (jsonString: string) => { + // Clear any existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); } - }, 300); - }, [onChange, setJsonError]); + + // Set a new timeout + timeoutRef.current = setTimeout(() => { + try { + const parsed = JSON.parse(jsonString); + onChange(parsed); + setJsonError(undefined); + } catch { + // Don't set error during normal typing + } + }, 300); + }, + [onChange, setJsonError], + ); // Update rawJsonValue when value prop changes useEffect(() => { @@ -353,7 +356,7 @@ const DynamicJsonForm = ({ onChange={(newValue) => { // Always update local state setRawJsonValue(newValue); - + // Use the debounced function to attempt parsing and updating parent debouncedUpdateParent(newValue); }} From b9b116a5f254275225b6235832e21b2a2f5e1bde Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 5 Mar 2025 07:55:15 -0700 Subject: [PATCH 24/60] Remove duplicate react-dialog from merge --- client/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/client/package.json b/client/package.json index 1d40925..d45cdc7 100644 --- a/client/package.json +++ b/client/package.json @@ -26,7 +26,6 @@ "@modelcontextprotocol/sdk": "^1.6.1", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", From 04a90e8d892b4381ba1f50ee3c4323051ac8378c Mon Sep 17 00:00:00 2001 From: Ryan Rozich Date: Mon, 10 Mar 2025 07:50:02 -0500 Subject: [PATCH 25/60] Fix environment variable parsing to handle values with equals signs --- bin/cli.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 94348fb..c1ada65 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,9 +27,21 @@ async function main() { } if (parsingFlags && arg === "-e" && i + 1 < args.length) { - const [key, value] = args[++i].split("="); - if (key && value) { + // Parse environment variables passed with -e flag + // Format: -e KEY=VALUE + // Example: -e MY_VAR=my_value + // Handles env vars where VALUE can contain "=" signs (e.g., var1=sample=value) + const envVar = args[++i]; + const equalsIndex = envVar.indexOf("="); + + if (equalsIndex !== -1) { + // Split only at the first equals sign + const key = envVar.substring(0, equalsIndex); + const value = envVar.substring(equalsIndex + 1); envVars[key] = value; + } else { + // No equals sign found, use the whole string as key with empty value + envVars[envVar] = ""; } } else if (!command) { command = arg; @@ -113,4 +125,4 @@ main() .catch((e) => { console.error(e); process.exit(1); - }); + }); \ No newline at end of file From fe8b1ee88b7e8a02edefc73cfff7f6722a90284f Mon Sep 17 00:00:00 2001 From: Ryan Rozich Date: Tue, 11 Mar 2025 22:43:44 -0500 Subject: [PATCH 26/60] remove comments --- bin/cli.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index c1ada65..80d42b6 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,20 +27,14 @@ async function main() { } if (parsingFlags && arg === "-e" && i + 1 < args.length) { - // Parse environment variables passed with -e flag - // Format: -e KEY=VALUE - // Example: -e MY_VAR=my_value - // Handles env vars where VALUE can contain "=" signs (e.g., var1=sample=value) const envVar = args[++i]; const equalsIndex = envVar.indexOf("="); if (equalsIndex !== -1) { - // Split only at the first equals sign const key = envVar.substring(0, equalsIndex); const value = envVar.substring(equalsIndex + 1); envVars[key] = value; } else { - // No equals sign found, use the whole string as key with empty value envVars[envVar] = ""; } } else if (!command) { From 60c4645eafdfeff02e4642958e28824646e1700c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Wed, 12 Mar 2025 08:20:11 -0700 Subject: [PATCH 27/60] Fix formatting --- bin/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 80d42b6..8e0318d 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -29,7 +29,7 @@ async function main() { if (parsingFlags && arg === "-e" && i + 1 < args.length) { const envVar = args[++i]; const equalsIndex = envVar.indexOf("="); - + if (equalsIndex !== -1) { const key = envVar.substring(0, equalsIndex); const value = envVar.substring(equalsIndex + 1); @@ -119,4 +119,4 @@ main() .catch((e) => { console.error(e); process.exit(1); - }); \ No newline at end of file + }); From d70e6dc0e8a5d8ffb3a84bfdfa04131f7af2dfd8 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 13 Mar 2025 15:17:17 -0400 Subject: [PATCH 28/60] In useConnection.ts, - import LoggingMessageNotificationSchema - set onNotification as notification handler for LoggingMessageNotificationSchema --- client/src/lib/hooks/useConnection.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index ea9e05a..19b0e9d 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -10,6 +10,7 @@ import { ListRootsRequestSchema, ProgressNotificationSchema, ResourceUpdatedNotificationSchema, + LoggingMessageNotificationSchema, Request, Result, ServerCapabilities, @@ -258,6 +259,11 @@ export function useConnection({ ResourceUpdatedNotificationSchema, onNotification, ); + + client.setNotificationHandler( + LoggingMessageNotificationSchema, + onNotification, + ); } if (onStdErrNotification) { From 090b7efdea0162dcaeeef29ae21ab6cf996a14b2 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Tue, 11 Mar 2025 10:36:21 +0800 Subject: [PATCH 29/60] add sse accept header --- server/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index 2874b45..aa8fa9a 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -66,7 +66,9 @@ const createTransport = async (req: express.Request) => { return transport; } else if (transportType === "sse") { const url = query.url as string; - const headers: HeadersInit = {}; + const headers: HeadersInit = { + 'Accept': 'text/event-stream', + }; for (const key of SSE_HEADERS_PASSTHROUGH) { if (req.headers[key] === undefined) { continue; From aeaf32fa45230178ff2a88097110e6dbd9c81075 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Sat, 15 Mar 2025 12:51:44 +0800 Subject: [PATCH 30/60] fix --- server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index aa8fa9a..2a4fe65 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -67,7 +67,7 @@ const createTransport = async (req: express.Request) => { } else if (transportType === "sse") { const url = query.url as string; const headers: HeadersInit = { - 'Accept': 'text/event-stream', + Accept: "text/event-stream", }; for (const key of SSE_HEADERS_PASSTHROUGH) { if (req.headers[key] === undefined) { From 51049522399322971edf2390471bdc2763a97c01 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 15 Mar 2025 16:37:10 -0400 Subject: [PATCH 31/60] Add log level setting in UI * This fixes #188 * In App.tsx - import LoggingLevel from sdk - add [logLevel, setLogLevel] useState with value of type LoggingLevel initialized to "debug" - add useEffect that stores the new logLevel in localStorage as "logLevel" - added sendLogLevelRequest function that takes a level argument of type LoggingLevel and sends the appropriate request. It calls setLogLevel when done, to update the local UI - pass logLevel and sendLogLevelRequest to Sidebar component as props * In Sidebar.tsx - Import LoggingLevel and LoggingLevelSchema from sdk - add props and prop types for logLevel and sendLogLevelRequest and loggingSupported - add Select component populated with the enum values of LoggingLevelSchema, shown only if loggingSupported is true and connectionStatus is "connected" * --- client/src/App.tsx | 18 +++++++++++++++++ client/src/components/Sidebar.tsx | 32 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5650954..0a699b1 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -15,6 +15,7 @@ import { Root, ServerNotification, Tool, + LoggingLevel, } from "@modelcontextprotocol/sdk/types.js"; import React, { Suspense, useEffect, useRef, useState } from "react"; import { useConnection } from "./lib/hooks/useConnection"; @@ -91,6 +92,7 @@ const App = () => { (localStorage.getItem("lastTransportType") as "stdio" | "sse") || "stdio" ); }); + const [logLevel, setLogLevel] = useState("debug"); const [notifications, setNotifications] = useState([]); const [stdErrNotifications, setStdErrNotifications] = useState< StdErrNotification[] @@ -412,6 +414,17 @@ const App = () => { await sendNotification({ method: "notifications/roots/list_changed" }); }; + const sendLogLevelRequest = async (level: LoggingLevel) => { + await makeRequest( + { + method: "logging/setLevel" as const, + params: { level }, + }, + z.object({}), + ); + setLogLevel(level); + }; + return (
{ setBearerToken={setBearerToken} onConnect={connectMcpServer} stdErrNotifications={stdErrNotifications} + logLevel={logLevel} + sendLogLevelRequest={sendLogLevelRequest} + loggingSupported={ + !!serverCapabilities?.logging || false + } />
diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 48c6ff2..aa930e5 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -19,6 +19,10 @@ import { SelectValue, } from "@/components/ui/select"; import { StdErrNotification } from "@/lib/notificationTypes"; +import { + LoggingLevel, + LoggingLevelSchema, +} from "@modelcontextprotocol/sdk/types.js"; import useTheme from "../lib/useTheme"; import { version } from "../../../package.json"; @@ -39,6 +43,9 @@ interface SidebarProps { setBearerToken: (token: string) => void; onConnect: () => void; stdErrNotifications: StdErrNotification[]; + logLevel: LoggingLevel; + sendLogLevelRequest: (level: LoggingLevel) => void; + loggingSupported: boolean; } const Sidebar = ({ @@ -57,6 +64,9 @@ const Sidebar = ({ setBearerToken, onConnect, stdErrNotifications, + logLevel, + sendLogLevelRequest, + loggingSupported, }: SidebarProps) => { const [theme, setTheme] = useTheme(); const [showEnvVars, setShowEnvVars] = useState(false); @@ -290,6 +300,28 @@ const Sidebar = ({ : "Disconnected"}
+ + {loggingSupported && connectionStatus === "connected" && ( +
+ + +
+ )} + {stdErrNotifications.length > 0 && ( <>
From d8b5bdb6135d70dbd18a5fa5ad33e870c9187da3 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 15 Mar 2025 17:03:22 -0400 Subject: [PATCH 32/60] Run prettier --- client/src/App.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 0a699b1..1ae5250 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -445,9 +445,7 @@ const App = () => { stdErrNotifications={stdErrNotifications} logLevel={logLevel} sendLogLevelRequest={sendLogLevelRequest} - loggingSupported={ - !!serverCapabilities?.logging || false - } + loggingSupported={!!serverCapabilities?.logging || false} />
From a3740c4798ad4fcbb7e661dcb124d0a5fb270b62 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:24:34 -0700 Subject: [PATCH 33/60] Remove unneeded DynamicJsonForm.tsx --- client/src/utils/__mocks__/DynamicJsonForm.ts | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 client/src/utils/__mocks__/DynamicJsonForm.ts diff --git a/client/src/utils/__mocks__/DynamicJsonForm.ts b/client/src/utils/__mocks__/DynamicJsonForm.ts deleted file mode 100644 index 83a98e8..0000000 --- a/client/src/utils/__mocks__/DynamicJsonForm.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Mock for DynamicJsonForm that only exports the types needed for tests -export type JsonValue = - | string - | number - | boolean - | null - | JsonValue[] - | { [key: string]: JsonValue }; - -export type JsonSchemaType = { - type: "string" | "number" | "integer" | "boolean" | "array" | "object"; - description?: string; - properties?: Record; - items?: JsonSchemaType; -}; - -// Default export is not used in the tests -export default {}; From 7c4ed6abca9a77f8e838c0a5b134295b6954e11b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:24:48 -0700 Subject: [PATCH 34/60] Use working-directory instead of cd to client --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb0aa6d..1f13444 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,9 +27,8 @@ jobs: - run: npm install --no-package-lock - name: Run client tests - run: | - cd client - npm test + working-directory: ./client + run: npm test - run: npm run build From e1b015e40d6c0c1628950f0d23bbe5da980541a8 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:28:07 -0700 Subject: [PATCH 35/60] Add comments explaining extra parsing logic --- client/src/components/DynamicJsonForm.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 430b7ba..7da1762 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -46,15 +46,17 @@ const DynamicJsonForm = ({ }: DynamicJsonFormProps) => { const [isJsonMode, setIsJsonMode] = useState(false); const [jsonError, setJsonError] = useState(); - // Add state for storing raw JSON value + // Store the raw JSON string to allow immediate feedback during typing + // while deferring parsing until the user stops typing const [rawJsonValue, setRawJsonValue] = useState( JSON.stringify(value ?? generateDefaultValue(schema), null, 2), ); - // Create a ref to store the timeout ID + // Use a ref to manage debouncing timeouts to avoid parsing JSON + // on every keystroke which would be inefficient and error-prone const timeoutRef = useRef>(); - // Create a debounced function to update parent state + // Debounce JSON parsing and parent updates to handle typing gracefully const debouncedUpdateParent = useCallback( (jsonString: string) => { // Clear any existing timeout @@ -146,6 +148,8 @@ const DynamicJsonForm = ({ value={(currentValue as string) ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required fields by setting undefined + // This preserves the distinction between empty string and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { @@ -163,6 +167,8 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required number fields + // This preserves the distinction between 0 and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { @@ -184,10 +190,13 @@ const DynamicJsonForm = ({ value={(currentValue as number)?.toString() ?? ""} onChange={(e) => { const val = e.target.value; + // Allow clearing non-required integer fields + // This preserves the distinction between 0 and unset if (!val && !propSchema.required) { handleFieldChange(path, undefined); } else { const num = Number(val); + // Only update if it's a valid integer if (!isNaN(num) && Number.isInteger(num)) { handleFieldChange(path, num); } From 50a65d0c7a16040611d8e5c601a26ad5ef9b098e Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:29:59 -0700 Subject: [PATCH 36/60] Use generateDefaultValue for object and array defaults --- client/src/components/ToolsTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 810242f..30764b2 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -6,6 +6,7 @@ import { Label } from "@/components/ui/label"; import { TabsContent } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import DynamicJsonForm, { JsonSchemaType, JsonValue } from "./DynamicJsonForm"; +import { generateDefaultValue } from "@/utils/schemaUtils"; import { ListToolsResult, Tool, @@ -215,8 +216,7 @@ const ToolsTab = ({ items: prop.items, }} value={ - (params[key] as JsonValue) ?? - (prop.type === "array" ? [] : {}) + (params[key] as JsonValue) ?? generateDefaultValue(prop) } onChange={(newValue: JsonValue) => { setParams({ From cae7c763582aae784132c381aaa158894b20d2ab Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:31:49 -0700 Subject: [PATCH 37/60] Fix formatting --- client/src/components/ToolsTab.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 30764b2..ee9a4f9 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -216,7 +216,8 @@ const ToolsTab = ({ items: prop.items, }} value={ - (params[key] as JsonValue) ?? generateDefaultValue(prop) + (params[key] as JsonValue) ?? + generateDefaultValue(prop) } onChange={(newValue: JsonValue) => { setParams({ From 28978ea24f8d5e9db2e66c02a500b5fc9a2e0759 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 16 Mar 2025 15:42:16 -0700 Subject: [PATCH 38/60] Update package lock after re-running npm install --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9b2854..21c9c30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.5.1", - "@modelcontextprotocol/inspector-server": "^0.5.1", + "@modelcontextprotocol/inspector-client": "^0.6.0", + "@modelcontextprotocol/inspector-server": "^0.6.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -31,7 +31,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -11298,7 +11298,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.5.1", + "version": "0.6.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", From 7ddba51b36c4eac7595903dd68c0da84a1636181 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 17 Mar 2025 06:36:35 -0700 Subject: [PATCH 39/60] Generate empty objects and arrays for non required object and array fields --- client/src/utils/__tests__/schemaUtils.test.ts | 14 ++++++++++++++ client/src/utils/schemaUtils.ts | 2 ++ 2 files changed, 16 insertions(+) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index cd9c288..00187cd 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -37,6 +37,20 @@ describe("generateDefaultValue", () => { ); }); + test("generates empty array for non-required array", () => { + expect(generateDefaultValue({ type: "array", required: false })).toEqual([]); + }); + + test("generates empty object for non-required object", () => { + expect(generateDefaultValue({ type: "object", required: false })).toEqual({}); + }); + + test("generates null for non-required primitive types", () => { + expect(generateDefaultValue({ type: "string", required: false })).toBe(null); + expect(generateDefaultValue({ type: "number", required: false })).toBe(null); + expect(generateDefaultValue({ type: "boolean", required: false })).toBe(null); + }); + test("generates object with properties", () => { const schema: JsonSchemaType = { type: "object", diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 88a9b53..819ac99 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -13,6 +13,8 @@ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { } if (!schema.required) { + if (schema.type === "array") return []; + if (schema.type === "object") return {}; return null; } From a85d5e70505cd5e6f519d170244d13983745ffca Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 17 Mar 2025 07:56:23 -0700 Subject: [PATCH 40/60] Fix formatting --- .../src/utils/__tests__/schemaUtils.test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index 00187cd..e164d0d 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -38,17 +38,27 @@ describe("generateDefaultValue", () => { }); test("generates empty array for non-required array", () => { - expect(generateDefaultValue({ type: "array", required: false })).toEqual([]); + expect(generateDefaultValue({ type: "array", required: false })).toEqual( + [], + ); }); test("generates empty object for non-required object", () => { - expect(generateDefaultValue({ type: "object", required: false })).toEqual({}); + expect(generateDefaultValue({ type: "object", required: false })).toEqual( + {}, + ); }); test("generates null for non-required primitive types", () => { - expect(generateDefaultValue({ type: "string", required: false })).toBe(null); - expect(generateDefaultValue({ type: "number", required: false })).toBe(null); - expect(generateDefaultValue({ type: "boolean", required: false })).toBe(null); + expect(generateDefaultValue({ type: "string", required: false })).toBe( + null, + ); + expect(generateDefaultValue({ type: "number", required: false })).toBe( + null, + ); + expect(generateDefaultValue({ type: "boolean", required: false })).toBe( + null, + ); }); test("generates object with properties", () => { From c463dc58c2cd183b96d5470d92c929442aade203 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 18 Mar 2025 06:23:25 -0700 Subject: [PATCH 41/60] Simplify check for defaults and add another test --- client/src/utils/__tests__/schemaUtils.test.ts | 6 ++++++ client/src/utils/schemaUtils.ts | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts index e164d0d..6462e12 100644 --- a/client/src/utils/__tests__/schemaUtils.test.ts +++ b/client/src/utils/__tests__/schemaUtils.test.ts @@ -108,6 +108,12 @@ describe("generateDefaultValue", () => { }, }); }); + + test("uses schema default value when provided", () => { + expect(generateDefaultValue({ type: "string", default: "test" })).toBe( + "test", + ); + }); }); describe("formatFieldLabel", () => { diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 819ac99..686030f 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -8,8 +8,7 @@ import { JsonObject } from "./jsonPathUtils"; */ export function generateDefaultValue(schema: JsonSchemaType): JsonValue { if ("default" in schema) { - // Ensure we don't return undefined even if schema.default is undefined - return schema.default === undefined ? null : schema.default; + return schema.default; } if (!schema.required) { From 029e482e05a57f5610662d080c5aba59f2dd762c Mon Sep 17 00:00:00 2001 From: Nathan Arseneau Date: Wed, 19 Mar 2025 20:15:14 -0400 Subject: [PATCH 42/60] fix: set default value for input fields in ToolsTab component --- client/src/components/ToolsTab.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 777db80..dcfae44 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -229,6 +229,7 @@ const ToolsTab = ({ id={key} name={key} placeholder={prop.description} + value={(params[key] as string) ?? ""} onChange={(e) => setParams({ ...params, From 4fdbcee7060f748fe68271414bd9422ee4674fda Mon Sep 17 00:00:00 2001 From: jazminliu Date: Thu, 20 Mar 2025 22:04:59 +0800 Subject: [PATCH 43/60] Update Vite configuration to enable host access and fix proxy server URL to use the current hostname. --- client/src/App.tsx | 2 +- client/vite.config.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5650954..fde9d6a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -47,7 +47,7 @@ import ToolsTab from "./components/ToolsTab"; const params = new URLSearchParams(window.location.search); const PROXY_PORT = params.get("proxyPort") ?? "3000"; -const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`; +const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const App = () => { // Handle OAuth callback route diff --git a/client/vite.config.ts b/client/vite.config.ts index b3d0f45..fa817c7 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -5,7 +5,9 @@ import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - server: {}, + server: { + host: true, + }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), From 3488bdb613fa8d0a5f7aed01ec0911f57c3b8805 Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Tue, 18 Mar 2025 10:07:43 +0000 Subject: [PATCH 44/60] feat: Add utility function to escape Unicode characters in tool results --- client/src/components/ToolsTab.tsx | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index ee9a4f9..ff4044f 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -8,15 +8,31 @@ import { Textarea } from "@/components/ui/textarea"; import DynamicJsonForm, { JsonSchemaType, JsonValue } from "./DynamicJsonForm"; import { generateDefaultValue } from "@/utils/schemaUtils"; import { + CallToolResultSchema, + CompatibilityCallToolResult, ListToolsResult, Tool, - CallToolResultSchema, } from "@modelcontextprotocol/sdk/types.js"; import { AlertCircle, Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; -import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js"; +// Utility function to escape Unicode characters +function escapeUnicode(obj: any): string { + return JSON.stringify( + obj, + (_key: string, value) => { + if (typeof value === "string") { + // Replace non-ASCII characters with their Unicode escape sequences + return value.replace(/[^\0-\x7F]/g, (char) => { + return "\\u" + ("0000" + char.charCodeAt(0).toString(16)).slice(-4); + }); + } + return value; + }, + 2, + ); +} const ToolsTab = ({ tools, @@ -54,7 +70,7 @@ const ToolsTab = ({ <>

Invalid Tool Result:

-              {JSON.stringify(toolResult, null, 2)}
+              {escapeUnicode(toolResult)}
             

Errors:

{parsedResult.error.errors.map((error, idx) => ( @@ -62,7 +78,7 @@ const ToolsTab = ({ key={idx} className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64" > - {JSON.stringify(error, null, 2)} + {escapeUnicode(error)} ))} @@ -101,7 +117,7 @@ const ToolsTab = ({ ) : (
-                    {JSON.stringify(item.resource, null, 2)}
+                    {escapeUnicode(item.resource)}
                   
))}
@@ -113,7 +129,7 @@ const ToolsTab = ({ <>

Tool Result (Legacy):

-            {JSON.stringify(toolResult.toolResult, null, 2)}
+            {escapeUnicode(toolResult.toolResult)}
           
); From af44efb2362ed399ede821583415d2fa58d3c9f6 Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Fri, 21 Mar 2025 22:30:34 +0000 Subject: [PATCH 45/60] chore: extract utils escapeUnicode --- client/src/components/ToolsTab.tsx | 18 +------------ .../src/utils/__tests__/escapeUnicode.test.ts | 27 +++++++++++++++++++ client/src/utils/escapeUnicode.ts | 16 +++++++++++ package-lock.json | 1 + package.json | 1 + 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 client/src/utils/__tests__/escapeUnicode.test.ts create mode 100644 client/src/utils/escapeUnicode.ts diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index ff4044f..82ebdb0 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -16,23 +16,7 @@ import { import { AlertCircle, Send } from "lucide-react"; import { useEffect, useState } from "react"; import ListPane from "./ListPane"; - -// Utility function to escape Unicode characters -function escapeUnicode(obj: any): string { - return JSON.stringify( - obj, - (_key: string, value) => { - if (typeof value === "string") { - // Replace non-ASCII characters with their Unicode escape sequences - return value.replace(/[^\0-\x7F]/g, (char) => { - return "\\u" + ("0000" + char.charCodeAt(0).toString(16)).slice(-4); - }); - } - return value; - }, - 2, - ); -} +import { escapeUnicode } from "@/utils/escapeUnicode"; const ToolsTab = ({ tools, diff --git a/client/src/utils/__tests__/escapeUnicode.test.ts b/client/src/utils/__tests__/escapeUnicode.test.ts new file mode 100644 index 0000000..b2eb130 --- /dev/null +++ b/client/src/utils/__tests__/escapeUnicode.test.ts @@ -0,0 +1,27 @@ +import { escapeUnicode } from "../escapeUnicode"; + +describe("escapeUnicode", () => { + it("should escape Unicode characters in a string", () => { + const input = { text: "你好世界" }; + const expected = '{\n "text": "\\\\u4f60\\\\u597d\\\\u4e16\\\\u754c"\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle empty strings", () => { + const input = { text: "" }; + const expected = '{\n "text": ""\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle null and undefined values", () => { + const input = { text: null, value: undefined }; + const expected = '{\n "text": null\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); + + it("should handle numbers and booleans", () => { + const input = { number: 123, boolean: true }; + const expected = '{\n "number": 123,\n "boolean": true\n}'; + expect(escapeUnicode(input)).toBe(expected); + }); +}); diff --git a/client/src/utils/escapeUnicode.ts b/client/src/utils/escapeUnicode.ts new file mode 100644 index 0000000..ed6fbd9 --- /dev/null +++ b/client/src/utils/escapeUnicode.ts @@ -0,0 +1,16 @@ +// Utility function to escape Unicode characters +export function escapeUnicode(obj: unknown): string { + return JSON.stringify( + obj, + (_key: string, value) => { + if (typeof value === "string") { + // Replace non-ASCII characters with their Unicode escape sequences + return value.replace(/[^\0-\x7F]/g, (char) => { + return "\\u" + ("0000" + char.charCodeAt(0).toString(16)).slice(-4); + }); + } + return value; + }, + 2, + ); +} diff --git a/package-lock.json b/package-lock.json index 21c9c30..68929c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "mcp-inspector": "bin/cli.js" }, "devDependencies": { + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" diff --git a/package.json b/package.json index 3f40d2e..e3bce92 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "ts-node": "^10.9.2" }, "devDependencies": { + "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" From 210975e38531b68be7af1eae72dee7aa0c0c5ab0 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 12:44:30 -0700 Subject: [PATCH 46/60] Add test dependencies --- client/package.json | 4 +- package-lock.json | 272 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index 0471765..c3e4ed3 100644 --- a/client/package.json +++ b/client/package.json @@ -24,8 +24,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", @@ -50,6 +50,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", diff --git a/package-lock.json b/package-lock.json index 68929c5..23bc2a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", @@ -87,6 +89,13 @@ "vite": "^5.4.8" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", + "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -576,6 +585,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -3786,6 +3808,148 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz", + "integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3820,6 +3984,14 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4585,6 +4757,16 @@ "node": ">=10" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -5331,6 +5513,13 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5467,6 +5656,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5524,6 +5723,14 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -6796,6 +7003,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7984,6 +8201,17 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -8137,6 +8365,16 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9151,6 +9389,27 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9837,6 +10096,19 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", From fc76a7c7d49cf2a05c70846387c8da6daabcb454 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 12:46:30 -0700 Subject: [PATCH 47/60] Add setup file and remove old testing mock that no longer exists from moduleNameMapper --- client/jest.config.cjs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 3830e79..73e0d16 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,12 +1,11 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", + setupFilesAfterEnv: [ + '/src/setupTests.ts' + ], moduleNameMapper: { - "^@/(.*)$": "/src/$1", - "^../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", - "^../../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", + "^@/(.*)$": "/src/$1" }, transform: { "^.+\\.tsx?$": [ @@ -14,9 +13,9 @@ module.exports = { { useESM: true, jsx: "react-jsx", - tsconfig: "tsconfig.jest.json", - }, - ], + tsconfig: "tsconfig.jest.json" + } + ] }, extensionsToTreatAsEsm: [".ts", ".tsx"], testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", @@ -25,13 +24,13 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$", + "\\.config\\.(js|ts|cjs|mjs)$" ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$", - ], + "\\.config\\.(js|ts|cjs|mjs)$" + ] }; From 85f0e216791d5c0e6e610bb1b30743de8b4b15a7 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:22:13 -0700 Subject: [PATCH 48/60] Use commonjs for jest --- client/jest.config.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 73e0d16..6a48e7d 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -11,7 +11,6 @@ module.exports = { "^.+\\.tsx?$": [ "ts-jest", { - useESM: true, jsx: "react-jsx", tsconfig: "tsconfig.jest.json" } From 668cc915e4028132dd13254b406919a74b76bfd8 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:22:31 -0700 Subject: [PATCH 49/60] Add jest-dom types --- client/tsconfig.app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index 980c215..e6620f7 100644 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -24,7 +24,8 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "types": ["jest", "@testing-library/jest-dom", "node"] }, "include": ["src"] } From 451704471c6324007cd263acf557a66ac943f95d Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:25:06 -0700 Subject: [PATCH 50/60] Remove setup --- client/jest.config.cjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 6a48e7d..949df20 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,9 +1,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", - setupFilesAfterEnv: [ - '/src/setupTests.ts' - ], moduleNameMapper: { "^@/(.*)$": "/src/$1" }, From 61e229a5529d343134c8333e2fb1700a43b4a79b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sun, 23 Mar 2025 13:35:24 -0700 Subject: [PATCH 51/60] Add sidebar tests --- .../src/components/__tests__/Sidebar.test.tsx | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 client/src/components/__tests__/Sidebar.test.tsx diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx new file mode 100644 index 0000000..765de2f --- /dev/null +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -0,0 +1,278 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, beforeEach, jest } from '@jest/globals'; +import Sidebar from '../Sidebar'; + +// Mock theme hook +jest.mock('../../lib/useTheme', () => ({ + __esModule: true, + default: () => ['light', jest.fn()], +})); + +describe('Sidebar Environment Variables', () => { + const defaultProps = { + connectionStatus: 'disconnected' as const, + transportType: 'stdio' as const, + setTransportType: jest.fn(), + command: '', + setCommand: jest.fn(), + args: '', + setArgs: jest.fn(), + sseUrl: '', + setSseUrl: jest.fn(), + env: {}, + setEnv: jest.fn(), + bearerToken: '', + setBearerToken: jest.fn(), + onConnect: jest.fn(), + stdErrNotifications: [], + logLevel: 'info' as const, + sendLogLevelRequest: jest.fn(), + loggingSupported: true, + }; + + const renderSidebar = (props = {}) => { + return render(); + }; + + const openEnvVarsSection = () => { + const button = screen.getByText('Environment Variables'); + fireEvent.click(button); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Basic Operations', () => { + it('should add a new environment variable', () => { + const setEnv = jest.fn(); + renderSidebar({ env: {}, setEnv }); + + openEnvVarsSection(); + + const addButton = screen.getByText('Add Environment Variable'); + fireEvent.click(addButton); + + expect(setEnv).toHaveBeenCalledWith({ '': '' }); + }); + + it('should remove an environment variable', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const removeButton = screen.getByRole('button', { name: '×' }); + fireEvent.click(removeButton); + + expect(setEnv).toHaveBeenCalledWith({}); + }); + + it('should update environment variable value', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const valueInput = screen.getByDisplayValue('test_value'); + fireEvent.change(valueInput, { target: { value: 'new_value' } }); + + expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: 'new_value' }); + }); + + it('should toggle value visibility', () => { + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv }); + + openEnvVarsSection(); + + const valueInput = screen.getByDisplayValue('test_value'); + expect(valueInput).toHaveProperty('type', 'password'); + + const toggleButton = screen.getByRole('button', { name: /show value/i }); + fireEvent.click(toggleButton); + + expect(valueInput).toHaveProperty('type', 'text'); + }); + }); + + describe('Key Editing', () => { + it('should maintain order when editing first key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); + fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + NEW_FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }); + }); + + it('should maintain order when editing middle key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const middleKeyInput = screen.getByDisplayValue('SECOND_KEY'); + fireEvent.change(middleKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + FIRST_KEY: 'first_value', + NEW_SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }); + }); + + it('should maintain order when editing last key', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + THIRD_KEY: 'third_value', + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const lastKeyInput = screen.getByDisplayValue('THIRD_KEY'); + fireEvent.change(lastKeyInput, { target: { value: 'NEW_THIRD_KEY' } }); + + expect(setEnv).toHaveBeenCalledWith({ + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + NEW_THIRD_KEY: 'third_value', + }); + }); + }); + + describe('Multiple Operations', () => { + it('should maintain state after multiple key edits', () => { + const setEnv = jest.fn(); + const initialEnv = { + FIRST_KEY: 'first_value', + SECOND_KEY: 'second_value', + }; + const { rerender } = renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + // First key edit + const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); + fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); + + // Get the updated env from the first setEnv call + const updatedEnv = (setEnv.mock.calls[0][0] as Record); + + // Rerender with the updated env + rerender(); + + // Second key edit + const secondKeyInput = screen.getByDisplayValue('SECOND_KEY'); + fireEvent.change(secondKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); + + // Verify the final state matches what we expect + expect(setEnv).toHaveBeenLastCalledWith({ + NEW_FIRST_KEY: 'first_value', + NEW_SECOND_KEY: 'second_value', + }); + }); + + it('should maintain visibility state after key edit', () => { + const initialEnv = { TEST_KEY: 'test_value' }; + const { rerender } = renderSidebar({ env: initialEnv }); + + openEnvVarsSection(); + + // Show the value + const toggleButton = screen.getByRole('button', { name: /show value/i }); + fireEvent.click(toggleButton); + + const valueInput = screen.getByDisplayValue('test_value'); + expect(valueInput).toHaveProperty('type', 'text'); + + // Edit the key + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'NEW_KEY' } }); + + // Rerender with updated env + rerender(); + + // Value should still be visible + const updatedValueInput = screen.getByDisplayValue('test_value'); + expect(updatedValueInput).toHaveProperty('type', 'text'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty key', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: '' } }); + + expect(setEnv).toHaveBeenCalledWith({ '': 'test_value' }); + }); + + it('should handle special characters in key', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'TEST-KEY@123' } }); + + expect(setEnv).toHaveBeenCalledWith({ 'TEST-KEY@123': 'test_value' }); + }); + + it('should handle unicode characters', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + fireEvent.change(keyInput, { target: { value: 'TEST_🔑' } }); + + expect(setEnv).toHaveBeenCalledWith({ 'TEST_🔑': 'test_value' }); + }); + + it('should handle very long key names', () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: 'test_value' }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const keyInput = screen.getByDisplayValue('TEST_KEY'); + const longKey = 'A'.repeat(100); + fireEvent.change(keyInput, { target: { value: longKey } }); + + expect(setEnv).toHaveBeenCalledWith({ [longKey]: 'test_value' }); + }); + }); +}); From cab1ed3dd849a85b37255eb2ec20ee7764565a4c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 08:28:28 -0700 Subject: [PATCH 52/60] Add some json form tests and handle css in ui tests --- client/jest.config.cjs | 3 +- client/src/__mocks__/styleMock.js | 1 + .../__tests__/DynamicJsonForm.test.tsx | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 client/src/__mocks__/styleMock.js create mode 100644 client/src/components/__tests__/DynamicJsonForm.test.tsx diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 949df20..b87383e 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -2,7 +2,8 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", moduleNameMapper: { - "^@/(.*)$": "/src/$1" + "^@/(.*)$": "/src/$1", + "\\.css$": "/src/__mocks__/styleMock.js" }, transform: { "^.+\\.tsx?$": [ diff --git a/client/src/__mocks__/styleMock.js b/client/src/__mocks__/styleMock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/client/src/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx new file mode 100644 index 0000000..d140663 --- /dev/null +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -0,0 +1,61 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, jest } from '@jest/globals'; +import DynamicJsonForm from '../DynamicJsonForm'; +import type { JsonSchemaType } from '../DynamicJsonForm'; + +describe('DynamicJsonForm Integer Fields', () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "integer" as const, + description: "Test integer field" + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn() + }; + return render(); + }; + + describe('Basic Operations', () => { + it('should render number input with step=1', () => { + renderForm(); + const input = screen.getByRole('spinbutton'); + expect(input).toHaveProperty('type', 'number'); + expect(input).toHaveProperty('step', '1'); + }); + + it('should pass integer values to onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: '42' } }); + + expect(onChange).toHaveBeenCalledWith(42); + // Verify the value is a number, not a string + expect(typeof onChange.mock.calls[0][0]).toBe('number'); + }); + + it('should not pass string values to onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: 'abc' } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); + + describe('Edge Cases', () => { + it('should handle non-numeric input by not calling onChange', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('spinbutton'); + fireEvent.change(input, { target: { value: 'abc' } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); +}); From 5735f2347a4d1465033806e4648583a5a35a4e0f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 08:52:45 -0700 Subject: [PATCH 53/60] Add tests related to issues/187 to confirm fixed --- .../__tests__/DynamicJsonForm.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx index d140663..f18c15f 100644 --- a/client/src/components/__tests__/DynamicJsonForm.test.tsx +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -3,6 +3,40 @@ import { describe, it, expect, jest } from '@jest/globals'; import DynamicJsonForm from '../DynamicJsonForm'; import type { JsonSchemaType } from '../DynamicJsonForm'; +describe('DynamicJsonForm String Fields', () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "string" as const, + description: "Test string field" + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn() + }; + return render(); + }; + + describe('Type Validation', () => { + it('should handle numeric input as string type', () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: '123321' } }); + + expect(onChange).toHaveBeenCalledWith('123321'); + // Verify the value is a string, not a number + expect(typeof onChange.mock.calls[0][0]).toBe('string'); + }); + + it('should render as text input, not number input', () => { + renderForm(); + const input = screen.getByRole('textbox'); + expect(input).toHaveProperty('type', 'text'); + }); + }); +}); + describe('DynamicJsonForm Integer Fields', () => { const renderForm = (props = {}) => { const defaultProps = { From fa3e2867c97e27c2bb434be7eaacd6698dc8fb4b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:07:53 -0700 Subject: [PATCH 54/60] Fix formatting --- client/jest.config.cjs | 14 +- .../__tests__/DynamicJsonForm.test.tsx | 86 ++--- .../src/components/__tests__/Sidebar.test.tsx | 296 +++++++++--------- 3 files changed, 198 insertions(+), 198 deletions(-) diff --git a/client/jest.config.cjs b/client/jest.config.cjs index b87383e..c360e72 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -3,16 +3,16 @@ module.exports = { testEnvironment: "jsdom", moduleNameMapper: { "^@/(.*)$": "/src/$1", - "\\.css$": "/src/__mocks__/styleMock.js" + "\\.css$": "/src/__mocks__/styleMock.js", }, transform: { "^.+\\.tsx?$": [ "ts-jest", { jsx: "react-jsx", - tsconfig: "tsconfig.jest.json" - } - ] + tsconfig: "tsconfig.jest.json", + }, + ], }, extensionsToTreatAsEsm: [".ts", ".tsx"], testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", @@ -21,13 +21,13 @@ module.exports = { "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$" + "\\.config\\.(js|ts|cjs|mjs)$", ], // Exclude the same patterns from coverage reports coveragePathIgnorePatterns: [ "/node_modules/", "/dist/", "/bin/", - "\\.config\\.(js|ts|cjs|mjs)$" - ] + "\\.config\\.(js|ts|cjs|mjs)$", + ], }; diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx index f18c15f..fce6014 100644 --- a/client/src/components/__tests__/DynamicJsonForm.test.tsx +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -1,94 +1,94 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, expect, jest } from '@jest/globals'; -import DynamicJsonForm from '../DynamicJsonForm'; -import type { JsonSchemaType } from '../DynamicJsonForm'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import DynamicJsonForm from "../DynamicJsonForm"; +import type { JsonSchemaType } from "../DynamicJsonForm"; -describe('DynamicJsonForm String Fields', () => { +describe("DynamicJsonForm String Fields", () => { const renderForm = (props = {}) => { const defaultProps = { schema: { type: "string" as const, - description: "Test string field" + description: "Test string field", } satisfies JsonSchemaType, value: undefined, - onChange: jest.fn() + onChange: jest.fn(), }; return render(); }; - describe('Type Validation', () => { - it('should handle numeric input as string type', () => { + describe("Type Validation", () => { + it("should handle numeric input as string type", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('textbox'); - fireEvent.change(input, { target: { value: '123321' } }); - - expect(onChange).toHaveBeenCalledWith('123321'); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "123321" } }); + + expect(onChange).toHaveBeenCalledWith("123321"); // Verify the value is a string, not a number - expect(typeof onChange.mock.calls[0][0]).toBe('string'); + expect(typeof onChange.mock.calls[0][0]).toBe("string"); }); - it('should render as text input, not number input', () => { + it("should render as text input, not number input", () => { renderForm(); - const input = screen.getByRole('textbox'); - expect(input).toHaveProperty('type', 'text'); + const input = screen.getByRole("textbox"); + expect(input).toHaveProperty("type", "text"); }); }); }); -describe('DynamicJsonForm Integer Fields', () => { +describe("DynamicJsonForm Integer Fields", () => { const renderForm = (props = {}) => { const defaultProps = { schema: { type: "integer" as const, - description: "Test integer field" + description: "Test integer field", } satisfies JsonSchemaType, value: undefined, - onChange: jest.fn() + onChange: jest.fn(), }; return render(); }; - describe('Basic Operations', () => { - it('should render number input with step=1', () => { + describe("Basic Operations", () => { + it("should render number input with step=1", () => { renderForm(); - const input = screen.getByRole('spinbutton'); - expect(input).toHaveProperty('type', 'number'); - expect(input).toHaveProperty('step', '1'); + const input = screen.getByRole("spinbutton"); + expect(input).toHaveProperty("type", "number"); + expect(input).toHaveProperty("step", "1"); }); - it('should pass integer values to onChange', () => { + it("should pass integer values to onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: '42' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "42" } }); + expect(onChange).toHaveBeenCalledWith(42); // Verify the value is a number, not a string - expect(typeof onChange.mock.calls[0][0]).toBe('number'); + expect(typeof onChange.mock.calls[0][0]).toBe("number"); }); - it('should not pass string values to onChange', () => { + it("should not pass string values to onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: 'abc' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + expect(onChange).not.toHaveBeenCalled(); }); }); - describe('Edge Cases', () => { - it('should handle non-numeric input by not calling onChange', () => { + describe("Edge Cases", () => { + it("should handle non-numeric input by not calling onChange", () => { const onChange = jest.fn(); renderForm({ onChange }); - - const input = screen.getByRole('spinbutton'); - fireEvent.change(input, { target: { value: 'abc' } }); - + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + expect(onChange).not.toHaveBeenCalled(); }); }); diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 765de2f..8c0b313 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -1,31 +1,31 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import { describe, it, beforeEach, jest } from '@jest/globals'; -import Sidebar from '../Sidebar'; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, beforeEach, jest } from "@jest/globals"; +import Sidebar from "../Sidebar"; // Mock theme hook -jest.mock('../../lib/useTheme', () => ({ +jest.mock("../../lib/useTheme", () => ({ __esModule: true, - default: () => ['light', jest.fn()], + default: () => ["light", jest.fn()], })); -describe('Sidebar Environment Variables', () => { +describe("Sidebar Environment Variables", () => { const defaultProps = { - connectionStatus: 'disconnected' as const, - transportType: 'stdio' as const, + connectionStatus: "disconnected" as const, + transportType: "stdio" as const, setTransportType: jest.fn(), - command: '', + command: "", setCommand: jest.fn(), - args: '', + args: "", setArgs: jest.fn(), - sseUrl: '', + sseUrl: "", setSseUrl: jest.fn(), env: {}, setEnv: jest.fn(), - bearerToken: '', + bearerToken: "", setBearerToken: jest.fn(), onConnect: jest.fn(), stdErrNotifications: [], - logLevel: 'info' as const, + logLevel: "info" as const, sendLogLevelRequest: jest.fn(), loggingSupported: true, }; @@ -35,7 +35,7 @@ describe('Sidebar Environment Variables', () => { }; const openEnvVarsSection = () => { - const button = screen.getByText('Environment Variables'); + const button = screen.getByText("Environment Variables"); fireEvent.click(button); }; @@ -43,236 +43,236 @@ describe('Sidebar Environment Variables', () => { jest.clearAllMocks(); }); - describe('Basic Operations', () => { - it('should add a new environment variable', () => { + describe("Basic Operations", () => { + it("should add a new environment variable", () => { const setEnv = jest.fn(); renderSidebar({ env: {}, setEnv }); - + openEnvVarsSection(); - - const addButton = screen.getByText('Add Environment Variable'); + + const addButton = screen.getByText("Add Environment Variable"); fireEvent.click(addButton); - - expect(setEnv).toHaveBeenCalledWith({ '': '' }); + + expect(setEnv).toHaveBeenCalledWith({ "": "" }); }); - it('should remove an environment variable', () => { + it("should remove an environment variable", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const removeButton = screen.getByRole('button', { name: '×' }); + + const removeButton = screen.getByRole("button", { name: "×" }); fireEvent.click(removeButton); - + expect(setEnv).toHaveBeenCalledWith({}); }); - it('should update environment variable value', () => { + it("should update environment variable value", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const valueInput = screen.getByDisplayValue('test_value'); - fireEvent.change(valueInput, { target: { value: 'new_value' } }); - - expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: 'new_value' }); + + const valueInput = screen.getByDisplayValue("test_value"); + fireEvent.change(valueInput, { target: { value: "new_value" } }); + + expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: "new_value" }); }); - it('should toggle value visibility', () => { - const initialEnv = { TEST_KEY: 'test_value' }; + it("should toggle value visibility", () => { + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv }); - + openEnvVarsSection(); - - const valueInput = screen.getByDisplayValue('test_value'); - expect(valueInput).toHaveProperty('type', 'password'); - - const toggleButton = screen.getByRole('button', { name: /show value/i }); + + const valueInput = screen.getByDisplayValue("test_value"); + expect(valueInput).toHaveProperty("type", "password"); + + const toggleButton = screen.getByRole("button", { name: /show value/i }); fireEvent.click(toggleButton); - - expect(valueInput).toHaveProperty('type', 'text'); + + expect(valueInput).toHaveProperty("type", "text"); }); }); - describe('Key Editing', () => { - it('should maintain order when editing first key', () => { + describe("Key Editing", () => { + it("should maintain order when editing first key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); - fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); - + + const firstKeyInput = screen.getByDisplayValue("FIRST_KEY"); + fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - NEW_FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + NEW_FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }); }); - it('should maintain order when editing middle key', () => { + it("should maintain order when editing middle key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const middleKeyInput = screen.getByDisplayValue('SECOND_KEY'); - fireEvent.change(middleKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); - + + const middleKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(middleKeyInput, { target: { value: "NEW_SECOND_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - FIRST_KEY: 'first_value', - NEW_SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + NEW_SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }); }); - it('should maintain order when editing last key', () => { + it("should maintain order when editing last key", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + THIRD_KEY: "third_value", }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const lastKeyInput = screen.getByDisplayValue('THIRD_KEY'); - fireEvent.change(lastKeyInput, { target: { value: 'NEW_THIRD_KEY' } }); - + + const lastKeyInput = screen.getByDisplayValue("THIRD_KEY"); + fireEvent.change(lastKeyInput, { target: { value: "NEW_THIRD_KEY" } }); + expect(setEnv).toHaveBeenCalledWith({ - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', - NEW_THIRD_KEY: 'third_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", + NEW_THIRD_KEY: "third_value", }); }); }); - describe('Multiple Operations', () => { - it('should maintain state after multiple key edits', () => { + describe("Multiple Operations", () => { + it("should maintain state after multiple key edits", () => { const setEnv = jest.fn(); const initialEnv = { - FIRST_KEY: 'first_value', - SECOND_KEY: 'second_value', + FIRST_KEY: "first_value", + SECOND_KEY: "second_value", }; const { rerender } = renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - + // First key edit - const firstKeyInput = screen.getByDisplayValue('FIRST_KEY'); - fireEvent.change(firstKeyInput, { target: { value: 'NEW_FIRST_KEY' } }); - + const firstKeyInput = screen.getByDisplayValue("FIRST_KEY"); + fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } }); + // Get the updated env from the first setEnv call - const updatedEnv = (setEnv.mock.calls[0][0] as Record); - + const updatedEnv = setEnv.mock.calls[0][0] as Record; + // Rerender with the updated env rerender(); - + // Second key edit - const secondKeyInput = screen.getByDisplayValue('SECOND_KEY'); - fireEvent.change(secondKeyInput, { target: { value: 'NEW_SECOND_KEY' } }); - + const secondKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(secondKeyInput, { target: { value: "NEW_SECOND_KEY" } }); + // Verify the final state matches what we expect expect(setEnv).toHaveBeenLastCalledWith({ - NEW_FIRST_KEY: 'first_value', - NEW_SECOND_KEY: 'second_value', + NEW_FIRST_KEY: "first_value", + NEW_SECOND_KEY: "second_value", }); }); - it('should maintain visibility state after key edit', () => { - const initialEnv = { TEST_KEY: 'test_value' }; + it("should maintain visibility state after key edit", () => { + const initialEnv = { TEST_KEY: "test_value" }; const { rerender } = renderSidebar({ env: initialEnv }); - + openEnvVarsSection(); - + // Show the value - const toggleButton = screen.getByRole('button', { name: /show value/i }); + const toggleButton = screen.getByRole("button", { name: /show value/i }); fireEvent.click(toggleButton); - - const valueInput = screen.getByDisplayValue('test_value'); - expect(valueInput).toHaveProperty('type', 'text'); - + + const valueInput = screen.getByDisplayValue("test_value"); + expect(valueInput).toHaveProperty("type", "text"); + // Edit the key - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'NEW_KEY' } }); - + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "NEW_KEY" } }); + // Rerender with updated env - rerender(); - + rerender(); + // Value should still be visible - const updatedValueInput = screen.getByDisplayValue('test_value'); - expect(updatedValueInput).toHaveProperty('type', 'text'); + const updatedValueInput = screen.getByDisplayValue("test_value"); + expect(updatedValueInput).toHaveProperty("type", "text"); }); }); - describe('Edge Cases', () => { - it('should handle empty key', () => { + describe("Edge Cases", () => { + it("should handle empty key", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: '' } }); - - expect(setEnv).toHaveBeenCalledWith({ '': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "" } }); + + expect(setEnv).toHaveBeenCalledWith({ "": "test_value" }); }); - it('should handle special characters in key', () => { + it("should handle special characters in key", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'TEST-KEY@123' } }); - - expect(setEnv).toHaveBeenCalledWith({ 'TEST-KEY@123': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "TEST-KEY@123" } }); + + expect(setEnv).toHaveBeenCalledWith({ "TEST-KEY@123": "test_value" }); }); - it('should handle unicode characters', () => { + it("should handle unicode characters", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - fireEvent.change(keyInput, { target: { value: 'TEST_🔑' } }); - - expect(setEnv).toHaveBeenCalledWith({ 'TEST_🔑': 'test_value' }); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + fireEvent.change(keyInput, { target: { value: "TEST_🔑" } }); + + expect(setEnv).toHaveBeenCalledWith({ "TEST_🔑": "test_value" }); }); - it('should handle very long key names', () => { + it("should handle very long key names", () => { const setEnv = jest.fn(); - const initialEnv = { TEST_KEY: 'test_value' }; + const initialEnv = { TEST_KEY: "test_value" }; renderSidebar({ env: initialEnv, setEnv }); - + openEnvVarsSection(); - - const keyInput = screen.getByDisplayValue('TEST_KEY'); - const longKey = 'A'.repeat(100); + + const keyInput = screen.getByDisplayValue("TEST_KEY"); + const longKey = "A".repeat(100); fireEvent.change(keyInput, { target: { value: longKey } }); - - expect(setEnv).toHaveBeenCalledWith({ [longKey]: 'test_value' }); + + expect(setEnv).toHaveBeenCalledWith({ [longKey]: "test_value" }); }); }); }); From a7f25153c445769add41e89dfaf15b7f5c60971f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:18:58 -0700 Subject: [PATCH 55/60] Add failing ToolsTab test that should get fixed with pull/198 --- .../components/__tests__/ToolsTab.test.tsx | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 client/src/components/__tests__/ToolsTab.test.tsx diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx new file mode 100644 index 0000000..2688b52 --- /dev/null +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -0,0 +1,72 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import ToolsTab from "../ToolsTab"; +import { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { Tabs } from "@/components/ui/tabs"; + +describe("ToolsTab", () => { + const mockTools: Tool[] = [ + { + name: "tool1", + description: "First tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const } + } + } + }, + { + name: "tool2", + description: "Second tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const } + } + } + } + ]; + + const defaultProps = { + tools: mockTools, + listTools: jest.fn(), + clearTools: jest.fn(), + callTool: jest.fn(), + selectedTool: null, + setSelectedTool: jest.fn(), + toolResult: null, + nextCursor: "", + error: null + }; + + const renderToolsTab = (props = {}) => { + return render( + + + + ); + }; + + it("should reset input values when switching tools", () => { + const { rerender } = renderToolsTab({ + selectedTool: mockTools[0] + }); + + // Enter a value in the first tool's input + const input = screen.getByRole("spinbutton") as HTMLInputElement; + fireEvent.change(input, { target: { value: "42" } }); + expect(input.value).toBe("42"); + + // Switch to second tool + rerender( + + + + ); + + // Verify input is reset + const newInput = screen.getByRole("spinbutton") as HTMLInputElement; + expect(newInput.value).toBe(""); + }); +}); From b7fa23676a6255176dad029dd1e79252ccba9cb4 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:27:51 -0700 Subject: [PATCH 56/60] Fix formatting --- .../components/__tests__/ToolsTab.test.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 2688b52..2a45065 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -12,9 +12,9 @@ describe("ToolsTab", () => { inputSchema: { type: "object" as const, properties: { - num: { type: "number" as const } - } - } + num: { type: "number" as const }, + }, + }, }, { name: "tool2", @@ -22,10 +22,10 @@ describe("ToolsTab", () => { inputSchema: { type: "object" as const, properties: { - num: { type: "number" as const } - } - } - } + num: { type: "number" as const }, + }, + }, + }, ]; const defaultProps = { @@ -37,20 +37,20 @@ describe("ToolsTab", () => { setSelectedTool: jest.fn(), toolResult: null, nextCursor: "", - error: null + error: null, }; const renderToolsTab = (props = {}) => { return render( - + , ); }; it("should reset input values when switching tools", () => { const { rerender } = renderToolsTab({ - selectedTool: mockTools[0] + selectedTool: mockTools[0], }); // Enter a value in the first tool's input @@ -62,7 +62,7 @@ describe("ToolsTab", () => { rerender( - + , ); // Verify input is reset From 379486b5ea4d4ab2e2faae5a96c79276e374234f Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:43:48 -0700 Subject: [PATCH 57/60] Add failing test for pull/206 --- .../src/components/__tests__/Sidebar.test.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 8c0b313..fc8a5f4 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -161,6 +161,31 @@ describe("Sidebar Environment Variables", () => { NEW_THIRD_KEY: "third_value", }); }); + + it("should maintain order during key editing", () => { + const setEnv = jest.fn(); + const initialEnv = { + KEY1: "value1", + KEY2: "value2" + }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + // Type "NEW_" one character at a time + const key1Input = screen.getByDisplayValue("KEY1"); + "NEW_".split("").forEach((char) => { + fireEvent.change(key1Input, { target: { value: char + "KEY1".slice(1) } }); + }); + + // Verify the last setEnv call maintains the order + const lastCall = setEnv.mock.calls[setEnv.mock.calls.length - 1][0] as Record; + const entries = Object.entries(lastCall); + + // The values should stay with their original keys + expect(entries[0][1]).toBe("value1"); // First entry should still have value1 + expect(entries[1][1]).toBe("value2"); // Second entry should still have value2 + }); }); describe("Multiple Operations", () => { From 65a0d46816aca50e87643e325d80160a4743b33b Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 09:47:34 -0700 Subject: [PATCH 58/60] Fix formatting --- client/src/components/__tests__/Sidebar.test.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index fc8a5f4..710c80d 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -166,7 +166,7 @@ describe("Sidebar Environment Variables", () => { const setEnv = jest.fn(); const initialEnv = { KEY1: "value1", - KEY2: "value2" + KEY2: "value2", }; renderSidebar({ env: initialEnv, setEnv }); @@ -175,13 +175,17 @@ describe("Sidebar Environment Variables", () => { // Type "NEW_" one character at a time const key1Input = screen.getByDisplayValue("KEY1"); "NEW_".split("").forEach((char) => { - fireEvent.change(key1Input, { target: { value: char + "KEY1".slice(1) } }); + fireEvent.change(key1Input, { + target: { value: char + "KEY1".slice(1) }, + }); }); // Verify the last setEnv call maintains the order - const lastCall = setEnv.mock.calls[setEnv.mock.calls.length - 1][0] as Record; + const lastCall = setEnv.mock.calls[ + setEnv.mock.calls.length - 1 + ][0] as Record; const entries = Object.entries(lastCall); - + // The values should stay with their original keys expect(entries[0][1]).toBe("value1"); // First entry should still have value1 expect(entries[1][1]).toBe("value2"); // Second entry should still have value2 From f2f209dbd3ded70d76a181557a1c598a6721dfe5 Mon Sep 17 00:00:00 2001 From: Mark Anthony Cianfrani Date: Sat, 22 Mar 2025 12:52:56 -0400 Subject: [PATCH 59/60] fix(sidebar): maintain order when changing values --- client/src/components/Sidebar.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index aa930e5..4f60a77 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -187,9 +187,17 @@ const Sidebar = ({ value={key} onChange={(e) => { const newKey = e.target.value; - const newEnv = { ...env }; - delete newEnv[key]; - newEnv[newKey] = value; + const newEnv = Object.entries(env).reduce( + (acc, [k, v]) => { + if (k === key) { + acc[newKey] = value; + } else { + acc[k] = v; + } + return acc; + }, + {} as Record, + ); setEnv(newEnv); setShownEnvVars((prev) => { const next = new Set(prev); From 16b38071e7554eb5556491f2f04339e58c5a073c Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Mon, 24 Mar 2025 12:36:17 -0700 Subject: [PATCH 60/60] Bump version to 0.7.0 --- client/package.json | 2 +- package-lock.json | 12 ++++++------ package.json | 6 +++--- server/package.json | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/package.json b/client/package.json index 0471765..30cf37f 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index 68929c5..eec3c95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,7 +32,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -11299,7 +11299,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", diff --git a/package.json b/package.json index e3bce92..66cc1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 5d8839f..732993f 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",