From e12bd5fa1e29cbfb29be31020cc49abfd749ca02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCller?= Date: Thu, 24 Oct 2024 18:18:41 +0200 Subject: [PATCH] Make HTML ID generation fast Replaces HTML ID generation with a simple monotonic sequence to avoid several expensive hash lookups for every generated ID. This entirely eliminates the chance for collisions under regular circumstances, and the default seed even performs better in some adverse circumstances, like multiple instances of the same library sharing an ID namespace on the same page. The only potential downside is that IDs might not be particularly semantically helpful, but that can be mitigated by just appending that information instead. This might be a breaking change if this module is considered a public API. --- .../angular/src/library/abstract-control.ts | 2 - .../src/library/jsonforms.component.ts | 4 +- packages/core/src/util/ids.ts | 37 ++--- packages/core/test/mappers/cell.test.ts | 4 +- packages/core/test/mappers/renderer.test.ts | 4 +- .../src/layouts/ExpandPanelRenderer.tsx | 14 +- packages/react/src/JsonForms.tsx | 150 +++++------------- .../unit/additional/LabelRenderer.spec.ts | 4 +- .../additional/ListWithDetailRenderer.spec.ts | 4 +- .../unit/complex/ArrayControlRenderer.spec.ts | 4 +- .../tests/unit/complex/OneOfRenderer.spec.ts | 4 +- .../controls/BooleanControlRenderer.spec.ts | 4 +- .../unit/controls/DateControlRenderer.spec.ts | 4 +- .../controls/DateTimeControlRenderer.spec.ts | 4 +- .../unit/controls/EnumControlRenderer.spec.ts | 4 +- .../controls/IntegerControlRenderer.spec.ts | 4 +- .../MultiStringControlRenderer.spec.ts | 4 +- .../controls/NumberControlRenderer.spec.ts | 4 +- .../controls/OneOfEnumControlRenderer.spec.ts | 4 +- .../controls/StringControlRenderer.spec.ts | 6 +- .../unit/controls/TimeControlRenderer.spec.ts | 4 +- .../unit/layout/ArrayLayoutRenderer.spec.ts | 4 +- packages/vue/src/jsonFormsCompositions.ts | 11 +- 23 files changed, 95 insertions(+), 193 deletions(-) diff --git a/packages/angular/src/library/abstract-control.ts b/packages/angular/src/library/abstract-control.ts index af43608ce0..0dde8f6574 100644 --- a/packages/angular/src/library/abstract-control.ts +++ b/packages/angular/src/library/abstract-control.ts @@ -29,7 +29,6 @@ import { JsonFormsState, JsonSchema, OwnPropsOfControl, - removeId, StatePropsOfControl, } from '@jsonforms/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core'; @@ -149,7 +148,6 @@ export abstract class JsonFormsAbstractControl< ngOnDestroy() { super.ngOnDestroy(); - removeId(this.id); } isEnabled(): boolean { diff --git a/packages/angular/src/library/jsonforms.component.ts b/packages/angular/src/library/jsonforms.component.ts index 22a93264bf..6333b03c0b 100644 --- a/packages/angular/src/library/jsonforms.component.ts +++ b/packages/angular/src/library/jsonforms.component.ts @@ -32,7 +32,7 @@ import { ViewContainerRef, } from '@angular/core'; import { - createId, + nextId, isControl, getConfig, JsonFormsProps, @@ -143,7 +143,7 @@ export class JsonFormsOutlet const controlInstance = instance as JsonFormsControl; if (controlInstance.id === undefined) { const id = isControl(props.uischema) - ? createId(props.uischema.scope) + ? nextId() + props.uischema.scope : undefined; (instance as JsonFormsControl).id = id; } diff --git a/packages/core/src/util/ids.ts b/packages/core/src/util/ids.ts index 4914754f46..34316019e3 100644 --- a/packages/core/src/util/ids.ts +++ b/packages/core/src/util/ids.ts @@ -23,30 +23,21 @@ THE SOFTWARE. */ -const usedIds: Set = new Set(); +let idNamespace: string; +let idIndex: number; -const makeId = (idBase: string, iteration: number) => - iteration <= 1 ? idBase : idBase + iteration.toString(); - -const isUniqueId = (idBase: string, iteration: number) => { - const newID = makeId(idBase, iteration); - return !usedIds.has(newID); -}; - -export const createId = (proposedId: string) => { - if (proposedId === undefined) { - // failsafe to avoid endless loops in error cases - proposedId = 'undefined'; - } - let tries = 0; - while (!isUniqueId(proposedId, tries)) { - tries++; - } - const newID = makeId(proposedId, tries); - usedIds.add(newID); - return newID; +export const seedIds = (namespace = 'jsonforms', index = 0) => { + idNamespace = namespace; + idIndex = index; }; -export const removeId = (id: string) => usedIds.delete(id); +// This has a salt in the unlikely case that someone bundled multiple instances +// of this module on a single site. +seedIds(`jsonforms${Math.random().toString(32).slice(2)}`); -export const clearAllIds = () => usedIds.clear(); +/** + * Generate an ID that's unique within the current execution context. This is + * intended for HTML ID generation. Does not guarantee stability across SSR + * boundaries! + */ +export const nextId = () => `:${idNamespace}:${(idIndex++).toString(36)}:`; diff --git a/packages/core/test/mappers/cell.test.ts b/packages/core/test/mappers/cell.test.ts index c0317aa614..dd940a0d06 100644 --- a/packages/core/test/mappers/cell.test.ts +++ b/packages/core/test/mappers/cell.test.ts @@ -25,7 +25,7 @@ import test from 'ava'; import * as _ from 'lodash'; import * as Redux from 'redux'; -import { clearAllIds, createAjv, validate } from '../../src/util'; +import { seedIds, createAjv, validate } from '../../src/util'; import { UPDATE_DATA, UpdateAction } from '../../src/actions'; import configureStore from 'redux-mock-store'; import { JsonFormsState } from '../../src/store'; @@ -263,7 +263,7 @@ test('mapStateToCellProps - data', (t) => { }); test('mapStateToCellProps - id', (t) => { - clearAllIds(); + seedIds(); const ownProps = { uischema: coreUISchema, id: '#/properties/firstName', diff --git a/packages/core/test/mappers/renderer.test.ts b/packages/core/test/mappers/renderer.test.ts index 8e686e884b..6ef1c6975f 100644 --- a/packages/core/test/mappers/renderer.test.ts +++ b/packages/core/test/mappers/renderer.test.ts @@ -64,7 +64,7 @@ import { mapStateToOneOfEnumControlProps, mapStateToOneOfProps, } from '../../src/mappers'; -import { clearAllIds, convertDateToString, createAjv } from '../../src/util'; +import { seedIds, convertDateToString, createAjv } from '../../src/util'; import { rankWith } from '../../src'; const middlewares: Redux.Middleware[] = []; @@ -429,7 +429,7 @@ test('mapStateToControlProps - no duplicate error messages', (t) => { }); test('mapStateToControlProps - id', (t) => { - clearAllIds(); + seedIds(); const ownProps = { uischema: coreUISchema, id: '#/properties/firstName', diff --git a/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx b/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx index fc02b28e6b..6e5add3635 100644 --- a/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx +++ b/packages/material-renderers/src/layouts/ExpandPanelRenderer.tsx @@ -5,8 +5,6 @@ import React, { Fragment, ReducerAction, useMemo, - useState, - useEffect, useCallback, } from 'react'; import { @@ -25,8 +23,7 @@ import { update, JsonFormsCellRendererRegistryEntry, JsonFormsUISchemaRegistryEntry, - createId, - removeId, + nextId, ArrayTranslations, computeChildLabel, UpdateArrayContext, @@ -88,13 +85,8 @@ export interface ExpandPanelProps DispatchPropsOfExpandPanel {} const ExpandPanelRendererComponent = (props: ExpandPanelProps) => { - const [labelHtmlId] = useState(createId('expand-panel')); - - useEffect(() => { - return () => { - removeId(labelHtmlId); - }; - }, [labelHtmlId]); + // TODO: Should probably use React.useId() when support for React < 18 is dropped. + const labelHtmlId = useMemo(() => nextId() + 'expand-panel', []); const { enabled, diff --git a/packages/react/src/JsonForms.tsx b/packages/react/src/JsonForms.tsx index 38ed5514d5..4262fa7f80 100644 --- a/packages/react/src/JsonForms.tsx +++ b/packages/react/src/JsonForms.tsx @@ -28,9 +28,8 @@ import type Ajv from 'ajv'; import type { ErrorObject } from 'ajv'; import { UnknownRenderer } from './UnknownRenderer'; import { - createId, + nextId, Generate, - isControl, JsonFormsCellRendererRegistryEntry, JsonFormsCore, JsonFormsI18nState, @@ -40,7 +39,6 @@ import { JsonSchema, Middleware, OwnPropsOfJsonFormsRenderer, - removeId, UISchemaElement, ValidationMode, } from '@jsonforms/core'; @@ -49,129 +47,57 @@ import { withJsonFormsRendererProps, } from './JsonFormsContext'; -interface JsonFormsRendererState { - id: string; -} - export interface JsonFormsReactProps { onChange?(state: Pick): void; middleware?: Middleware; } -export class JsonFormsDispatchRenderer extends React.Component< - JsonFormsProps, - JsonFormsRendererState -> { - constructor(props: JsonFormsProps) { - super(props); - this.state = { - id: isControl(props.uischema) - ? createId(props.uischema.scope) - : undefined, - }; - } - - componentWillUnmount() { - if (isControl(this.props.uischema)) { - removeId(this.state.id); - } - } - - componentDidUpdate(prevProps: JsonFormsProps) { - if (prevProps.schema !== this.props.schema) { - removeId(this.state.id); - this.setState({ - id: isControl(this.props.uischema) - ? createId(this.props.uischema.scope) - : undefined, - }); - } - } - - render() { - const { - schema, - rootSchema, - uischema, - path, - enabled, - renderers, - cells, - config, - } = this.props as JsonFormsProps; - - return ( - +export const JsonFormsDispatchRenderer = React.memo( + function JsonFormsDispatchRenderer(props: JsonFormsProps) { + // TODO: Should probably use React.useId() when support for React < 18 is dropped. + const id = useMemo(nextId, [props.schema]); + const testerContext = useMemo( + () => ({ + rootSchema: props.rootSchema, + config: props.config, + }), + [props.rootSchema, props.config] ); - } -} - -const TestAndRender = React.memo(function TestAndRender(props: { - uischema: UISchemaElement; - schema: JsonSchema; - rootSchema: JsonSchema; - path: string; - enabled: boolean; - renderers: JsonFormsRendererRegistryEntry[]; - cells: JsonFormsCellRendererRegistryEntry[]; - id: string; - config: any; -}) { - const testerContext = useMemo( - () => ({ - rootSchema: props.rootSchema, - config: props.config, - }), - [props.rootSchema, props.config] - ); - const renderer = useMemo( - () => - maxBy(props.renderers, (r) => - r.tester(props.uischema, props.schema, testerContext) - ), - [props.renderers, props.uischema, props.schema, testerContext] - ); - if ( - renderer === undefined || - renderer.tester(props.uischema, props.schema, testerContext) === -1 - ) { - return ; - } else { - const Render = renderer.renderer; - return ( - + const renderer = useMemo( + () => + maxBy(props.renderers, (r) => + r.tester(props.uischema, props.schema, testerContext) + ), + [props.renderers, props.uischema, props.schema, testerContext] ); + if ( + renderer === undefined || + renderer.tester(props.uischema, props.schema, testerContext) === -1 + ) { + return ; + } else { + const Render = renderer.renderer; + return ( + + ); + } } -}); +); /** * @deprecated Since Version 3.0 this optimization renderer is no longer necessary. * Use `JsonFormsDispatch` instead. * We still export it for backward compatibility */ -export class ResolvedJsonFormsDispatchRenderer extends JsonFormsDispatchRenderer { - constructor(props: JsonFormsProps) { - super(props); - } -} +export const ResolvedJsonFormsDispatchRenderer = JsonFormsDispatchRenderer; export const JsonFormsDispatch: ComponentType = withJsonFormsRendererProps(JsonFormsDispatchRenderer); diff --git a/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts b/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts index c7220b47cd..35e0d4fc0f 100644 --- a/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/additional/LabelRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import LabelRenderer from '../../../src/additional/LabelRenderer.vue'; import { entry as labelRendererEntry } from '../../../src/additional/LabelRenderer.entry'; import { mountJsonForms } from '../util'; @@ -20,7 +20,7 @@ describe('LabelRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts b/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts index 190b5ff49b..25a9e1e65c 100644 --- a/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/additional/ListWithDetailRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ListWithDetailRenderer from '../../../src/additional/ListWithDetailRenderer.vue'; import { entry as listWithDetailRendererEntry } from '../../../src/additional/ListWithDetailRenderer.entry'; import { mountJsonForms } from '../util'; @@ -28,7 +28,7 @@ describe('ListWithDetailRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts index 0422ca64fd..a45cca58a0 100644 --- a/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/complex/ArrayControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ArrayControlRenderer from '../../../src/complex/ArrayControlRenderer.vue'; import { entry as arrayControlRendererEntry } from '../../../src/complex/ArrayControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -27,7 +27,7 @@ describe('ArrayControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts index 9cae6a50ec..ee2349a23e 100644 --- a/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/complex/OneOfRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import OneOfControlRenderer from '../../../src/complex/OneOfRenderer.vue'; import { entry as oneOfControlRendererEntry } from '../../../src/complex/OneOfRenderer.entry'; import { mountJsonForms } from '../util'; @@ -38,7 +38,7 @@ describe('OneOfRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('clearDialogAccept')) { diff --git a/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts index 1c1c592a0a..08e1ef2d22 100644 --- a/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/BooleanControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import BooleanControlRenderer from '../../../src/controls/BooleanControlRenderer.vue'; import { entry as booleanControlRendererEntry } from '../../../src/controls/BooleanControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -23,7 +23,7 @@ describe('BooleanControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts index 4365a8486d..04e6484ddf 100644 --- a/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/DateControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import DateControlRenderer from '../../../src/controls/DateControlRenderer.vue'; import { entry as dateControlRendererEntry } from '../../../src/controls/DateControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -27,7 +27,7 @@ describe('DateControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts index 08f994643d..05ec9947bd 100644 --- a/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/DateTimeControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import DateTimeControlRenderer from '../../../src/controls/DateTimeControlRenderer.vue'; import { entry as dateTimeControlRendererEntry } from '../../../src/controls/DateTimeControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -26,7 +26,7 @@ describe('DateTimeControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts index 9f9c19c332..9c391e0107 100644 --- a/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/EnumControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import EnumControlRenderer from '../../../src/controls/EnumControlRenderer.vue'; import { entry as enumControlRendererEntry } from '../../../src/controls/EnumControlRenderer.entry'; import { wait } from '../../../tests'; @@ -24,7 +24,7 @@ describe('EnumControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts index ef64f124a2..88c01f4484 100644 --- a/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/IntegerControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import IntegerControlRenderer from '../../../src/controls/IntegerControlRenderer.vue'; import { entry as integerControlRendererEntry } from '../../../src/controls/IntegerControlRenderer.entry'; import { wait } from '../../../tests'; @@ -24,7 +24,7 @@ describe('IntegerControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts index 77a6b14e9e..3c6703acc8 100644 --- a/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/MultiStringControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import MultiStringControlRenderer from '../../../src/controls/MultiStringControlRenderer.vue'; import { entry as multiStringControlRendererEntry } from '../../../src/controls/MultiStringControlRenderer.entry'; import { wait } from '../../../tests'; @@ -25,7 +25,7 @@ describe('MultiStringControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts index 0f76ffd832..8121e50a4c 100644 --- a/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/NumberControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import NumberControlRenderer from '../../../src/controls/NumberControlRenderer.vue'; import { entry as numberControlRendererEntry } from '../../../src/controls/NumberControlRenderer.entry'; import { wait } from '../../../tests'; @@ -23,7 +23,7 @@ describe('NumberControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts index f8f293037f..2942b31fb4 100644 --- a/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/OneOfEnumControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import OneOfEnumControlRenderer from '../../../src/controls/OneOfEnumControlRenderer.vue'; import { entry as oneOfEnumControlRendererEntry } from '../../../src/controls/OneOfEnumControlRenderer.entry'; import { wait } from '../../../tests'; @@ -26,7 +26,7 @@ describe('OneOfEnumControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts index 345f9287d2..39bfa75af6 100644 --- a/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/StringControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import StringControlRenderer from '../../../src/controls/StringControlRenderer.vue'; import { entry as stringControlRendererEntry } from '../../../src/controls/StringControlRenderer.entry'; import { wait } from '../../../tests'; @@ -25,7 +25,7 @@ describe('StringControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); @@ -91,7 +91,7 @@ describe('StringControlRenderer.vue with suggestion', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts b/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts index 556200c44b..d9cd8b8715 100644 --- a/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/controls/TimeControlRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds } from '@jsonforms/core'; +import { seedIds } from '@jsonforms/core'; import TimeControlRenderer from '../../../src/controls/TimeControlRenderer.vue'; import { entry as timeControlRendererEntry } from '../../../src/controls/TimeControlRenderer.entry'; import { mountJsonForms } from '../util'; @@ -25,7 +25,7 @@ describe('TimeControlRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema); }); diff --git a/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts b/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts index ba8a591c5a..fa1d684f12 100644 --- a/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts +++ b/packages/vue-vuetify/tests/unit/layout/ArrayLayoutRenderer.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; -import { clearAllIds, type Translator } from '@jsonforms/core'; +import { seedIds, type Translator } from '@jsonforms/core'; import ArrayLayoutRenderer from '../../../src/layouts/ArrayLayoutRenderer.vue'; import { entry as arrayLayoutRendererEntry } from '../../../src/layouts/ArrayLayoutRenderer.entry'; import { mountJsonForms } from '../util'; @@ -28,7 +28,7 @@ describe('ArrayLayoutRenderer.vue', () => { beforeEach(() => { // clear all ids to guarantee that the snapshots will always be generated with the same ids - clearAllIds(); + seedIds(); wrapper = mountJsonForms(data, schema, renderers, uischema, undefined, { translate: ((id, defaultMessage) => { if (id.endsWith('addAriaLabel')) { diff --git a/packages/vue/src/jsonFormsCompositions.ts b/packages/vue/src/jsonFormsCompositions.ts index 1ac967aab6..dbb366230f 100644 --- a/packages/vue/src/jsonFormsCompositions.ts +++ b/packages/vue/src/jsonFormsCompositions.ts @@ -29,8 +29,7 @@ import { mapStateToDispatchCellProps, mapStateToOneOfEnumCellProps, StatePropsOfJsonFormsRenderer, - createId, - removeId, + nextId, mapStateToMultiEnumControlProps, mapDispatchToMultiEnumProps, mapStateToLabelProps, @@ -197,7 +196,7 @@ export function useControl< onBeforeMount(() => { if (control.value.uischema.scope) { - id.value = createId(control.value.uischema.scope); + id.value = nextId() + control.value.uischema.scope; } }); @@ -205,17 +204,13 @@ export function useControl< () => props.schema, (newSchema, prevSchem) => { if (newSchema !== prevSchem && isControl(control.value.uischema)) { - if (id.value) { - removeId(id.value); - } - id.value = createId(control.value.uischema.scope); + id.value = nextId() + control.value.uischema.scope; } } ); onUnmounted(() => { if (id.value) { - removeId(id.value); id.value = undefined; } });