From 5fddb7c6084999f5774a9e3c2ae7cab9b488a96d Mon Sep 17 00:00:00 2001 From: rishilifts Date: Thu, 22 Oct 2026 21:36:32 +0530 Subject: [PATCH] Address the third review round: simplify override warnings to logOnce() and drop the registry-template warning - warnOnInvalidOverride() now dedupes through logOnce() on the message itself, so loggedMessages.ts, its package.json exclusion and the hand-built key are gone - The registry-template warning never fired from the getTemplates() loop every caller uses, so it is dropped along with its changelog and docstring sentences; the Form-level registry check moves to the follow-up PR - FallbackField now passes getUiOptions() the merged global options, so an invalid own ui:UnsupportedFieldTemplate no longer changes which template renders compared with leaving the value out - The repeated registry lookup in getTemplate() goes through one lookupTemplate() helper - testing/testSetup.ts restored to its v7 version, taking it out of the PR --- CHANGELOG-v7.md | 2 +- .../src/components/fields/FallbackField.tsx | 8 ++- packages/utils/package.json | 1 - packages/utils/src/getTemplate.ts | 23 ++++---- packages/utils/src/logOnce.ts | 45 +++++++++++++-- packages/utils/src/loggedMessages.ts | 56 ------------------- .../utils/src/templateOverrideWarnings.ts | 54 ++---------------- packages/utils/test/getTemplate.test.ts | 25 --------- testing/testSetup.ts | 9 +-- 9 files changed, 68 insertions(+), 155 deletions(-) delete mode 100644 packages/utils/src/loggedMessages.ts diff --git a/CHANGELOG-v7.md b/CHANGELOG-v7.md index 83c21392fe6..2074ba6a627 100644 --- a/CHANGELOG-v7.md +++ b/CHANGELOG-v7.md @@ -749,7 +749,7 @@ should change the heading of the (upcoming) version to include a major version b - Fixed `mergeDefaultsWithFormData()` copying the `defaults` into the result before checking their type, so a string or array `default` merged with object `formData` surfaced as index keys (e.g. a `'x'` default merged with `{ b: 2 }` produced `{ '0': 'x', b: 2 }`). The defaults are now only copied when they are themselves an object, fixing [#5405](https://github.com/rjsf-team/react-jsonschema-form/issues/5405) - A relative `$id` nested in another resource is resolved against that resource's `$id`, as draft 2020-12 specifies, both when `createSchemaUtils()` makes a bundle's `$ref`s absolute and when `findSchemaDefinition()` looks a resource up by its `$id`, where it was read as written and a `$ref` into or inside it threw `Could not find a definition`. A `$ref` resolved through a definition is then resolved against the resource that definition sits in, including under a root without an absolute `$id`. The keywords beside a `$ref` keep the base of the schema they were written in, rebased on an `$id` beside the `$ref`, as the `$ref` itself is. An `$id` under a keyword that holds no subschemas, such as `components`, is data and starts no resource. A `$ref` that `createSchemaUtils()` would rewrite to a relative URI a validator resolves again to somewhere else, as under a root without an absolute `$id` inside `nested/child.json`, is left as written, so validating against the rewritten root keeps working. In a draft-07 or 2019-09 root, or one without `$schema`, a `#/...` `$ref` inside a definition with its own `$id` resolves in that definition's resource, where it threw `Could not find a definition`, a 2019-09 `$id` beside a `$ref` rebases it as a 2020-12 one does, and a `$ref` naming an embedded resource by its URI finds it in every draft, where only a 2020-12 root did. An embedded resource whose `$id` ends in `/`, or in an empty fragment (`root.json#`, as draft-07 schemas often write one), is found by a `$ref` to it too, fixing [#5467](https://github.com/rjsf-team/react-jsonschema-form/issues/5467) - **BREAKING CHANGE:** `toErrorList()` lists the errors an `ErrorSchema` holds in an array for the items of an array field, which it skipped, and `validationDataMerge()` merges such an array as the object keyed by index that `toErrorSchema()` builds, rather than replacing the other `ErrorSchema`'s errors for that array with it or appending it to another array of item errors. `unwrapErrorHandler()` strips the `addError()` functions from an array of item handlers a `customValidate` returns, which it left in the `ErrorSchema`, and keys the items by index. A `null` or `undefined` an `ErrorSchema` holds for an item or field without errors is left out of the merge rather than replacing the other `ErrorSchema`'s errors there, and out of what `unwrapErrorHandler()` returns. `unwrapErrorHandler()` leaves what sits under `__errors` as it is, as the other walks do, where it unwrapped an object there. `validationDataMerge()` therefore returns such an array as the object keyed by index, also when there is nothing to merge, where it used to pass a caller's array through, and returns `validationData` itself only when its `errorSchema` holds no such array and no `null` or `undefined`. The `ErrorSchema` and `FormValidation` types reject an array as the node of array data, which their numeric index signature accepted, so a typed `ErrorSchema` literal that holds one, such as `{ list: [{ __errors: ['taken'] }] }`, no longer compiles and has to key the items by index. The new `errorSchemaChild()` returns a value an `ErrorSchema` walk descends into, a plain object or such an array, and `undefined` for an `__errors` array or a leaf ([#3827](https://github.com/rjsf-team/react-jsonschema-form/issues/3827)) -- Fixed invalid template overrides being returned as components. `getTemplate()` and `getTemplates()` ignore an override that is not a component - a string naming no registered template (which used to fall back silently), a React element, or any other value - falling back to a valid `ui:globalOptions` template before the registry's own, the way `ui:field` does. An explicit own `null` is not a mistake: it opts the field out of any override, `ui:globalOptions` included, and the registry's template renders without a warning. `SchemaField` warns once per uiSchema entry, naming the field and saying when the value came from `ui:globalOptions`; both functions take a new optional `warnSource` parameter for that warning, which callers that only resolve omit and stay silent. A registry template that is not a component is also warned about, once per registry, since nothing valid is left to fall back to. `describeUnresolvedComponent()`, the helper that words these warnings, is now exported ([#5432](https://github.com/rjsf-team/react-jsonschema-form/issues/5432)) +- Fixed invalid template overrides being returned as components. `getTemplate()` and `getTemplates()` ignore an override that is not a component - a string naming no registered template (which used to fall back silently), a React element, or any other value - falling back to a valid `ui:globalOptions` template before the registry's own, the way `ui:field` does. An explicit own `null` is not a mistake: it opts the field out of any override, `ui:globalOptions` included, and the registry's template renders without a warning. `SchemaField` warns once per uiSchema entry, naming the field and saying when the value came from `ui:globalOptions`; both functions take a new optional `warnSource` parameter for that warning, which callers that only resolve omit and stay silent. `describeUnresolvedComponent()`, the helper that words these warnings, is now exported ([#5432](https://github.com/rjsf-team/react-jsonschema-form/issues/5432)) - Fixed form data and schema keys named like `Object.prototype` members, such as `toString`, `constructor` and a JSON `__proto__`, being read as inherited values or written through the prototype setter. `mergeDefaultsWithFormData()`, `mergeSchemas()` and `retrieveSchema()` keep a `__proto__` property as an own key; `retrieveSchema()` no longer treats an inherited name as a declared `dependencies` key; `getDefaultFormState()` keeps the defaults of a `__proto__` schema property and an `additionalProperties` default named `constructor` or `valueOf`, and no longer turns an inherited `toString` into an own key when it matches `allOf`, or reads an inherited member of a parent `const`; `omitExtraData()` reads and writes own keys only on submit; and `getDefaultFormState()` counts a property declared as `false` as a declared property rather than an additional one, so an object `default` still seeds its other keys beside one, fixing [#5515](https://github.com/rjsf-team/react-jsonschema-form/issues/5515) ([#5524](https://github.com/rjsf-team/react-jsonschema-form/pull/5524)) - Added theme presentation metadata, which `@rjsf/core` and every theme read: the `PresentationDeclaration`, `WidgetPresentationDeclaration`, `ThemePresentation`, `MergedThemePresentation` and `FieldPresentation` types, the `WIDGET_FIELD_PRESENTATION` declaration, and `mergeThemePresentation()`, `getPresentationDeclaration()`, `resolveFieldPresentation()` and `getSelectorPresentation()`, which build a field's plan of who draws its label, description, help and errors, under which ids, and the ARIA attributes its control carries. A declaration's `uncheckedIsFalse` ([#5388](https://github.com/rjsf-team/react-jsonschema-form/issues/5388)) marks a checkbox that holds `false` unchecked ([#5490](https://github.com/rjsf-team/react-jsonschema-form/issues/5490)) - Added the helpers that read a field's plan, `getControlAttributes()`, `getControlDescribedBy()`, `getDescribedControlAttributes()`, `getWidgetDescription()`, `isLabelledByTemplate()` and `hasAdditionalKeyLabel()`, and the `optionDescriptionId()`, `optionHelpId()`, `optionLabelId()` and `selectedOptionDescriptionId()` id generators ([#5490](https://github.com/rjsf-team/react-jsonschema-form/issues/5490)) diff --git a/packages/core/src/components/fields/FallbackField.tsx b/packages/core/src/components/fields/FallbackField.tsx index bd86729bf3f..8234610298d 100644 --- a/packages/core/src/components/fields/FallbackField.tsx +++ b/packages/core/src/components/fields/FallbackField.tsx @@ -539,7 +539,13 @@ export default function FallbackField< } const reason = props.reason ?? translateString(TranslatableString.UnknownFieldType, [String(schema.type)]); - const { UnsupportedFieldTemplate } = getTemplates(registry, getUiOptions(uiSchema)); + // Merged with the global options so the fallback in `getTemplate()` sees the same picture `SchemaField` does: + // without them an invalid own `ui:UnsupportedFieldTemplate` would fall back to `globalUiOptions`, changing which + // template renders compared with leaving the value out + const { UnsupportedFieldTemplate } = getTemplates( + registry, + getUiOptions(uiSchema, registry.globalUiOptions), + ); return ( <> diff --git a/packages/utils/package.json b/packages/utils/package.json index 99f7c4e8724..b4582b079cb 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -13,7 +13,6 @@ }, "./lib": "./lib/index.js", "./lib/describeElementGivenAsComponent.js": null, - "./lib/loggedMessages.js": null, "./lib/templateOverrideWarnings.js": null, "./lib/*.js": "./lib/*.js" }, diff --git a/packages/utils/src/getTemplate.ts b/packages/utils/src/getTemplate.ts index 47b537d18be..bc02e8bfadd 100644 --- a/packages/utils/src/getTemplate.ts +++ b/packages/utils/src/getTemplate.ts @@ -1,5 +1,5 @@ import isComponentType from './isComponentType.ts'; -import { isTemplateName, warnOnInvalidOverride, warnOnInvalidRegistryTemplate } from './templateOverrideWarnings.ts'; +import { isTemplateName, warnOnInvalidOverride } from './templateOverrideWarnings.ts'; import type { FormContextType, TemplatesType, Registry, UIOptionsType, StrictRJSFSchema, RJSFSchema } from './types.ts'; /** The field a template-override warning should name, passed by the one caller that renders every field @@ -19,14 +19,18 @@ export interface TemplateWarnSource< ownUiOptions: UIOptionsType; } +/** Returns the registry template `name` refers to, or `undefined` when the registry holds nothing under it */ +function lookupTemplate(customTemplates: Record, name: string): unknown { + return Object.hasOwn(customTemplates, name) ? customTemplates[name] : undefined; +} + /** Returns the template with the given `name` from either the `uiSchema` if it is defined or from the `registry` * otherwise. NOTE, since `ButtonTemplates` are not overridden in `uiSchema` only those in the `registry` are returned. * * An override that is not a component is ignored, warning when a `warnSource` is given: a string naming no registered * template, a React element, or any other non-component value falls back to a valid `ui:globalOptions` template before - * the registry's own, as `resolveUiField()` does for `ui:field`. A registry template that is not a component is warned - * about once per registry. An explicit own `null` is not a mistake: it opts the field out of any override, - * `ui:globalOptions` included, so the registry's own template renders without a warning. + * the registry's own, as `resolveUiField()` does for `ui:field`. An explicit own `null` is not a mistake: it opts the + * field out of any override, `ui:globalOptions` included, so the registry's own template renders without a warning. * * @param name - The name of the template to fetch, restricted to the keys of `TemplatesType` * @param registry - The `Registry` from which to read the template @@ -53,18 +57,16 @@ export default function getTemplate< const customTemplates: Record = templates; const uiOverrides: Record = uiOptions; const override = uiOverrides[name] ?? undefined; - // No override, or an explicit own `null` that opts out of any override (including `ui:globalOptions`, as a falsy own - // `ui:field` does): the registry's own template renders. The check on it is cached per registry, so this common path - // does not re-validate a static registry on every render of every field and widget. + // No override, or an explicit own `null` that opts out of any override (including `ui:globalOptions`, as an own + // `null` `ui:field` does, though here `''`, `0` and `false` still warn): the registry's own template renders if (override === undefined) { - warnOnInvalidRegistryTemplate(name, customTemplates); return templates[name]; } // Allow templates to be customized per-field by using string keys from the registry; a string naming no registered // template is not a component, so it falls through to the fallback below let template: unknown = override; if (typeof override === 'string') { - template = Object.hasOwn(customTemplates, override) ? customTemplates[override] : undefined; + template = lookupTemplate(customTemplates, override); } if (isComponentType(template)) { // Indexing templates or uiOptions by a generic key results in TS2590: Expression produces a union type that is too @@ -81,12 +83,11 @@ export default function getTemplate< if (globalOverride !== undefined && globalOverride !== override) { let globalTemplate: unknown = globalOverride; if (typeof globalOverride === 'string') { - globalTemplate = Object.hasOwn(customTemplates, globalOverride) ? customTemplates[globalOverride] : undefined; + globalTemplate = lookupTemplate(customTemplates, globalOverride); } if (isComponentType(globalTemplate)) { return globalTemplate as TemplatesType[Name]; } } - warnOnInvalidRegistryTemplate(name, customTemplates); return templates[name]; } diff --git a/packages/utils/src/logOnce.ts b/packages/utils/src/logOnce.ts index 348fefbb74d..f56e44e03d0 100644 --- a/packages/utils/src/logOnce.ts +++ b/packages/utils/src/logOnce.ts @@ -1,11 +1,34 @@ -import { forgetLogged, rememberLogged } from './loggedMessages.ts'; import toDisplayString from './toDisplayString.ts'; -export { LOG_ONCE_MAX_MESSAGES } from './loggedMessages.ts'; - /** The console methods `logOnce()` can log through */ export type LogOnceLevel = 'warn' | 'error'; +/** How many distinct messages are remembered before the oldest generation of them is dropped, so a long-running + * process (such as a server rendering many different schemas) can't grow the set without bound. Deduping holds for a + * working set of up to twice this and then stops abruptly: a render producing more distinct messages than that logs + * every one of them on every pass, because no message survives long enough to be seen a second time. That edge is why + * the number is larger than the mistakes a form could plausibly contain — the warnings are raised per field, not per + * mistake, so an array of a thousand rows whose item schema is misconfigured twice is two thousand distinct messages. + * What is bounded is the number of remembered messages, at twice this; how much memory they take follows what callers + * pass, since a remembered message holds the `message` and the string form of the `error`, both of which can carry + * arbitrary text, and a plain-object or array `error` is held as its whole JSON. Two full generations of the warnings + * raised inside this library, which run a few hundred bytes each, stay under a megabyte. The cap is a ceiling rather + * than an allocation, so a form warning about a handful of fields holds a handful of strings. + */ +export const LOG_ONCE_MAX_MESSAGES = 2000; + +/** What has been logged is kept in two generations rather than one set: when `currentMessages` fills it becomes + * `previousMessages` and a fresh one takes its place, so a message is still remembered for at least + * `LOG_ONCE_MAX_MESSAGES` further distinct messages after it is first seen, and memory stays bounded at twice that. + * Both halves matter. Reaching the cap must not un-remember everything at once, or a render producing more distinct + * messages than the cap starts each pass with nothing remembered and logs all of them every time. And it must not stop + * remembering either, since keys carrying a per-row id or index, or another tenant's form under SSR, fill the cap with + * messages that never recur, and anything first seen afterwards would then log on every render for the rest of the + * process's life. + */ +let currentMessages = new Set(); +let previousMessages = new Set(); + /** Logs `message` (followed by `error`, when there is one) through `console.warn()` or `console.error()`, but only the * first time that combination of `level`, `message` and `error` is seen, so that a warning raised while rendering isn't * repeated on every re-render. The `error` is compared by its type and its `toDisplayString()` form, so two distinct @@ -25,9 +48,18 @@ export default function logOnce(message: string, level: LogOnceLevel = 'warn', e error === undefined ? `${level}\u0000${message}` : `${level}\u0000${message}\u0000${typeof error}\u0000${toDisplayString(error)}`; - if (!rememberLogged(key)) { + // A message found in `previousMessages` is deliberately left there rather than promoted into `currentMessages`, so + // one seen on every render is logged a second time once two rotations have displaced it. Promoting it would hold it + // forever, at the cost of a slot in the current generation too, which is what caps how large a working set stays + // deduped: the largest one that survives, of exactly twice the cap, would drop to one log per pass. + if (currentMessages.has(key) || previousMessages.has(key)) { return; } + if (currentMessages.size >= LOG_ONCE_MAX_MESSAGES) { + previousMessages = currentMessages; + currentMessages = new Set(); + } + currentMessages.add(key); const args = error === undefined ? [message] : [message, error]; // `level` is only type-checked, so a JS caller can pass anything; `console[level]` would throw inside the very // render that was trying to warn, and the key is already remembered by then, so the message would never log at all. @@ -47,4 +79,7 @@ export default function logOnce(message: string, level: LogOnceLevel = 'warn', e * playground does: a wizard whose steps reuse the same field ids, a schema editor or a form builder would otherwise * have the second schema's warnings held back by the first's. */ -export const resetLogOnce = forgetLogged; +export function resetLogOnce() { + currentMessages.clear(); + previousMessages.clear(); +} diff --git a/packages/utils/src/loggedMessages.ts b/packages/utils/src/loggedMessages.ts deleted file mode 100644 index 17856f96044..00000000000 --- a/packages/utils/src/loggedMessages.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** The messages `logOnce()` and the override warnings have logged. Internal to `@rjsf/utils`: `package.json` excludes - * this module from the `./lib/*.js` exports wildcard so it can't be deep-imported. `resetLogOnce()` clears it. - */ - -/** How many distinct messages are remembered before the oldest generation of them is dropped, so a long-running - * process (such as a server rendering many different schemas) can't grow the set without bound. Deduping holds for a - * working set of up to twice this and then stops abruptly: a render producing more distinct messages than that logs - * every one of them on every pass, because no message survives long enough to be seen a second time. That edge is why - * the number is larger than the mistakes a form could plausibly contain — the warnings are raised per field, not per - * mistake, so an array of a thousand rows whose item schema is misconfigured twice is two thousand distinct messages. - * What is bounded is the number of remembered messages, at twice this; how much memory they take follows what callers - * pass, since a remembered message holds the `message` and the string form of the `error`, both of which can carry - * arbitrary text, and a plain-object or array `error` is held as its whole JSON. Two full generations of the warnings - * raised inside this library, which run a few hundred bytes each, stay under a megabyte. The cap is a ceiling rather - * than an allocation, so a form warning about a handful of fields holds a handful of strings. - */ -export const LOG_ONCE_MAX_MESSAGES = 2000; - -/** What has been logged is kept in two generations rather than one set: when `currentMessages` fills it becomes - * `previousMessages` and a fresh one takes its place, so a message is still remembered for at least - * `LOG_ONCE_MAX_MESSAGES` further distinct messages after it is first seen, and memory stays bounded at twice that. - * Both halves matter. Reaching the cap must not un-remember everything at once, or a render producing more distinct - * messages than the cap starts each pass with nothing remembered and logs all of them every time. And it must not stop - * remembering either, since keys carrying a per-row id or index, or another tenant's form under SSR, fill the cap with - * messages that never recur, and anything first seen afterwards would then log on every render for the rest of the - * process's life. - */ -let currentMessages = new Set(); -let previousMessages = new Set(); - -/** Remembers `key` and says whether it is new. A key found in `previousMessages` is deliberately left there rather than - * promoted into `currentMessages`, so one seen on every render is logged a second time once two rotations have - * displaced it. Promoting it would hold it forever, at the cost of a slot in the current generation too, which is what - * caps how large a working set stays deduped: the largest one that survives, of exactly twice the cap, would drop to - * one log per pass. - * - * @param key - What identifies the message - * @returns - True the first time `key` is seen, false while it is still remembered - */ -export function rememberLogged(key: string): boolean { - if (currentMessages.has(key) || previousMessages.has(key)) { - return false; - } - if (currentMessages.size >= LOG_ONCE_MAX_MESSAGES) { - previousMessages = currentMessages; - currentMessages = new Set(); - } - currentMessages.add(key); - return true; -} - -/** Forgets every remembered key */ -export function forgetLogged() { - currentMessages.clear(); - previousMessages.clear(); -} diff --git a/packages/utils/src/templateOverrideWarnings.ts b/packages/utils/src/templateOverrideWarnings.ts index ff9982ebde3..f38f7735a36 100644 --- a/packages/utils/src/templateOverrideWarnings.ts +++ b/packages/utils/src/templateOverrideWarnings.ts @@ -1,9 +1,5 @@ -import { isValidElement } from 'react'; - import describeUnresolvedComponent from './describeUnresolvedComponent.ts'; import type { TemplateWarnSource } from './getTemplate.ts'; -import isComponentType from './isComponentType.ts'; -import { rememberLogged } from './loggedMessages.ts'; import logOnce from './logOnce.ts'; import type { FormContextType, TemplatesType, Registry, StrictRJSFSchema, RJSFSchema } from './types.ts'; @@ -49,40 +45,8 @@ export function isTemplateName(name: string): boolean { return Object.hasOwn(TEMPLATE_NAMES, name); } -/** `isComponentType()` results for each registry's `templates` object. The registry is static for the form's - * lifetime, so its templates are checked once each rather than on every override-free render. */ -const registryTemplateChecks = new WeakMap, Map>(); - -/** Logs once when the registry's own `name` template is not a component, checking each registry template at most once */ -export function warnOnInvalidRegistryTemplate(name: string, customTemplates: Record): void { - if (!isTemplateName(name)) { - return; - } - let perName = registryTemplateChecks.get(customTemplates); - if (perName === undefined) { - perName = new Map(); - registryTemplateChecks.set(customTemplates, perName); - } - let valid = perName.get(name); - if (valid === undefined) { - valid = isComponentType(customTemplates[name]); - perName.set(name, valid); - } - if (!valid) { - const fallback = customTemplates[name]; - // A registry entry is used as given, never looked up by name, so a string one is described as the value it is - const description = describeUnresolvedComponent( - typeof fallback === 'string' ? undefined : fallback, - fallback, - 'registered template', - 'MyTemplate', - ); - logOnce(`The registry's ${name} ${description}.`); - } -} - -/** Logs the invalid-override warning once per uiSchema entry, naming the field's entry for an own `ui:` option and - * `ui:globalOptions` for an inherited one */ +/** Logs the invalid-override warning once per message, naming the field's entry for an own `ui:` option and + * `ui:globalOptions` for an inherited one, so a changed value logs again */ export function warnOnInvalidOverride< T = unknown, S extends StrictRJSFSchema = RJSFSchema, @@ -98,16 +62,8 @@ export function warnOnInvalidOverride< // The merged options spread the field's own entries last, so a key the field set itself is its own mistake even // when it repeats the global value const fromGlobal = !Object.hasOwn(warnSource.ownUiOptions, name) && Object.hasOwn(globalOverrides, name); - // The key keeps the distinctions the message makes (a string's text, an element, the type) so a changed value - // logs again, and is remembered alongside `logOnce()`'s own messages so `resetLogOnce()` clears it and it is capped the - // same way, while the message is still built only the first time - const source = fromGlobal ? 'global' : warnSource.entryLabel(); - const key = `${name}\0${source}\0${typeof override}\0${typeof override === 'string' && override}\0${isValidElement(override)}`; - if (!rememberLogged(key)) { - return; - } + // `entryLabel()` runs only for an own override, so the label of a field without one is never built + const target = fromGlobal ? `ui:globalOptions.${name}` : `ui:${name} for ${warnSource.entryLabel()}`; const description = describeUnresolvedComponent(override, template, 'registered template', 'MyTemplate'); - const target = fromGlobal ? `ui:globalOptions.${name}` : `ui:${name} for ${source}`; - // oxlint-disable-next-line no-console - console.warn(`${target} ${description}, so it is ignored.`); + logOnce(`${target} ${description}, so it is ignored.`); } diff --git a/packages/utils/test/getTemplate.test.ts b/packages/utils/test/getTemplate.test.ts index c00482a2243..f7ea74f161f 100644 --- a/packages/utils/test/getTemplate.test.ts +++ b/packages/utils/test/getTemplate.test.ts @@ -150,31 +150,6 @@ describe('getTemplate', () => { ); }); - it('describes a string registry entry as the value it is, not as a name to look up', () => { - const invalidRegistry: Registry = { ...registry, templates: { ...registry.templates } }; - const customTemplates: Record = invalidRegistry.templates; - customTemplates.FieldTemplate = 'Foo'; - expect(getTemplate('FieldTemplate', invalidRegistry)).toBe('Foo'); - expect(warnSuppression.consoleSpy).toHaveBeenCalledExactlyOnceWith( - "The registry's FieldTemplate is not a component (got string).", - ); - }); - - it('warns when the registry template is not a component either', () => { - const invalidRegistry: Registry = { ...registry, templates: { ...registry.templates } }; - const customTemplates: Record = invalidRegistry.templates; - customTemplates.FieldTemplate = createElement(FakeTemplate); - const options: UIOptionsType = { FieldTemplate: 42 as unknown as UIOptionsType['FieldTemplate'] }; - const warnSource = { entryLabel: () => '"root"', ownUiOptions: options }; - getTemplate('FieldTemplate', invalidRegistry, options, warnSource); - expect(warnSuppression.consoleSpy.mock.calls).toEqual([ - ['ui:FieldTemplate for "root" is not a component (got number), so it is ignored.'], - [ - "The registry's FieldTemplate is a React element rather than a component (pass MyTemplate, not ).", - ], - ]); - }); - it('names the field in the warning when given its label', () => { const fieldOptions: UIOptionsType = { FieldTemplate: 'Typo' as unknown as UIOptionsType['FieldTemplate'], diff --git a/testing/testSetup.ts b/testing/testSetup.ts index 3aaca3c684f..0e840ac65b6 100644 --- a/testing/testSetup.ts +++ b/testing/testSetup.ts @@ -12,9 +12,6 @@ if (typeof window !== 'undefined') { mockResizeObserver(); } -// logOnce() only logs a message the first time it's seen, and so does getTemplate() for each override warning, so -// without this reset a test asserting on a warning would pass or fail depending on whether an earlier -// test in the same file had already triggered it. -beforeEach(() => { - resetLogOnce(); -}); +// logOnce() only logs a message the first time it's seen, so without this a test asserting on a warning would pass or +// fail depending on whether an earlier test in the same file had already triggered it. +beforeEach(resetLogOnce);