From 2f9d14a173eb18bdee2aff565bf10a6ceb6ec9dd Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Sat, 18 Jul 2026 18:55:02 +0200 Subject: [PATCH] fix(vue-vuetify): resolve `$ref` on `additional-properties`' `propertyNames` The `additional-properties` (map) renderer builds the nested new-key form's schema by spreading `control.value.schema.propertyNames`. When `propertyNames` is a `$ref` into the root's `$defs` (the shape a schema that shares a named key type across many maps emits), the nested `json-forms` mounts it as its OWN root, where `#/$defs/...` does not exist. AJV then throws `can't resolve reference #/$defs/... from id #` and the map fails to render. Resolve the `$ref` against the real root schema (`control.value.rootSchema`, via `Resolve.schema` -- the same accessor the renderer already uses for the additional-properties value schema) before spreading, so the nested form gets a self-contained subschema. Adds a regression test mounting such a map and asserting the mount does not throw (`tests/unit/additional/AdditionalPropertiesRef.spec.ts`). Assisted-by: Claude:claude-fable-5 Signed-off-by: Kiara Grouwstra --- .../components/AdditionalProperties.vue | 11 +++++- .../AdditionalPropertiesRef.spec.ts | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/vue-vuetify/tests/unit/additional/AdditionalPropertiesRef.spec.ts diff --git a/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue b/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue index eade49467..e8fa4b662 100644 --- a/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue +++ b/packages/vue-vuetify/src/complex/components/AdditionalProperties.vue @@ -301,8 +301,17 @@ export default defineComponent({ // TODO: create issue against jsonforms to add propertyNames into the JsonSchema interface // propertyNames exist in draft-6 but not defined in the JsonSchema if (typeof (control.value.schema as any).propertyNames === 'object') { + let propertyNames = (control.value.schema as any).propertyNames; + if (typeof propertyNames.$ref === 'string') { + propertyNames = + Resolve.schema( + control.value.rootSchema, + propertyNames.$ref, + control.value.rootSchema, + ) ?? propertyNames; + } result = { - ...(control.value.schema as any).propertyNames, + ...propertyNames, ...result, }; } else if ( diff --git a/packages/vue-vuetify/tests/unit/additional/AdditionalPropertiesRef.spec.ts b/packages/vue-vuetify/tests/unit/additional/AdditionalPropertiesRef.spec.ts new file mode 100644 index 000000000..bac020705 --- /dev/null +++ b/packages/vue-vuetify/tests/unit/additional/AdditionalPropertiesRef.spec.ts @@ -0,0 +1,35 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { clearAllIds } from '@jsonforms/core'; +import { extendedVuetifyRenderers } from '../../../src'; +import { mountJsonForms } from '../util'; + +// Test use of `$ref` in additional-properties `propertyNames` +describe('AdditionalProperties nested $ref propertyNames', () => { + const schema = { + type: 'object' as const, + $defs: { + attrName: { + type: 'string' as const, + pattern: '^[A-Za-z_][A-Za-z0-9_]*$', + }, + }, + properties: { + secretFiles: { + type: 'object' as const, + additionalProperties: { type: 'string' as const }, + propertyNames: { $ref: '#/$defs/attrName' }, + }, + }, + }; + const uischema = { type: 'Control' as const, scope: '#' }; + + beforeEach(() => { + clearAllIds(); + }); + + it('mounts a map whose key type is a `$ref` into the root `$defs`', () => { + expect(() => + mountJsonForms({ secretFiles: {} }, schema, extendedVuetifyRenderers, uischema), + ).not.toThrow(); + }); +});