@object-ui/fields 17.5.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6150 -0
- package/README.md +108 -8
- package/dist/FieldEditWidget.d.ts +70 -8
- package/dist/coerceToSafeValue.d.ts +54 -0
- package/dist/currency.d.ts +1 -1
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +5 -5
- package/dist/index.css +53 -11
- package/dist/index.d.ts +653 -128
- package/dist/index.js +4275 -3171
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +20 -4
- package/dist/widgets/BooleanField.d.ts +2 -2
- package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
- package/dist/widgets/CheckboxesField.d.ts +1 -1
- package/dist/widgets/CodeField.d.ts +1 -1
- package/dist/widgets/ColorField.d.ts +1 -1
- package/dist/widgets/CurrencyField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +2 -2
- package/dist/widgets/DateTimeField.d.ts +2 -2
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +43 -2
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +10 -4
- package/dist/widgets/GridField.d.ts +102 -86
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +31 -4
- package/dist/widgets/LookupField.d.ts +9 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +2 -2
- package/dist/widgets/ObjectField.d.ts +10 -3
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +38 -8
- package/dist/widgets/PasswordField.d.ts +15 -1
- package/dist/widgets/PeoplePicker.d.ts +17 -2
- package/dist/widgets/PercentField.d.ts +3 -2
- package/dist/widgets/PersonRow.d.ts +13 -2
- package/dist/widgets/PhoneField.d.ts +1 -1
- package/dist/widgets/QRCodeField.d.ts +1 -1
- package/dist/widgets/RadioField.d.ts +1 -1
- package/dist/widgets/RatingField.d.ts +1 -1
- package/dist/widgets/RecipientPickerField.d.ts +44 -1
- package/dist/widgets/RecordPickerDialog.d.ts +33 -16
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- package/dist/widgets/SelectionTray.d.ts +13 -2
- package/dist/widgets/SignatureField.d.ts +32 -2
- package/dist/widgets/SliderField.d.ts +37 -2
- package/dist/widgets/SummaryField.d.ts +1 -1
- package/dist/widgets/TagsField.d.ts +1 -1
- package/dist/widgets/TextAreaField.d.ts +6 -2
- package/dist/widgets/TextField.d.ts +2 -2
- package/dist/widgets/TimeField.d.ts +2 -2
- package/dist/widgets/UrlField.d.ts +1 -1
- package/dist/widgets/UserField.d.ts +1 -1
- package/dist/widgets/VectorField.d.ts +1 -1
- package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
- package/dist/widgets/address-format.d.ts +27 -7
- package/dist/widgets/file-affordance.d.ts +33 -0
- package/dist/widgets/file-size-guard.d.ts +5 -23
- package/dist/widgets/file-value.d.ts +38 -9
- package/dist/widgets/lookupColumnDisplay.d.ts +110 -0
- package/dist/widgets/nativeDateValue.d.ts +9 -27
- package/dist/widgets/number-format.d.ts +137 -0
- package/dist/widgets/numberBadInput.d.ts +135 -0
- package/dist/widgets/personDisplay.d.ts +22 -4
- package/dist/widgets/richTextDisplay.d.ts +116 -0
- package/dist/widgets/toDomProps.d.ts +10 -1
- package/dist/widgets/toHostGroupProps.d.ts +172 -21
- package/dist/widgets/toHostProps.d.ts +105 -0
- package/dist/widgets/types.d.ts +19 -6
- package/dist/widgets/upload-error-message.d.ts +6 -0
- package/dist/widgets/uploadingScope.d.ts +142 -0
- package/dist/widgets/useCascadingOptions.d.ts +3 -2
- package/dist/widgets/useRecordQuery.d.ts +4 -2
- package/dist/widgets/useUploadingSignal.d.ts +8 -0
- package/package.json +11 -13
- package/dist/widgets/CharacterCount.d.ts +0 -56
- package/src/FieldEditWidget.test.ts +0 -133
- package/src/FieldEditWidget.tsx +0 -207
- package/src/__tests__/AddressCellRenderer.test.tsx +0 -166
- package/src/__tests__/CurrencyField.minorUnits.test.tsx +0 -191
- package/src/__tests__/CurrencyField.symbol.test.tsx +0 -198
- package/src/__tests__/DateCellRenderer.test.tsx +0 -82
- package/src/__tests__/NumberCellRenderer.grouping.test.tsx +0 -146
- package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
- package/src/__tests__/PercentCellRenderer.locale.test.tsx +0 -152
- package/src/__tests__/PercentCellRenderer.test.tsx +0 -115
- package/src/__tests__/boolean-label-association-e2e.test.tsx +0 -298
- package/src/__tests__/capability-multiselect-retired.test.ts +0 -109
- package/src/__tests__/cell-truncation.test.tsx +0 -96
- package/src/__tests__/composite-group-label-e2e.test.tsx +0 -436
- package/src/__tests__/composite-group-label-readonly-e2e.test.tsx +0 -403
- package/src/__tests__/date-formatter-residue-4272.test.ts +0 -120
- package/src/__tests__/date-locale-channel.test.tsx +0 -305
- package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
- package/src/__tests__/formatCurrency.centsDigit.test.tsx +0 -158
- package/src/__tests__/formatCurrency.minorUnits.test.tsx +0 -221
- package/src/__tests__/group-labelling-declaration.test.ts +0 -148
- package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
- package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
- package/src/__tests__/lookupCellRecordLink.test.tsx +0 -171
- package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
- package/src/__tests__/percent-cell-vs-measure-4576.test.ts +0 -115
- package/src/__tests__/percent-formatter-locale-4553.test.ts +0 -147
- package/src/__tests__/percent-tie-halfup-4590.test.ts +0 -224
- package/src/__tests__/registry-collision.test.ts +0 -91
- package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
- package/src/__tests__/select-label-association-e2e.test.tsx +0 -199
- package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
- package/src/__tests__/validation-feedback.test.tsx +0 -361
- package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
- package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -357
- package/src/__tests__/widget-props-contract.test.tsx +0 -142
- package/src/coerce-safe-value.test.ts +0 -34
- package/src/complex-widgets.test.tsx +0 -924
- package/src/currency.ts +0 -144
- package/src/datetime-widgets.test.tsx +0 -185
- package/src/field-type-alias.multiple.test.ts +0 -81
- package/src/field-type-alias.ts +0 -156
- package/src/field-type-coverage.test.ts +0 -67
- package/src/index.css +0 -44
- package/src/index.tsx +0 -2745
- package/src/standard-widgets.test.tsx +0 -364
- package/src/widgets/AddressField.postalCode.test.tsx +0 -257
- package/src/widgets/AddressField.tsx +0 -182
- package/src/widgets/AddressField.uniqueIds.test.tsx +0 -93
- package/src/widgets/AutoNumberField.tsx +0 -18
- package/src/widgets/AvatarField.tsx +0 -117
- package/src/widgets/BooleanField.tsx +0 -86
- package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
- package/src/widgets/CapabilityMultiSelectField.tsx +0 -241
- package/src/widgets/CharacterCount.tsx +0 -263
- package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
- package/src/widgets/CheckboxesField.tsx +0 -169
- package/src/widgets/CodeField.tsx +0 -41
- package/src/widgets/ColorField.tsx +0 -53
- package/src/widgets/CurrencyField.tsx +0 -135
- package/src/widgets/DateField.tsx +0 -40
- package/src/widgets/DateTimeField.tsx +0 -46
- package/src/widgets/EmailField.tsx +0 -37
- package/src/widgets/FileField.tsx +0 -512
- package/src/widgets/FilterConditionField.tsx +0 -454
- package/src/widgets/FormulaField.tsx +0 -38
- package/src/widgets/FullscreenFieldEditor.tsx +0 -60
- package/src/widgets/GeolocationField.tsx +0 -193
- package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
- package/src/widgets/GridField.test.tsx +0 -632
- package/src/widgets/GridField.tsx +0 -1070
- package/src/widgets/ImageCropperDialog.tsx +0 -245
- package/src/widgets/ImageField.maxSize.test.tsx +0 -154
- package/src/widgets/ImageField.tsx +0 -305
- package/src/widgets/ImageLightbox.test.tsx +0 -52
- package/src/widgets/ImageLightbox.tsx +0 -108
- package/src/widgets/LocationField.tsx +0 -57
- package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
- package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
- package/src/widgets/LookupField.hydration.test.tsx +0 -136
- package/src/widgets/LookupField.idField.test.tsx +0 -61
- package/src/widgets/LookupField.tsx +0 -1295
- package/src/widgets/MarkdownContent.tsx +0 -19
- package/src/widgets/MasterDetailField.tsx +0 -131
- package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
- package/src/widgets/MultiSelectField.tsx +0 -196
- package/src/widgets/NumberField.tsx +0 -49
- package/src/widgets/ObjectField.tsx +0 -94
- package/src/widgets/ObjectRefField.tsx +0 -121
- package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
- package/src/widgets/OptionsEmptyState.test.tsx +0 -169
- package/src/widgets/OptionsEmptyState.tsx +0 -99
- package/src/widgets/PasswordField.tsx +0 -49
- package/src/widgets/PeoplePicker.test.tsx +0 -360
- package/src/widgets/PeoplePicker.tsx +0 -579
- package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
- package/src/widgets/PercentField.tsx +0 -95
- package/src/widgets/PersonRow.tsx +0 -117
- package/src/widgets/PhoneField.tsx +0 -37
- package/src/widgets/QRCodeField.tsx +0 -103
- package/src/widgets/RadioField.cascade.test.tsx +0 -141
- package/src/widgets/RadioField.tsx +0 -143
- package/src/widgets/RatingField.tsx +0 -94
- package/src/widgets/RecipientPickerField.test.tsx +0 -157
- package/src/widgets/RecipientPickerField.tsx +0 -188
- package/src/widgets/RecordPickerDialog.baseFilterShapes.test.tsx +0 -191
- package/src/widgets/RecordPickerDialog.dateLocale.test.tsx +0 -124
- package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
- package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
- package/src/widgets/RecordPickerDialog.tsx +0 -1365
- package/src/widgets/RichTextField.tsx +0 -176
- package/src/widgets/SelectField.cascade.test.tsx +0 -138
- package/src/widgets/SelectField.tsx +0 -193
- package/src/widgets/SelectionTray.tsx +0 -110
- package/src/widgets/SignatureField.tsx +0 -140
- package/src/widgets/SliderField.tsx +0 -39
- package/src/widgets/SummaryField.tsx +0 -31
- package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
- package/src/widgets/TagsField.placeholder.test.tsx +0 -95
- package/src/widgets/TagsField.tsx +0 -88
- package/src/widgets/TextAreaField.tsx +0 -213
- package/src/widgets/TextField.tsx +0 -45
- package/src/widgets/TimeField.tsx +0 -31
- package/src/widgets/UrlField.tsx +0 -46
- package/src/widgets/UserField.tsx +0 -59
- package/src/widgets/UserFieldPicker.test.tsx +0 -78
- package/src/widgets/VectorField.tsx +0 -29
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -166
- package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
- package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
- package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
- package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
- package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
- package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
- package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
- package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
- package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
- package/src/widgets/_cell-renderer-bridge.ts +0 -19
- package/src/widgets/address-format.ts +0 -120
- package/src/widgets/deriveLookupColumns.test.ts +0 -112
- package/src/widgets/deriveLookupColumns.ts +0 -108
- package/src/widgets/file-size-guard.ts +0 -55
- package/src/widgets/file-value.test.ts +0 -230
- package/src/widgets/file-value.ts +0 -246
- package/src/widgets/nativeDateValue.test.ts +0 -115
- package/src/widgets/nativeDateValue.ts +0 -112
- package/src/widgets/openNativePicker.ts +0 -18
- package/src/widgets/optionWidgets.gatedOptions.test.tsx +0 -267
- package/src/widgets/optionWidgets.unconfiguredOptions.test.tsx +0 -150
- package/src/widgets/personDisplay.test.ts +0 -113
- package/src/widgets/personDisplay.ts +0 -92
- package/src/widgets/recentLookups.test.ts +0 -45
- package/src/widgets/recentLookups.ts +0 -47
- package/src/widgets/toDomProps.ts +0 -204
- package/src/widgets/toHostGroupProps.ts +0 -102
- package/src/widgets/types.ts +0 -242
- package/src/widgets/useCascadingOptions.ts +0 -46
- package/src/widgets/useFieldTranslation.ts +0 -132
- package/src/widgets/useRecordQuery.test.ts +0 -174
- package/src/widgets/useRecordQuery.ts +0 -291
- package/src/widgets/useUploadingSignal.test.tsx +0 -62
- package/src/widgets/useUploadingSignal.ts +0 -20
- package/src/withFieldCarrier.tsx +0 -75
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* GATE: every registered field widget must announce a validation failure to
|
|
11
|
-
* assistive tech — `aria-invalid="true"` on some element inside its own form
|
|
12
|
-
* row (objectui#3306, the paradigm of objectui#3291's leak sweep).
|
|
13
|
-
*
|
|
14
|
-
* ## Why a registry-wide sweep
|
|
15
|
-
*
|
|
16
|
-
* This failure class recurs per widget: objectui#3222 fixed seven widgets that
|
|
17
|
-
* computed `aria-invalid` from a prop nobody produced; objectui#3290 fixed the
|
|
18
|
-
* `aria-required` state channel; objectui#3306 fixed `select`, whose Radix
|
|
19
|
-
* `Select.Root` — not being a DOM element — silently swallowed every `aria-*`
|
|
20
|
-
* the form renderer delivered. Each was invisible until someone happened to
|
|
21
|
-
* audit that one widget, because the per-widget tests only cover the widgets
|
|
22
|
-
* someone already fixed. This sweep covers the REGISTRY, so the next widget
|
|
23
|
-
* with a swallowed or missing `aria-invalid` fails here by name instead of
|
|
24
|
-
* shipping.
|
|
25
|
-
*
|
|
26
|
-
* ## The ratchet
|
|
27
|
-
*
|
|
28
|
-
* Widgets listed in {@link NOT_YET_DELIVERED} are known not to deliver the
|
|
29
|
-
* attribute today. They are asserted in the OPPOSITE direction — the sweep
|
|
30
|
-
* proves they still do NOT deliver — so fixing one turns its entry red and
|
|
31
|
-
* forces its removal from the ledger. The ledger can only shrink; a widget can
|
|
32
|
-
* never silently regress INTO it, and a fix can never silently go unrecorded.
|
|
33
|
-
* The remaining entries are tracked as follow-up work (see the ledger's doc
|
|
34
|
-
* comment); this file is the source of truth for what is left.
|
|
35
|
-
*
|
|
36
|
-
* ## Discipline inherited from the #3291 sweep
|
|
37
|
-
*
|
|
38
|
-
* - **An error variant that produces no error tests nothing** — every case
|
|
39
|
-
* asserts the "is required" message rendered before it reads any aria. The
|
|
40
|
-
* `required` check is a PRESENCE check (`null` is missing, `false`/`0` are
|
|
41
|
-
* values — cloud#972), so `null` drives a real failure for every type.
|
|
42
|
-
* - **Popovers are not opened** (happy-dom lacks the pointer-capture APIs
|
|
43
|
-
* Radix needs); the inline control every widget renders is the surface under
|
|
44
|
-
* test, which is exactly where `aria-invalid` must sit.
|
|
45
|
-
* - Widgets are registered from STATIC imports wrapped in `withFieldCarrier`
|
|
46
|
-
* (the real registration seam, objectui#3233), never `registerAllFields()`,
|
|
47
|
-
* whose `React.lazy` loaders are this repo's known flake generator
|
|
48
|
-
* (AGENTS.md 测试纪律 / objectui#3010).
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
import type { ComponentType } from 'react';
|
|
52
|
-
import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest';
|
|
53
|
-
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
|
54
|
-
import '@testing-library/jest-dom';
|
|
55
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
56
|
-
// Module scope: pulls in the form renderer's registration side effect.
|
|
57
|
-
import '@object-ui/components';
|
|
58
|
-
|
|
59
|
-
import { withFieldCarrier } from '../withFieldCarrier';
|
|
60
|
-
import { FORM_FIELD_TYPES } from '../index';
|
|
61
|
-
|
|
62
|
-
import { TextField } from '../widgets/TextField';
|
|
63
|
-
import { TextAreaField } from '../widgets/TextAreaField';
|
|
64
|
-
import { NumberField } from '../widgets/NumberField';
|
|
65
|
-
import { BooleanField } from '../widgets/BooleanField';
|
|
66
|
-
import { SelectField } from '../widgets/SelectField';
|
|
67
|
-
import { DateField } from '../widgets/DateField';
|
|
68
|
-
import { DateTimeField } from '../widgets/DateTimeField';
|
|
69
|
-
import { TimeField } from '../widgets/TimeField';
|
|
70
|
-
import { EmailField } from '../widgets/EmailField';
|
|
71
|
-
import { PhoneField } from '../widgets/PhoneField';
|
|
72
|
-
import { UrlField } from '../widgets/UrlField';
|
|
73
|
-
import { MultiSelectField } from '../widgets/MultiSelectField';
|
|
74
|
-
import { RadioField } from '../widgets/RadioField';
|
|
75
|
-
import { CheckboxesField } from '../widgets/CheckboxesField';
|
|
76
|
-
import { TagsField } from '../widgets/TagsField';
|
|
77
|
-
import { CurrencyField } from '../widgets/CurrencyField';
|
|
78
|
-
import { PercentField } from '../widgets/PercentField';
|
|
79
|
-
import { PasswordField } from '../widgets/PasswordField';
|
|
80
|
-
import { RichTextField } from '../widgets/RichTextField';
|
|
81
|
-
import { LookupField } from '../widgets/LookupField';
|
|
82
|
-
import { FileField } from '../widgets/FileField';
|
|
83
|
-
import { ImageField } from '../widgets/ImageField';
|
|
84
|
-
import { LocationField } from '../widgets/LocationField';
|
|
85
|
-
import { FormulaField } from '../widgets/FormulaField';
|
|
86
|
-
import { SummaryField } from '../widgets/SummaryField';
|
|
87
|
-
import { AutoNumberField } from '../widgets/AutoNumberField';
|
|
88
|
-
import { UserField } from '../widgets/UserField';
|
|
89
|
-
import { ObjectField } from '../widgets/ObjectField';
|
|
90
|
-
import { VectorField } from '../widgets/VectorField';
|
|
91
|
-
import { GridField } from '../widgets/GridField';
|
|
92
|
-
import { ColorField } from '../widgets/ColorField';
|
|
93
|
-
import { SliderField } from '../widgets/SliderField';
|
|
94
|
-
import { RatingField } from '../widgets/RatingField';
|
|
95
|
-
import { CodeField } from '../widgets/CodeField';
|
|
96
|
-
import { AvatarField } from '../widgets/AvatarField';
|
|
97
|
-
import { AddressField } from '../widgets/AddressField';
|
|
98
|
-
import { GeolocationField } from '../widgets/GeolocationField';
|
|
99
|
-
import { SignatureField } from '../widgets/SignatureField';
|
|
100
|
-
import { QRCodeField } from '../widgets/QRCodeField';
|
|
101
|
-
import { ObjectRefField } from '../widgets/ObjectRefField';
|
|
102
|
-
import { FilterConditionField } from '../widgets/FilterConditionField';
|
|
103
|
-
import { RecipientPickerField } from '../widgets/RecipientPickerField';
|
|
104
|
-
|
|
105
|
-
/** Same widget map as the #3291 sweep, so the parity check covers the registry. */
|
|
106
|
-
const WIDGETS: Record<string, ComponentType<any>> = {
|
|
107
|
-
text: TextField,
|
|
108
|
-
textarea: TextAreaField,
|
|
109
|
-
number: NumberField,
|
|
110
|
-
boolean: BooleanField,
|
|
111
|
-
select: SelectField,
|
|
112
|
-
date: DateField,
|
|
113
|
-
datetime: DateTimeField,
|
|
114
|
-
time: TimeField,
|
|
115
|
-
email: EmailField,
|
|
116
|
-
phone: PhoneField,
|
|
117
|
-
url: UrlField,
|
|
118
|
-
multiselect: MultiSelectField,
|
|
119
|
-
radio: RadioField,
|
|
120
|
-
checkboxes: CheckboxesField,
|
|
121
|
-
tags: TagsField,
|
|
122
|
-
currency: CurrencyField,
|
|
123
|
-
percent: PercentField,
|
|
124
|
-
password: PasswordField,
|
|
125
|
-
markdown: RichTextField,
|
|
126
|
-
html: RichTextField,
|
|
127
|
-
richtext: RichTextField,
|
|
128
|
-
lookup: LookupField,
|
|
129
|
-
master_detail: LookupField,
|
|
130
|
-
file: FileField,
|
|
131
|
-
image: ImageField,
|
|
132
|
-
location: LocationField,
|
|
133
|
-
formula: FormulaField,
|
|
134
|
-
summary: SummaryField,
|
|
135
|
-
auto_number: AutoNumberField,
|
|
136
|
-
user: UserField,
|
|
137
|
-
owner: UserField,
|
|
138
|
-
object: ObjectField,
|
|
139
|
-
vector: VectorField,
|
|
140
|
-
grid: GridField,
|
|
141
|
-
color: ColorField,
|
|
142
|
-
slider: SliderField,
|
|
143
|
-
rating: RatingField,
|
|
144
|
-
code: CodeField,
|
|
145
|
-
avatar: AvatarField,
|
|
146
|
-
address: AddressField,
|
|
147
|
-
geolocation: GeolocationField,
|
|
148
|
-
signature: SignatureField,
|
|
149
|
-
qrcode: QRCodeField,
|
|
150
|
-
'object-ref': ObjectRefField,
|
|
151
|
-
'filter-condition': FilterConditionField,
|
|
152
|
-
'recipient-picker': RecipientPickerField,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* THE RATCHET LEDGER — widget types MEASURED (by this very sweep, run with an
|
|
157
|
-
* empty ledger) not to put `aria-invalid` on any element of their row after a
|
|
158
|
-
* real validation failure. Every entry is a known accessibility gap tracked in
|
|
159
|
-
* objectui#3318, not an accepted state: the field shows its red message while
|
|
160
|
-
* assistive tech is told nothing.
|
|
161
|
-
*
|
|
162
|
-
* The objectui#3318 delivery batch cleared 20 of the original 29 entries with
|
|
163
|
-
* the objectui#3222/#3306 pattern. What remains cannot take that pattern
|
|
164
|
-
* as-is; each entry names why, so the follow-up starts from the blocker and
|
|
165
|
-
* not from a re-audit:
|
|
166
|
-
*
|
|
167
|
-
* - `formula` / `summary` / `auto_number` / `vector` — read-only computed /
|
|
168
|
-
* display widgets: they render static text and NO focusable control, so
|
|
169
|
-
* there is no element assistive tech would read the state from (marking the
|
|
170
|
-
* text span would be exactly the non-focusable-wrapper move this ledger's
|
|
171
|
-
* rules forbid). A `required` failure on a non-editable field is a metadata
|
|
172
|
-
* authoring problem more than a widget one.
|
|
173
|
-
* - `grid` — composite line-item editor with its own per-CELL `aria-invalid`
|
|
174
|
-
* for line validation; driving it from a FORM-level failure needs a design
|
|
175
|
-
* (which cell? the add-row button?) rather than a spread.
|
|
176
|
-
* - `slider` — the focusable thumb (`role="slider"`, the ARIA-designated
|
|
177
|
-
* carrier) is rendered inside the synced shadcn `ui/slider.tsx` (a #7
|
|
178
|
-
* no-touch file), which forwards arbitrary props only to its non-focusable
|
|
179
|
-
* Root span; delivering needs a components-level change.
|
|
180
|
-
* - `signature` — the drawing `<canvas>` is not focusable at all (no keyboard
|
|
181
|
-
* input path exists); the only focusable element is the auxiliary Clear
|
|
182
|
-
* button. Needs a real a11y design, not an attribute.
|
|
183
|
-
* - `filter-condition` / `recipient-picker` — dependency-gated: with no
|
|
184
|
-
* sibling `object_name` / `recipient_type` chosen (the state a fresh form
|
|
185
|
-
* and THIS SWEEP render) they show a hint paragraph with no focusable
|
|
186
|
-
* control. Their editable states DO deliver `aria-invalid` since #3318's
|
|
187
|
-
* delivery batch.
|
|
188
|
-
*
|
|
189
|
-
* Do NOT add to this list to make a new widget pass; fix the widget (the
|
|
190
|
-
* objectui#3222/#3306 pattern: spread `toDomProps(props)` onto the real
|
|
191
|
-
* focusable control — never a non-DOM Radix Root — then `aria-invalid=
|
|
192
|
-
* {!!error}` after the spread). An entry is removed by fixing the widget: the
|
|
193
|
-
* sweep asserts each entry still fails to deliver, so a fixed widget turns
|
|
194
|
-
* its own ledger row red until it is removed here. The ledger only shrinks.
|
|
195
|
-
*/
|
|
196
|
-
const NOT_YET_DELIVERED: ReadonlySet<string> = new Set([
|
|
197
|
-
'formula',
|
|
198
|
-
'summary',
|
|
199
|
-
'auto_number',
|
|
200
|
-
'vector',
|
|
201
|
-
'grid',
|
|
202
|
-
'slider',
|
|
203
|
-
'signature',
|
|
204
|
-
'filter-condition',
|
|
205
|
-
'recipient-picker',
|
|
206
|
-
]);
|
|
207
|
-
|
|
208
|
-
/** Option widgets render an "unfillable" placeholder unless offered a list. */
|
|
209
|
-
const OPTION_TYPES = new Set(['select', 'multiselect', 'radio', 'checkboxes', 'tags']);
|
|
210
|
-
const OPTIONS = [
|
|
211
|
-
{ label: 'Alpha', value: 'alpha' },
|
|
212
|
-
{ label: 'Beta', value: 'beta' },
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
/** `null` is MISSING for the presence-check `required` (cloud#972). */
|
|
216
|
-
const MISSING_VALUE = null;
|
|
217
|
-
|
|
218
|
-
function fieldConfig(type: string) {
|
|
219
|
-
return {
|
|
220
|
-
name: 'f',
|
|
221
|
-
label: 'F',
|
|
222
|
-
type: `field:${type}`,
|
|
223
|
-
required: true,
|
|
224
|
-
...(OPTION_TYPES.has(type) ? { options: OPTIONS } : {}),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function renderForm(field: Record<string, unknown>) {
|
|
229
|
-
const Form = ComponentRegistry.get('form')!;
|
|
230
|
-
return render(
|
|
231
|
-
<Form
|
|
232
|
-
schema={{
|
|
233
|
-
type: 'form',
|
|
234
|
-
mode: 'create',
|
|
235
|
-
showSubmit: true,
|
|
236
|
-
showCancel: false,
|
|
237
|
-
submitLabel: 'Create',
|
|
238
|
-
defaultValues: { f: MISSING_VALUE },
|
|
239
|
-
fields: [field],
|
|
240
|
-
onSubmit: () => {},
|
|
241
|
-
}}
|
|
242
|
-
/>,
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function formRow(): Promise<Element> {
|
|
247
|
-
return waitFor(() => {
|
|
248
|
-
const row = document.querySelector('[data-field="f"]');
|
|
249
|
-
if (!row) throw new Error('field row never rendered');
|
|
250
|
-
return row;
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Drive a REAL validation failure and prove it rendered before reading aria:
|
|
256
|
-
* submit, then wait for the "is required" message inside the row.
|
|
257
|
-
*/
|
|
258
|
-
async function failValidation(): Promise<Element> {
|
|
259
|
-
fireEvent.click(screen.getByRole('button', { name: /create/i }));
|
|
260
|
-
await waitFor(() => {
|
|
261
|
-
expect(document.querySelector('[data-field="f"]')?.textContent ?? '').toContain(
|
|
262
|
-
'is required',
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
return formRow();
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
beforeAll(() => {
|
|
269
|
-
for (const [type, Widget] of Object.entries(WIDGETS)) {
|
|
270
|
-
ComponentRegistry.register(type, withFieldCarrier(Widget) as any, {
|
|
271
|
-
namespace: 'field',
|
|
272
|
-
skipFallback: true,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}, 60000);
|
|
276
|
-
|
|
277
|
-
beforeEach(() => {
|
|
278
|
-
if (!(Element.prototype as any).scrollIntoView) {
|
|
279
|
-
(Element.prototype as any).scrollIntoView = () => {};
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
afterEach(() => {
|
|
284
|
-
cleanup();
|
|
285
|
-
vi.restoreAllMocks();
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const ALL_TYPES = Object.keys(WIDGETS);
|
|
289
|
-
const DELIVERING = ALL_TYPES.filter((type) => !NOT_YET_DELIVERED.has(type));
|
|
290
|
-
const LEDGERED = ALL_TYPES.filter((type) => NOT_YET_DELIVERED.has(type));
|
|
291
|
-
|
|
292
|
-
describe('every registered field widget announces a failed validation (objectui#3306)', () => {
|
|
293
|
-
it('covers every field type the form can render', () => {
|
|
294
|
-
expect(Object.keys(WIDGETS).sort()).toEqual([...FORM_FIELD_TYPES].sort());
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('the ledger only names types that exist', () => {
|
|
298
|
-
// A renamed/removed widget must not leave a stale ledger entry that would
|
|
299
|
-
// silently assert against nothing.
|
|
300
|
-
expect([...NOT_YET_DELIVERED].filter((type) => !(type in WIDGETS))).toEqual([]);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it.each(DELIVERING)(
|
|
304
|
-
'field:%s — carries aria-invalid inside its row after a real failure',
|
|
305
|
-
async (type) => {
|
|
306
|
-
renderForm(fieldConfig(type));
|
|
307
|
-
const row = await formRow();
|
|
308
|
-
|
|
309
|
-
// No premature alarm: a widget must not claim invalid before validation
|
|
310
|
-
// ran. (`aria-invalid="false"` is fine and expected — asserting on the
|
|
311
|
-
// "true" value keeps this half meaningful for widgets that stay silent
|
|
312
|
-
// until they fail.)
|
|
313
|
-
expect(row.querySelector('[aria-invalid="true"]')).toBeNull();
|
|
314
|
-
|
|
315
|
-
const after = await failValidation();
|
|
316
|
-
expect(
|
|
317
|
-
after.querySelector('[aria-invalid="true"]'),
|
|
318
|
-
`field:${type} rendered its "is required" message but no element in its row carries aria-invalid="true" — assistive tech is never told the field failed`,
|
|
319
|
-
).not.toBeNull();
|
|
320
|
-
},
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
it.each(LEDGERED)(
|
|
324
|
-
'field:%s — KNOWN GAP: still does not deliver aria-invalid (fix it, then remove it from NOT_YET_DELIVERED)',
|
|
325
|
-
async (type) => {
|
|
326
|
-
renderForm(fieldConfig(type));
|
|
327
|
-
await formRow();
|
|
328
|
-
|
|
329
|
-
const after = await failValidation();
|
|
330
|
-
// The ratchet: the day this widget starts delivering, this assertion
|
|
331
|
-
// fails and the entry MUST be removed from the ledger above.
|
|
332
|
-
expect(
|
|
333
|
-
after.querySelector('[aria-invalid="true"]'),
|
|
334
|
-
`field:${type} now delivers aria-invalid — remove it from NOT_YET_DELIVERED so the sweep guards it forward`,
|
|
335
|
-
).toBeNull();
|
|
336
|
-
},
|
|
337
|
-
);
|
|
338
|
-
});
|