Fix formatting
This commit is contained in:
@@ -1,32 +1,37 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'ts-jest',
|
preset: "ts-jest",
|
||||||
testEnvironment: 'jsdom',
|
testEnvironment: "jsdom",
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@/(.*)$': '<rootDir>/src/$1',
|
"^@/(.*)$": "<rootDir>/src/$1",
|
||||||
'^../components/DynamicJsonForm$': '<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts',
|
"^../components/DynamicJsonForm$":
|
||||||
'^../../components/DynamicJsonForm$': '<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts'
|
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
|
||||||
|
"^../../components/DynamicJsonForm$":
|
||||||
|
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
|
||||||
},
|
},
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.tsx?$': ['ts-jest', {
|
"^.+\\.tsx?$": [
|
||||||
useESM: true,
|
"ts-jest",
|
||||||
jsx: 'react-jsx',
|
{
|
||||||
tsconfig: 'tsconfig.jest.json'
|
useESM: true,
|
||||||
}]
|
jsx: "react-jsx",
|
||||||
|
tsconfig: "tsconfig.jest.json",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
extensionsToTreatAsEsm: [".ts", ".tsx"],
|
||||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
|
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
// Exclude directories and files that don't need to be tested
|
// Exclude directories and files that don't need to be tested
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
'/node_modules/',
|
"/node_modules/",
|
||||||
'/dist/',
|
"/dist/",
|
||||||
'/bin/',
|
"/bin/",
|
||||||
'\\.config\\.(js|ts|cjs|mjs)$'
|
"\\.config\\.(js|ts|cjs|mjs)$",
|
||||||
],
|
],
|
||||||
// Exclude the same patterns from coverage reports
|
// Exclude the same patterns from coverage reports
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'/node_modules/',
|
"/node_modules/",
|
||||||
'/dist/',
|
"/dist/",
|
||||||
'/bin/',
|
"/bin/",
|
||||||
'\\.config\\.(js|ts|cjs|mjs)$'
|
"\\.config\\.(js|ts|cjs|mjs)$",
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,25 +28,25 @@ interface DynamicJsonFormProps {
|
|||||||
maxDepth?: number;
|
maxDepth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const DynamicJsonForm = ({
|
const DynamicJsonForm = ({
|
||||||
schema,
|
schema,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
maxDepth = 3,
|
maxDepth = 3,
|
||||||
}: DynamicJsonFormProps) => {
|
}: DynamicJsonFormProps) => {
|
||||||
|
|
||||||
const [isJsonMode, setIsJsonMode] = useState(false);
|
const [isJsonMode, setIsJsonMode] = useState(false);
|
||||||
const [jsonError, setJsonError] = useState<string>();
|
const [jsonError, setJsonError] = useState<string>();
|
||||||
// Add state for storing raw JSON value
|
// Add state for storing raw JSON value
|
||||||
const [rawJsonValue, setRawJsonValue] = useState<string>(
|
const [rawJsonValue, setRawJsonValue] = useState<string>(
|
||||||
JSON.stringify(value ?? generateDefaultValue(schema), null, 2)
|
JSON.stringify(value ?? generateDefaultValue(schema), null, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update rawJsonValue when value prop changes
|
// Update rawJsonValue when value prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isJsonMode) {
|
if (!isJsonMode) {
|
||||||
setRawJsonValue(JSON.stringify(value ?? generateDefaultValue(schema), null, 2));
|
setRawJsonValue(
|
||||||
|
JSON.stringify(value ?? generateDefaultValue(schema), null, 2),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [value, schema, isJsonMode]);
|
}, [value, schema, isJsonMode]);
|
||||||
|
|
||||||
@@ -64,7 +64,9 @@ const DynamicJsonForm = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update raw JSON value when switching to JSON mode
|
// 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);
|
setIsJsonMode(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -131,7 +133,7 @@ const DynamicJsonForm = ({
|
|||||||
);
|
);
|
||||||
case "object": {
|
case "object": {
|
||||||
// Handle case where we have a value but no schema properties
|
// 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 we have schema properties, use them to render fields
|
||||||
if (propSchema.properties) {
|
if (propSchema.properties) {
|
||||||
@@ -254,11 +256,7 @@ const DynamicJsonForm = ({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleSwitchToFormMode}
|
|
||||||
>
|
|
||||||
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,23 +279,28 @@ const DynamicJsonForm = ({
|
|||||||
}}
|
}}
|
||||||
error={jsonError}
|
error={jsonError}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : // If schema type is object but value is not an object or is empty, and we have actual JSON data,
|
||||||
// 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
|
||||||
// render a simple representation of the JSON data
|
schema.type === "object" &&
|
||||||
schema.type === "object" &&
|
(typeof value !== "object" ||
|
||||||
(typeof value !== "object" || value === null || Object.keys(value).length === 0) &&
|
value === null ||
|
||||||
|
Object.keys(value).length === 0) &&
|
||||||
rawJsonValue &&
|
rawJsonValue &&
|
||||||
rawJsonValue !== "{}" ? (
|
rawJsonValue !== "{}" ? (
|
||||||
<div className="space-y-4 border rounded-md p-4">
|
<div className="space-y-4 border rounded-md p-4">
|
||||||
<p className="text-sm text-gray-500">Form view not available for this JSON structure. Using simplified view:</p>
|
<p className="text-sm text-gray-500">
|
||||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto">
|
Form view not available for this JSON structure. Using simplified
|
||||||
{rawJsonValue}
|
view:
|
||||||
</pre>
|
</p>
|
||||||
<p className="text-sm text-gray-500">Use JSON mode for full editing capabilities.</p>
|
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto">
|
||||||
</div>
|
{rawJsonValue}
|
||||||
) : (
|
</pre>
|
||||||
renderFormFields(schema, value)
|
<p className="text-sm text-gray-500">
|
||||||
)
|
Use JSON mode for full editing capabilities.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
renderFormFields(schema, value)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,9 +11,15 @@ interface JsonEditorProps {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps) => {
|
const JsonEditor = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
error: externalError,
|
||||||
|
}: JsonEditorProps) => {
|
||||||
const [editorContent, setEditorContent] = useState(value);
|
const [editorContent, setEditorContent] = useState(value);
|
||||||
const [internalError, setInternalError] = useState<string | undefined>(undefined);
|
const [internalError, setInternalError] = useState<string | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditorContent(value);
|
setEditorContent(value);
|
||||||
@@ -22,8 +28,8 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps)
|
|||||||
const formatJson = (json: string): string => {
|
const formatJson = (json: string): string => {
|
||||||
try {
|
try {
|
||||||
// Handle empty arrays and objects specifically
|
// 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);
|
return JSON.stringify(JSON.parse(json), null, 2);
|
||||||
} catch {
|
} catch {
|
||||||
return json;
|
return json;
|
||||||
@@ -52,17 +58,15 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps)
|
|||||||
return (
|
return (
|
||||||
<div className="relative space-y-2">
|
<div className="relative space-y-2">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button variant="outline" size="sm" onClick={handleFormatJson}>
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleFormatJson}
|
|
||||||
>
|
|
||||||
Format JSON
|
Format JSON
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`border rounded-md ${
|
className={`border rounded-md ${
|
||||||
displayError ? "border-red-500" : "border-gray-200 dark:border-gray-800"
|
displayError
|
||||||
|
? "border-red-500"
|
||||||
|
: "border-gray-200 dark:border-gray-800"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Editor
|
<Editor
|
||||||
@@ -81,7 +85,9 @@ const JsonEditor = ({ value, onChange, error: externalError }: JsonEditorProps)
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{displayError && <p className="text-sm text-red-500 mt-1">{displayError}</p>}
|
{displayError && (
|
||||||
|
<p className="text-sm text-red-500 mt-1">{displayError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,54 +1,61 @@
|
|||||||
import { updateValueAtPath, getValueAtPath } from '../jsonPathUtils';
|
import { updateValueAtPath, getValueAtPath } from "../jsonPathUtils";
|
||||||
import { JsonValue } from '../../components/DynamicJsonForm';
|
import { JsonValue } from "../../components/DynamicJsonForm";
|
||||||
|
|
||||||
describe('updateValueAtPath', () => {
|
describe("updateValueAtPath", () => {
|
||||||
// Basic functionality tests
|
// Basic functionality tests
|
||||||
test('returns the new value when path is empty', () => {
|
test("returns the new value when path is empty", () => {
|
||||||
expect(updateValueAtPath({ foo: 'bar' }, [], 'newValue')).toBe('newValue');
|
expect(updateValueAtPath({ foo: "bar" }, [], "newValue")).toBe("newValue");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('initializes an empty object when input is null/undefined and path starts with a string', () => {
|
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(null as any, ["foo"], "bar")).toEqual({
|
||||||
expect(updateValueAtPath(undefined as any, ['foo'], 'bar')).toEqual({ foo: 'bar' });
|
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', () => {
|
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(null as any, ["0"], "bar")).toEqual(["bar"]);
|
||||||
expect(updateValueAtPath(undefined as any, ['0'], 'bar')).toEqual(['bar']);
|
expect(updateValueAtPath(undefined as any, ["0"], "bar")).toEqual(["bar"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Object update tests
|
// Object update tests
|
||||||
test('updates a simple object property', () => {
|
test("updates a simple object property", () => {
|
||||||
const obj = { name: 'John', age: 30 };
|
const obj = { name: "John", age: 30 };
|
||||||
expect(updateValueAtPath(obj, ['age'], 31)).toEqual({ name: 'John', age: 31 });
|
expect(updateValueAtPath(obj, ["age"], 31)).toEqual({
|
||||||
|
name: "John",
|
||||||
|
age: 31,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates a nested object property', () => {
|
test("updates a nested object property", () => {
|
||||||
const obj = { user: { name: 'John', address: { city: 'New York' } } };
|
const obj = { user: { name: "John", address: { city: "New York" } } };
|
||||||
expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual(
|
expect(
|
||||||
{ user: { name: 'John', address: { city: 'Boston' } } }
|
updateValueAtPath(obj, ["user", "address", "city"], "Boston"),
|
||||||
);
|
).toEqual({ user: { name: "John", address: { city: "Boston" } } });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates missing object properties', () => {
|
test("creates missing object properties", () => {
|
||||||
const obj = { user: { name: 'John' } };
|
const obj = { user: { name: "John" } };
|
||||||
expect(updateValueAtPath(obj, ['user', 'address', 'city'], 'Boston')).toEqual(
|
expect(
|
||||||
{ user: { name: 'John', address: { city: 'Boston' } } }
|
updateValueAtPath(obj, ["user", "address", "city"], "Boston"),
|
||||||
);
|
).toEqual({ user: { name: "John", address: { city: "Boston" } } });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Array update tests
|
// Array update tests
|
||||||
test('updates an array item', () => {
|
test("updates an array item", () => {
|
||||||
const arr = [1, 2, 3, 4];
|
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 arr = [1, 2, 3];
|
||||||
const result = updateValueAtPath(arr, ['5'], 'new') as JsonValue[];
|
const result = updateValueAtPath(arr, ["5"], "new") as JsonValue[];
|
||||||
|
|
||||||
// Check overall array structure
|
// 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
|
// Explicitly verify that indices 3 and 4 contain null, not undefined
|
||||||
expect(result[3]).toBe(null);
|
expect(result[3]).toBe(null);
|
||||||
@@ -65,48 +72,48 @@ describe('updateValueAtPath', () => {
|
|||||||
expect(result.every((_, index: number) => index in result)).toBe(true);
|
expect(result.every((_, index: number) => index in result)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates a nested array item', () => {
|
test("updates a nested array item", () => {
|
||||||
const obj = { users: [{ name: 'John' }, { name: 'Jane' }] };
|
const obj = { users: [{ name: "John" }, { name: "Jane" }] };
|
||||||
expect(updateValueAtPath(obj, ['users', '1', 'name'], 'Janet')).toEqual(
|
expect(updateValueAtPath(obj, ["users", "1", "name"], "Janet")).toEqual({
|
||||||
{ users: [{ name: 'John' }, { name: 'Janet' }] }
|
users: [{ name: "John" }, { name: "Janet" }],
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error handling tests
|
// Error handling tests
|
||||||
test('returns original value when trying to update a primitive with a path', () => {
|
test("returns original value when trying to update a primitive with a path", () => {
|
||||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
const spy = jest.spyOn(console, "error").mockImplementation();
|
||||||
const result = updateValueAtPath('string', ['foo'], 'bar');
|
const result = updateValueAtPath("string", ["foo"], "bar");
|
||||||
expect(result).toBe('string');
|
expect(result).toBe("string");
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns original array when index is invalid', () => {
|
test("returns original array when index is invalid", () => {
|
||||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
const spy = jest.spyOn(console, "error").mockImplementation();
|
||||||
const arr = [1, 2, 3];
|
const arr = [1, 2, 3];
|
||||||
expect(updateValueAtPath(arr, ['invalid'], 4)).toEqual(arr);
|
expect(updateValueAtPath(arr, ["invalid"], 4)).toEqual(arr);
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns original array when index is negative', () => {
|
test("returns original array when index is negative", () => {
|
||||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
const spy = jest.spyOn(console, "error").mockImplementation();
|
||||||
const arr = [1, 2, 3];
|
const arr = [1, 2, 3];
|
||||||
expect(updateValueAtPath(arr, ['-1'], 4)).toEqual(arr);
|
expect(updateValueAtPath(arr, ["-1"], 4)).toEqual(arr);
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
spy.mockRestore();
|
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
|
// Create a sparse array by deleting an element
|
||||||
const sparseArr = [1, 2, 3];
|
const sparseArr = [1, 2, 3];
|
||||||
delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3]
|
delete sparseArr[1]; // Now sparseArr is [1, <1 empty item>, 3]
|
||||||
|
|
||||||
// Update a value beyond the array length
|
// 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
|
// 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
|
// Explicitly verify that index 1 (the hole) contains null, not undefined
|
||||||
expect(result[1]).toBe(null);
|
expect(result[1]).toBe(null);
|
||||||
@@ -121,49 +128,53 @@ describe('updateValueAtPath', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getValueAtPath', () => {
|
describe("getValueAtPath", () => {
|
||||||
test('returns the original value when path is empty', () => {
|
test("returns the original value when path is empty", () => {
|
||||||
const obj = { foo: 'bar' };
|
const obj = { foo: "bar" };
|
||||||
expect(getValueAtPath(obj, [])).toBe(obj);
|
expect(getValueAtPath(obj, [])).toBe(obj);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns the value at a simple path', () => {
|
test("returns the value at a simple path", () => {
|
||||||
const obj = { name: 'John', age: 30 };
|
const obj = { name: "John", age: 30 };
|
||||||
expect(getValueAtPath(obj, ['name'])).toBe('John');
|
expect(getValueAtPath(obj, ["name"])).toBe("John");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns the value at a nested path', () => {
|
test("returns the value at a nested path", () => {
|
||||||
const obj = { user: { name: 'John', address: { city: 'New York' } } };
|
const obj = { user: { name: "John", address: { city: "New York" } } };
|
||||||
expect(getValueAtPath(obj, ['user', 'address', 'city'])).toBe('New York');
|
expect(getValueAtPath(obj, ["user", "address", "city"])).toBe("New York");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns default value when path does not exist', () => {
|
test("returns default value when path does not exist", () => {
|
||||||
const obj = { user: { name: 'John' } };
|
const obj = { user: { name: "John" } };
|
||||||
expect(getValueAtPath(obj, ['user', 'address', 'city'], 'Unknown')).toBe('Unknown');
|
expect(getValueAtPath(obj, ["user", "address", "city"], "Unknown")).toBe(
|
||||||
|
"Unknown",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns default value when input is null/undefined', () => {
|
test("returns default value when input is null/undefined", () => {
|
||||||
expect(getValueAtPath(null as any, ['foo'], 'default')).toBe('default');
|
expect(getValueAtPath(null as any, ["foo"], "default")).toBe("default");
|
||||||
expect(getValueAtPath(undefined as any, ['foo'], 'default')).toBe('default');
|
expect(getValueAtPath(undefined as any, ["foo"], "default")).toBe(
|
||||||
|
"default",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles array indices correctly', () => {
|
test("handles array indices correctly", () => {
|
||||||
const arr = ['a', 'b', 'c'];
|
const arr = ["a", "b", "c"];
|
||||||
expect(getValueAtPath(arr, ['1'])).toBe('b');
|
expect(getValueAtPath(arr, ["1"])).toBe("b");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns default value for out of bounds array indices', () => {
|
test("returns default value for out of bounds array indices", () => {
|
||||||
const arr = ['a', 'b', 'c'];
|
const arr = ["a", "b", "c"];
|
||||||
expect(getValueAtPath(arr, ['5'], 'default')).toBe('default');
|
expect(getValueAtPath(arr, ["5"], "default")).toBe("default");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns default value for invalid array indices', () => {
|
test("returns default value for invalid array indices", () => {
|
||||||
const arr = ['a', 'b', 'c'];
|
const arr = ["a", "b", "c"];
|
||||||
expect(getValueAtPath(arr, ['invalid'], 'default')).toBe('default');
|
expect(getValueAtPath(arr, ["invalid"], "default")).toBe("default");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('navigates through mixed object and array paths', () => {
|
test("navigates through mixed object and array paths", () => {
|
||||||
const obj = { users: [{ name: 'John' }, { name: 'Jane' }] };
|
const obj = { users: [{ name: "John" }, { name: "Jane" }] };
|
||||||
expect(getValueAtPath(obj, ['users', '1', 'name'])).toBe('Jane');
|
expect(getValueAtPath(obj, ["users", "1", "name"])).toBe("Jane");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,135 +1,141 @@
|
|||||||
import { generateDefaultValue, formatFieldLabel, validateValueAgainstSchema } from '../schemaUtils';
|
import {
|
||||||
import { JsonSchemaType } from '../../components/DynamicJsonForm';
|
generateDefaultValue,
|
||||||
|
formatFieldLabel,
|
||||||
|
validateValueAgainstSchema,
|
||||||
|
} from "../schemaUtils";
|
||||||
|
import { JsonSchemaType } from "../../components/DynamicJsonForm";
|
||||||
|
|
||||||
describe('generateDefaultValue', () => {
|
describe("generateDefaultValue", () => {
|
||||||
test('generates default string', () => {
|
test("generates default string", () => {
|
||||||
expect(generateDefaultValue({ type: 'string' })).toBe('');
|
expect(generateDefaultValue({ type: "string" })).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default number', () => {
|
test("generates default number", () => {
|
||||||
expect(generateDefaultValue({ type: 'number' })).toBe(0);
|
expect(generateDefaultValue({ type: "number" })).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default integer', () => {
|
test("generates default integer", () => {
|
||||||
expect(generateDefaultValue({ type: 'integer' })).toBe(0);
|
expect(generateDefaultValue({ type: "integer" })).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default boolean', () => {
|
test("generates default boolean", () => {
|
||||||
expect(generateDefaultValue({ type: 'boolean' })).toBe(false);
|
expect(generateDefaultValue({ type: "boolean" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default array', () => {
|
test("generates default array", () => {
|
||||||
expect(generateDefaultValue({ type: 'array' })).toEqual([]);
|
expect(generateDefaultValue({ type: "array" })).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default empty object', () => {
|
test("generates default empty object", () => {
|
||||||
expect(generateDefaultValue({ type: 'object' })).toEqual({});
|
expect(generateDefaultValue({ type: "object" })).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates default null for unknown types', () => {
|
test("generates default null for unknown types", () => {
|
||||||
expect(generateDefaultValue({ type: 'unknown' as any })).toBe(null);
|
expect(generateDefaultValue({ type: "unknown" as any })).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates object with properties', () => {
|
test("generates object with properties", () => {
|
||||||
const schema: JsonSchemaType = {
|
const schema: JsonSchemaType = {
|
||||||
type: 'object',
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: 'string' },
|
name: { type: "string" },
|
||||||
age: { type: 'number' },
|
age: { type: "number" },
|
||||||
isActive: { type: 'boolean' }
|
isActive: { type: "boolean" },
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
expect(generateDefaultValue(schema)).toEqual({
|
expect(generateDefaultValue(schema)).toEqual({
|
||||||
name: '',
|
name: "",
|
||||||
age: 0,
|
age: 0,
|
||||||
isActive: false
|
isActive: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles nested objects', () => {
|
test("handles nested objects", () => {
|
||||||
const schema: JsonSchemaType = {
|
const schema: JsonSchemaType = {
|
||||||
type: 'object',
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
user: {
|
user: {
|
||||||
type: 'object',
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
name: { type: 'string' },
|
name: { type: "string" },
|
||||||
address: {
|
address: {
|
||||||
type: 'object',
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
city: { type: 'string' }
|
city: { type: "string" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
expect(generateDefaultValue(schema)).toEqual({
|
expect(generateDefaultValue(schema)).toEqual({
|
||||||
user: {
|
user: {
|
||||||
name: '',
|
name: "",
|
||||||
address: {
|
address: {
|
||||||
city: ''
|
city: "",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('formatFieldLabel', () => {
|
describe("formatFieldLabel", () => {
|
||||||
test('formats camelCase', () => {
|
test("formats camelCase", () => {
|
||||||
expect(formatFieldLabel('firstName')).toBe('First Name');
|
expect(formatFieldLabel("firstName")).toBe("First Name");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('formats snake_case', () => {
|
test("formats snake_case", () => {
|
||||||
expect(formatFieldLabel('first_name')).toBe('First name');
|
expect(formatFieldLabel("first_name")).toBe("First name");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('formats single word', () => {
|
test("formats single word", () => {
|
||||||
expect(formatFieldLabel('name')).toBe('Name');
|
expect(formatFieldLabel("name")).toBe("Name");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('formats mixed case with underscores', () => {
|
test("formats mixed case with underscores", () => {
|
||||||
expect(formatFieldLabel('user_firstName')).toBe('User first Name');
|
expect(formatFieldLabel("user_firstName")).toBe("User first Name");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles empty string', () => {
|
test("handles empty string", () => {
|
||||||
expect(formatFieldLabel('')).toBe('');
|
expect(formatFieldLabel("")).toBe("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateValueAgainstSchema', () => {
|
describe("validateValueAgainstSchema", () => {
|
||||||
test('validates string type', () => {
|
test("validates string type", () => {
|
||||||
expect(validateValueAgainstSchema('test', { type: 'string' })).toBe(true);
|
expect(validateValueAgainstSchema("test", { type: "string" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema(123, { type: 'string' })).toBe(false);
|
expect(validateValueAgainstSchema(123, { type: "string" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates number type', () => {
|
test("validates number type", () => {
|
||||||
expect(validateValueAgainstSchema(123, { type: 'number' })).toBe(true);
|
expect(validateValueAgainstSchema(123, { type: "number" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema('test', { type: 'number' })).toBe(false);
|
expect(validateValueAgainstSchema("test", { type: "number" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates integer type', () => {
|
test("validates integer type", () => {
|
||||||
expect(validateValueAgainstSchema(123, { type: 'integer' })).toBe(true);
|
expect(validateValueAgainstSchema(123, { type: "integer" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema('test', { type: 'integer' })).toBe(false);
|
expect(validateValueAgainstSchema("test", { type: "integer" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates boolean type', () => {
|
test("validates boolean type", () => {
|
||||||
expect(validateValueAgainstSchema(true, { type: 'boolean' })).toBe(true);
|
expect(validateValueAgainstSchema(true, { type: "boolean" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema('test', { type: 'boolean' })).toBe(false);
|
expect(validateValueAgainstSchema("test", { type: "boolean" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates array type', () => {
|
test("validates array type", () => {
|
||||||
expect(validateValueAgainstSchema([], { type: 'array' })).toBe(true);
|
expect(validateValueAgainstSchema([], { type: "array" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema({}, { type: 'array' })).toBe(false);
|
expect(validateValueAgainstSchema({}, { type: "array" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validates object type', () => {
|
test("validates object type", () => {
|
||||||
expect(validateValueAgainstSchema({}, { type: 'object' })).toBe(true);
|
expect(validateValueAgainstSchema({}, { type: "object" })).toBe(true);
|
||||||
expect(validateValueAgainstSchema([], { type: 'object' })).toBe(false);
|
expect(validateValueAgainstSchema([], { type: "object" })).toBe(false);
|
||||||
expect(validateValueAgainstSchema('test', { type: 'object' })).toBe(false);
|
expect(validateValueAgainstSchema("test", { type: "object" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns true for unknown types', () => {
|
test("returns true for unknown types", () => {
|
||||||
expect(validateValueAgainstSchema('anything', { type: 'unknown' as any })).toBe(true);
|
expect(
|
||||||
|
validateValueAgainstSchema("anything", { type: "unknown" as any }),
|
||||||
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export type JsonObject = { [key: string]: JsonValue };
|
|||||||
export function updateValueAtPath(
|
export function updateValueAtPath(
|
||||||
obj: JsonValue,
|
obj: JsonValue,
|
||||||
path: string[],
|
path: string[],
|
||||||
value: JsonValue
|
value: JsonValue,
|
||||||
): JsonValue {
|
): JsonValue {
|
||||||
if (path.length === 0) return value;
|
if (path.length === 0) return value;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export function updateValueAtPath(
|
|||||||
else {
|
else {
|
||||||
console.error(
|
console.error(
|
||||||
`Cannot update path ${path.join(".")} in non-object/array value:`,
|
`Cannot update path ${path.join(".")} in non-object/array value:`,
|
||||||
obj
|
obj,
|
||||||
);
|
);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export function updateValueAtPath(
|
|||||||
function updateArray(
|
function updateArray(
|
||||||
array: JsonValue[],
|
array: JsonValue[],
|
||||||
path: string[],
|
path: string[],
|
||||||
value: JsonValue
|
value: JsonValue,
|
||||||
): JsonValue[] {
|
): JsonValue[] {
|
||||||
const [index, ...restPath] = path;
|
const [index, ...restPath] = path;
|
||||||
const arrayIndex = Number(index);
|
const arrayIndex = Number(index);
|
||||||
@@ -82,7 +82,11 @@ function updateArray(
|
|||||||
if (restPath.length === 0) {
|
if (restPath.length === 0) {
|
||||||
newArray[arrayIndex] = value;
|
newArray[arrayIndex] = value;
|
||||||
} else {
|
} else {
|
||||||
newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value);
|
newArray[arrayIndex] = updateValueAtPath(
|
||||||
|
newArray[arrayIndex],
|
||||||
|
restPath,
|
||||||
|
value,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return newArray;
|
return newArray;
|
||||||
}
|
}
|
||||||
@@ -93,7 +97,7 @@ function updateArray(
|
|||||||
function updateObject(
|
function updateObject(
|
||||||
obj: JsonObject,
|
obj: JsonObject,
|
||||||
path: string[],
|
path: string[],
|
||||||
value: JsonValue
|
value: JsonValue,
|
||||||
): JsonObject {
|
): JsonObject {
|
||||||
const [key, ...restPath] = path;
|
const [key, ...restPath] = path;
|
||||||
|
|
||||||
@@ -127,7 +131,7 @@ function updateObject(
|
|||||||
export function getValueAtPath(
|
export function getValueAtPath(
|
||||||
obj: JsonValue,
|
obj: JsonValue,
|
||||||
path: string[],
|
path: string[],
|
||||||
defaultValue: JsonValue = null
|
defaultValue: JsonValue = null,
|
||||||
): JsonValue {
|
): JsonValue {
|
||||||
if (path.length === 0) return obj;
|
if (path.length === 0) return obj;
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function formatFieldLabel(key: string): string {
|
|||||||
*/
|
*/
|
||||||
export function validateValueAgainstSchema(
|
export function validateValueAgainstSchema(
|
||||||
value: JsonValue,
|
value: JsonValue,
|
||||||
schema: JsonSchemaType
|
schema: JsonSchemaType,
|
||||||
): boolean {
|
): boolean {
|
||||||
// Basic type validation
|
// Basic type validation
|
||||||
switch (schema.type) {
|
switch (schema.type) {
|
||||||
@@ -65,7 +65,9 @@ export function validateValueAgainstSchema(
|
|||||||
case "array":
|
case "array":
|
||||||
return Array.isArray(value);
|
return Array.isArray(value);
|
||||||
case "object":
|
case "object":
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
return (
|
||||||
|
typeof value === "object" && value !== null && !Array.isArray(value)
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user