@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,611 +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
|
-
* Record Picker FILTER PANEL — select filter inputs carry the schema's options
|
|
11
|
-
* (#3336).
|
|
12
|
-
*
|
|
13
|
-
* #3333 fixed the picker's table CELLS: they now format through the referenced
|
|
14
|
-
* object's schema `fields` map (`fieldsMeta`) plus the shared i18n option
|
|
15
|
-
* translation, so a `select` column renders the authored option label instead
|
|
16
|
-
* of title-casing the raw stored value.
|
|
17
|
-
*
|
|
18
|
-
* The filter panel is the other face of the same widget and was still broken.
|
|
19
|
-
* `LookupField` turns each typed picker column into a `RecordPickerFilterColumn`
|
|
20
|
-
* (`{ field, label, type: 'select' }`) with no `options`, and the panel's select
|
|
21
|
-
* input renders `col.options?.map(...)` — so the dropdown opened EMPTY and the
|
|
22
|
-
* column could not be filtered at all.
|
|
23
|
-
*
|
|
24
|
-
* ## What these cases assert, and in which direction
|
|
25
|
-
*
|
|
26
|
-
* - **The two reproducers (`renderFilterBar` slot + built-in panel DOM) were
|
|
27
|
-
* RED before the fix**: `options` was `undefined` and the dropdown rendered
|
|
28
|
-
* zero `option` roles.
|
|
29
|
-
* - **The parity case is the load-bearing one.** It asserts the filter option
|
|
30
|
-
* label and the table cell for the SAME field are the same translated string,
|
|
31
|
-
* under an `I18nProvider` that translates `fieldOptions.<obj>.<field>.<value>`.
|
|
32
|
-
* That is what pins "one source" rather than "two derivations that happen to
|
|
33
|
-
* agree today" — a second, filter-only derivation reading `meta.options`
|
|
34
|
-
* directly would satisfy the label assertions but not this one.
|
|
35
|
-
* - **Two cases are deliberate NO-OP pins, green before and after** (called out
|
|
36
|
-
* individually below): a schema field with no `options` still yields an empty
|
|
37
|
-
* dropdown (the fix adds no consumer-side leniency — no options synthesised
|
|
38
|
-
* from the loaded page's raw values), and explicitly authored filter options
|
|
39
|
-
* keep winning over the schema (including the `in`-operator options
|
|
40
|
-
* auto-derived from `lookupFilters`). They guard the precedence and the
|
|
41
|
-
* non-leniency the fix chose, not a defect it repaired.
|
|
42
|
-
*
|
|
43
|
-
* The second suite in this file covers #3422 — what the panel DOWNLOADS once an
|
|
44
|
-
* option is picked. Having the right options to choose from (#3336) and sending
|
|
45
|
-
* the right value for the chosen one are the two halves of the same control, so
|
|
46
|
-
* they are pinned side by side; see that suite's own header for its directions.
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import React from 'react';
|
|
50
|
-
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
|
51
|
-
import { describe, it, expect, vi, beforeAll } from 'vitest';
|
|
52
|
-
import { I18nProvider } from '@object-ui/i18n';
|
|
53
|
-
import { RecordPickerDialog } from './RecordPickerDialog';
|
|
54
|
-
import type { RecordPickerFilterBarProps } from './RecordPickerDialog';
|
|
55
|
-
import { LookupField } from './LookupField';
|
|
56
|
-
import { getCellRenderer } from '../index';
|
|
57
|
-
|
|
58
|
-
// Radix Select opens on pointer events happy-dom/jsdom do not implement.
|
|
59
|
-
beforeAll(() => {
|
|
60
|
-
class MockPointerEvent extends Event {
|
|
61
|
-
button: number;
|
|
62
|
-
ctrlKey: boolean;
|
|
63
|
-
pointerType: string;
|
|
64
|
-
constructor(type: string, props: any = {}) {
|
|
65
|
-
super(type, props);
|
|
66
|
-
this.button = props.button ?? 0;
|
|
67
|
-
this.ctrlKey = props.ctrlKey ?? false;
|
|
68
|
-
this.pointerType = props.pointerType ?? 'mouse';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
(window as any).PointerEvent = MockPointerEvent;
|
|
72
|
-
(HTMLElement.prototype as any).hasPointerCapture = vi.fn();
|
|
73
|
-
(HTMLElement.prototype as any).releasePointerCapture = vi.fn();
|
|
74
|
-
(HTMLElement.prototype as any).scrollIntoView = vi.fn();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const projects = [
|
|
78
|
-
{ id: 'p1', name: 'Line A retooling', project_phase: 'manufacturing' },
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
/** The referenced object's schema `fields` map, as `getObjectSchema` returns it. */
|
|
82
|
-
const projectFields = {
|
|
83
|
-
name: { type: 'text', label: 'Name' },
|
|
84
|
-
project_phase: {
|
|
85
|
-
type: 'select',
|
|
86
|
-
label: 'Project Phase',
|
|
87
|
-
options: [
|
|
88
|
-
{ label: '01 Initiation', value: 'initiation' },
|
|
89
|
-
{ label: '03 Manufacturing', value: 'manufacturing' },
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
// A select field the metadata author never gave options to.
|
|
93
|
-
risk_level: { type: 'select', label: 'Risk Level' },
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
function makeDataSource() {
|
|
97
|
-
const find = vi.fn(async () => ({ data: projects, total: projects.length }));
|
|
98
|
-
const getObjectSchema = vi.fn(async () => ({
|
|
99
|
-
name: 'projects',
|
|
100
|
-
fields: projectFields,
|
|
101
|
-
highlightFields: ['name', 'project_phase'],
|
|
102
|
-
}));
|
|
103
|
-
return { find, getObjectSchema } as any;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const phaseFilterColumn = { field: 'project_phase', label: 'Project Phase', type: 'select' as const };
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Open the built-in filter panel and return the first select trigger in it.
|
|
110
|
-
*
|
|
111
|
-
* The toggle is reached structurally (first button in the filter bar) rather
|
|
112
|
-
* than by its accessible name: one case below mounts a `zh` I18nProvider, and
|
|
113
|
-
* `initReactI18next` makes that instance the default for provider-less renders
|
|
114
|
-
* afterwards — so a `/filters/i` name match would pass or fail depending on
|
|
115
|
-
* test order, not on the behaviour under test.
|
|
116
|
-
*
|
|
117
|
-
* `findBy…`: the filter bar only exists once the filter columns are resolved,
|
|
118
|
-
* which in the LookupField path waits on the referenced object's schema fetch.
|
|
119
|
-
*/
|
|
120
|
-
async function openFilterPanel(): Promise<HTMLElement> {
|
|
121
|
-
const bar = await screen.findByTestId('record-picker-filter-bar');
|
|
122
|
-
fireEvent.click(bar.querySelector('button')!);
|
|
123
|
-
return (await screen.findByTestId('record-picker-filter-panel')) as HTMLElement;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function openFilterSelect(): Promise<Element> {
|
|
127
|
-
const panel = await openFilterPanel();
|
|
128
|
-
const trigger = panel.querySelector('[role="combobox"]');
|
|
129
|
-
expect(trigger).toBeTruthy();
|
|
130
|
-
await act(async () => {
|
|
131
|
-
fireEvent.pointerDown(trigger!, { button: 0 });
|
|
132
|
-
});
|
|
133
|
-
return trigger!;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
describe('RecordPickerDialog filter panel — select options come from the schema (#3336)', () => {
|
|
137
|
-
it('hands the filter bar a select column carrying the schema field options', async () => {
|
|
138
|
-
let captured: RecordPickerFilterBarProps | null = null;
|
|
139
|
-
render(
|
|
140
|
-
<RecordPickerDialog
|
|
141
|
-
open
|
|
142
|
-
onOpenChange={() => {}}
|
|
143
|
-
dataSource={makeDataSource()}
|
|
144
|
-
objectName="projects"
|
|
145
|
-
columns={[{ field: 'project_phase', label: 'Project Phase', type: 'select' }]}
|
|
146
|
-
onSelect={() => {}}
|
|
147
|
-
cellRenderer={getCellRenderer}
|
|
148
|
-
fieldsMeta={projectFields}
|
|
149
|
-
filterColumns={[phaseFilterColumn]}
|
|
150
|
-
renderFilterBar={(p) => {
|
|
151
|
-
captured = p;
|
|
152
|
-
return <div data-testid="external-filter-bar" />;
|
|
153
|
-
}}
|
|
154
|
-
/>,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
await waitFor(() => expect(captured).not.toBeNull());
|
|
158
|
-
// Pre-fix this was `undefined` — the panel had nothing to render.
|
|
159
|
-
expect(captured!.filterColumns[0].options).toEqual([
|
|
160
|
-
{ label: '01 Initiation', value: 'initiation' },
|
|
161
|
-
{ label: '03 Manufacturing', value: 'manufacturing' },
|
|
162
|
-
]);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('renders the schema options in the built-in filter panel dropdown', async () => {
|
|
166
|
-
render(
|
|
167
|
-
<RecordPickerDialog
|
|
168
|
-
open
|
|
169
|
-
onOpenChange={() => {}}
|
|
170
|
-
dataSource={makeDataSource()}
|
|
171
|
-
objectName="projects"
|
|
172
|
-
columns={[
|
|
173
|
-
{ field: 'name', label: 'Name', type: 'text' },
|
|
174
|
-
{ field: 'project_phase', label: 'Project Phase', type: 'select' },
|
|
175
|
-
]}
|
|
176
|
-
onSelect={() => {}}
|
|
177
|
-
cellRenderer={getCellRenderer}
|
|
178
|
-
fieldsMeta={projectFields}
|
|
179
|
-
filterColumns={[phaseFilterColumn]}
|
|
180
|
-
/>,
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
await waitFor(() => expect(screen.getByText('Line A retooling')).toBeInTheDocument());
|
|
184
|
-
await openFilterSelect();
|
|
185
|
-
|
|
186
|
-
// Pre-fix: SelectContent was empty, so neither option role existed.
|
|
187
|
-
expect(await screen.findByRole('option', { name: '03 Manufacturing' })).toBeTruthy();
|
|
188
|
-
expect(screen.getByRole('option', { name: '01 Initiation' })).toBeTruthy();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('filters on the picked option value', async () => {
|
|
192
|
-
const ds = makeDataSource();
|
|
193
|
-
render(
|
|
194
|
-
<RecordPickerDialog
|
|
195
|
-
open
|
|
196
|
-
onOpenChange={() => {}}
|
|
197
|
-
dataSource={ds}
|
|
198
|
-
objectName="projects"
|
|
199
|
-
columns={[{ field: 'project_phase', label: 'Project Phase', type: 'select' }]}
|
|
200
|
-
onSelect={() => {}}
|
|
201
|
-
cellRenderer={getCellRenderer}
|
|
202
|
-
fieldsMeta={projectFields}
|
|
203
|
-
filterColumns={[phaseFilterColumn]}
|
|
204
|
-
/>,
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
208
|
-
await openFilterSelect();
|
|
209
|
-
|
|
210
|
-
const option = await screen.findByRole('option', { name: '03 Manufacturing' });
|
|
211
|
-
await act(async () => {
|
|
212
|
-
fireEvent.click(option);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await waitFor(() => {
|
|
216
|
-
const lastParams = ds.find.mock.calls[ds.find.mock.calls.length - 1][1];
|
|
217
|
-
expect(lastParams.$filter).toMatchObject({ project_phase: 'manufacturing' });
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* The load-bearing case: ONE source for both faces of the widget. The filter
|
|
223
|
-
* option label and the table cell for the same field must be the same
|
|
224
|
-
* translated string — a filter-only re-derivation that skipped the shared
|
|
225
|
-
* i18n option path would show the authored English here.
|
|
226
|
-
*/
|
|
227
|
-
it('translates filter option labels through the same i18n path as the cells', async () => {
|
|
228
|
-
let captured: RecordPickerFilterBarProps | null = null;
|
|
229
|
-
render(
|
|
230
|
-
<I18nProvider
|
|
231
|
-
config={{
|
|
232
|
-
defaultLanguage: 'zh',
|
|
233
|
-
detectBrowserLanguage: false,
|
|
234
|
-
resources: {
|
|
235
|
-
zh: {
|
|
236
|
-
demo: {
|
|
237
|
-
fields: { projects_zh: { project_phase: '项目阶段' } },
|
|
238
|
-
fieldOptions: {
|
|
239
|
-
projects_zh: {
|
|
240
|
-
project_phase: {
|
|
241
|
-
initiation: '01 立项',
|
|
242
|
-
manufacturing: '03 制造',
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
}}
|
|
250
|
-
>
|
|
251
|
-
{/* A dedicated object name: the bundle above stays registered on the
|
|
252
|
-
default i18next instance for later renders, and keying it to
|
|
253
|
-
`projects_zh` keeps it from translating the other cases' options. */}
|
|
254
|
-
<RecordPickerDialog
|
|
255
|
-
open
|
|
256
|
-
onOpenChange={() => {}}
|
|
257
|
-
dataSource={makeDataSource()}
|
|
258
|
-
objectName="projects_zh"
|
|
259
|
-
columns={[{ field: 'project_phase', label: 'Project Phase', type: 'select' }]}
|
|
260
|
-
onSelect={() => {}}
|
|
261
|
-
cellRenderer={getCellRenderer}
|
|
262
|
-
fieldsMeta={projectFields}
|
|
263
|
-
filterColumns={[phaseFilterColumn]}
|
|
264
|
-
renderFilterBar={(p) => {
|
|
265
|
-
captured = p;
|
|
266
|
-
return <div data-testid="external-filter-bar" />;
|
|
267
|
-
}}
|
|
268
|
-
/>
|
|
269
|
-
</I18nProvider>,
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
// The cell already resolved the translated label after #3333 …
|
|
273
|
-
await waitFor(() => expect(screen.getByText('03 制造')).toBeInTheDocument());
|
|
274
|
-
// … and the filter option for the same field now says exactly the same thing.
|
|
275
|
-
await waitFor(() => expect(captured).not.toBeNull());
|
|
276
|
-
const labels = captured!.filterColumns[0].options?.map(o => o.label);
|
|
277
|
-
expect(labels).toEqual(['01 立项', '03 制造']);
|
|
278
|
-
expect(labels).not.toContain('03 Manufacturing');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* NO-OP PIN (green before and after). A select field the author gave no
|
|
283
|
-
* options keeps an empty dropdown: the fix derives options from the schema
|
|
284
|
-
* and adds no consumer-side leniency — it never invents entries from the
|
|
285
|
-
* loaded page's raw stored values, which would hide the metadata gap behind
|
|
286
|
-
* a list that changes per page.
|
|
287
|
-
*/
|
|
288
|
-
it('leaves a select filter optionless when the schema field declares none', async () => {
|
|
289
|
-
let captured: RecordPickerFilterBarProps | null = null;
|
|
290
|
-
render(
|
|
291
|
-
<RecordPickerDialog
|
|
292
|
-
open
|
|
293
|
-
onOpenChange={() => {}}
|
|
294
|
-
dataSource={makeDataSource()}
|
|
295
|
-
objectName="projects"
|
|
296
|
-
columns={[{ field: 'name', label: 'Name', type: 'text' }]}
|
|
297
|
-
onSelect={() => {}}
|
|
298
|
-
cellRenderer={getCellRenderer}
|
|
299
|
-
fieldsMeta={projectFields}
|
|
300
|
-
filterColumns={[{ field: 'risk_level', label: 'Risk Level', type: 'select' }]}
|
|
301
|
-
renderFilterBar={(p) => {
|
|
302
|
-
captured = p;
|
|
303
|
-
return <div data-testid="external-filter-bar" />;
|
|
304
|
-
}}
|
|
305
|
-
/>,
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
await waitFor(() => expect(captured).not.toBeNull());
|
|
309
|
-
expect(captured!.filterColumns[0].options).toBeUndefined();
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* NO-OP PIN (green before and after). Authored filter options are the more
|
|
314
|
-
* specific statement and keep winning — including the `in`-operator options
|
|
315
|
-
* `lookupFilters` auto-derivation builds, which must not be replaced by the
|
|
316
|
-
* schema's full option set (that would widen a deliberately narrowed list).
|
|
317
|
-
*/
|
|
318
|
-
it('keeps explicitly authored filter options over the schema ones', async () => {
|
|
319
|
-
let captured: RecordPickerFilterBarProps | null = null;
|
|
320
|
-
render(
|
|
321
|
-
<RecordPickerDialog
|
|
322
|
-
open
|
|
323
|
-
onOpenChange={() => {}}
|
|
324
|
-
dataSource={makeDataSource()}
|
|
325
|
-
objectName="projects"
|
|
326
|
-
onSelect={() => {}}
|
|
327
|
-
cellRenderer={getCellRenderer}
|
|
328
|
-
fieldsMeta={projectFields}
|
|
329
|
-
lookupFilters={[{ field: 'project_phase', operator: 'in', value: ['manufacturing'] }]}
|
|
330
|
-
renderFilterBar={(p) => {
|
|
331
|
-
captured = p;
|
|
332
|
-
return <div data-testid="external-filter-bar" />;
|
|
333
|
-
}}
|
|
334
|
-
/>,
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
await waitFor(() => expect(captured).not.toBeNull());
|
|
338
|
-
expect(captured!.filterColumns[0].options).toEqual([
|
|
339
|
-
{ label: 'manufacturing', value: 'manufacturing' },
|
|
340
|
-
]);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Record Picker FILTER PANEL — the picked option keeps its AUTHORED type
|
|
346
|
-
* (#3422).
|
|
347
|
-
*
|
|
348
|
-
* Radix `Select` speaks strings: options render as `String(opt.value)` and
|
|
349
|
-
* `onValueChange` returns that string. The panel used to store the string
|
|
350
|
-
* as-is, and `filterValuesToRecord`'s `select` branch passes its value through
|
|
351
|
-
* untouched — so a `lookup_filters`-derived option whose authored value is a
|
|
352
|
-
* number or a boolean queried `{ level: "1" }` against records storing
|
|
353
|
-
* `level: 1`. The user picked an option that plainly has records and got an
|
|
354
|
-
* empty list.
|
|
355
|
-
*
|
|
356
|
-
* The fix maps the control's string back through `col.options` at the control
|
|
357
|
-
* boundary (`matchOptionValue`, the #3090 semantics from
|
|
358
|
-
* `packages/components/src/renderers/form/option-value.ts`) — NOT a coercion
|
|
359
|
-
* in `filterValuesToRecord`, which would have to guess whether `"1"` meant `1`
|
|
360
|
-
* or `"1"`.
|
|
361
|
-
*
|
|
362
|
-
* ## Directions, decided before the run
|
|
363
|
-
*
|
|
364
|
-
* - **The two reproducers are RED before / GREEN after**: `$filter.level` was
|
|
365
|
-
* the string `"1"`, `$filter.active` the string `"false"`. They assert
|
|
366
|
-
* `toBe(1)` / `toBe(false)` plus an explicit `typeof`, because `"1" == 1` and
|
|
367
|
-
* a loose assertion would have passed on the broken build.
|
|
368
|
-
* - **Three cases are NO-OP pins, green before and after** (marked
|
|
369
|
-
* individually): schema-derived options are already strings and must
|
|
370
|
-
* round-trip byte-identical (the fix must not start morphing the path
|
|
371
|
-
* #3336 just made work); and the `number` / `boolean` filter inputs, which
|
|
372
|
-
* already converted explicitly, must keep doing exactly that — the
|
|
373
|
-
* three-branch inconsistency the issue flagged is resolved by lifting
|
|
374
|
-
* `select` up to them, not by touching them.
|
|
375
|
-
* - **The collision case documents a tie-break, not a repair.** Two options
|
|
376
|
-
* whose `String()` forms collide (`1` and `'1'`) cannot both be addressed by
|
|
377
|
-
* a string-speaking control — Radix sees two `SelectItem`s with the same
|
|
378
|
-
* `value`. The first match wins; the case pins that so the resolution is a
|
|
379
|
-
* stated rule rather than an accident of `Array.prototype.find`.
|
|
380
|
-
*/
|
|
381
|
-
describe('RecordPickerDialog filter panel — picked option keeps its authored type (#3422)', () => {
|
|
382
|
-
const tasks = [{ id: 't1', name: 'Recalibrate press', level: 1, active: false }];
|
|
383
|
-
|
|
384
|
-
function makeTaskDataSource() {
|
|
385
|
-
const find = vi.fn(async () => ({ data: tasks, total: tasks.length }));
|
|
386
|
-
const getObjectSchema = vi.fn(async () => ({
|
|
387
|
-
name: 'tasks',
|
|
388
|
-
fields: {
|
|
389
|
-
name: { type: 'text', label: 'Name' },
|
|
390
|
-
level: { type: 'number', label: 'Level' },
|
|
391
|
-
active: { type: 'boolean', label: 'Active' },
|
|
392
|
-
},
|
|
393
|
-
highlightFields: ['name'],
|
|
394
|
-
}));
|
|
395
|
-
return { find, getObjectSchema } as any;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/** The `$filter` of the most recent `find` call. */
|
|
399
|
-
function lastFilter(ds: any): Record<string, any> {
|
|
400
|
-
return ds.find.mock.calls[ds.find.mock.calls.length - 1][1].$filter;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* REPRODUCER (red before the fix). `lookup_filters` with an `in` array of
|
|
405
|
-
* numbers derives a select whose option values are numbers
|
|
406
|
-
* (`LookupFilterDef.value` is `unknown`, so the author's type survives into
|
|
407
|
-
* `options`). Picking one must query the number.
|
|
408
|
-
*/
|
|
409
|
-
it('sends the authored NUMBER for a lookupFilters-derived select option', async () => {
|
|
410
|
-
const ds = makeTaskDataSource();
|
|
411
|
-
render(
|
|
412
|
-
<RecordPickerDialog
|
|
413
|
-
open
|
|
414
|
-
onOpenChange={() => {}}
|
|
415
|
-
dataSource={ds}
|
|
416
|
-
objectName="tasks"
|
|
417
|
-
onSelect={() => {}}
|
|
418
|
-
cellRenderer={getCellRenderer}
|
|
419
|
-
lookupFilters={[{ field: 'level', operator: 'in', value: [1, 2, 3] }]}
|
|
420
|
-
/>,
|
|
421
|
-
);
|
|
422
|
-
|
|
423
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
424
|
-
await openFilterSelect();
|
|
425
|
-
|
|
426
|
-
await act(async () => {
|
|
427
|
-
fireEvent.click(await screen.findByRole('option', { name: '1' }));
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Pre-fix this was the string "1"; `toBe` distinguishes them, `==` would not.
|
|
431
|
-
await waitFor(() => expect(lastFilter(ds).level).toBe(1));
|
|
432
|
-
expect(typeof lastFilter(ds).level).toBe('number');
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
/** REPRODUCER (red before the fix) — the boolean half of the same morph. */
|
|
436
|
-
it('sends the authored BOOLEAN for a lookupFilters-derived select option', async () => {
|
|
437
|
-
const ds = makeTaskDataSource();
|
|
438
|
-
render(
|
|
439
|
-
<RecordPickerDialog
|
|
440
|
-
open
|
|
441
|
-
onOpenChange={() => {}}
|
|
442
|
-
dataSource={ds}
|
|
443
|
-
objectName="tasks"
|
|
444
|
-
onSelect={() => {}}
|
|
445
|
-
cellRenderer={getCellRenderer}
|
|
446
|
-
lookupFilters={[{ field: 'active', operator: 'in', value: [true, false] }]}
|
|
447
|
-
/>,
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
451
|
-
await openFilterSelect();
|
|
452
|
-
|
|
453
|
-
await act(async () => {
|
|
454
|
-
fireEvent.click(await screen.findByRole('option', { name: 'false' }));
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// Pre-fix this was the string "false" — which is TRUTHY, so the query asked
|
|
458
|
-
// for the opposite of what the user picked wherever the backend coerces.
|
|
459
|
-
await waitFor(() => expect(lastFilter(ds).active).toBe(false));
|
|
460
|
-
expect(typeof lastFilter(ds).active).toBe('boolean');
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* NO-OP PIN (green before and after). Schema-derived options are strings by
|
|
465
|
-
* spec (`SelectOptionSchema.value` is `string`), so the #3336 path must come
|
|
466
|
-
* out of the round-trip byte-identical — the fix restores the authored type,
|
|
467
|
-
* it does not invent one.
|
|
468
|
-
*/
|
|
469
|
-
it('round-trips a schema-derived string option unchanged', async () => {
|
|
470
|
-
const ds = makeDataSource();
|
|
471
|
-
render(
|
|
472
|
-
<RecordPickerDialog
|
|
473
|
-
open
|
|
474
|
-
onOpenChange={() => {}}
|
|
475
|
-
dataSource={ds}
|
|
476
|
-
objectName="projects"
|
|
477
|
-
columns={[{ field: 'project_phase', label: 'Project Phase', type: 'select' }]}
|
|
478
|
-
onSelect={() => {}}
|
|
479
|
-
cellRenderer={getCellRenderer}
|
|
480
|
-
fieldsMeta={projectFields}
|
|
481
|
-
filterColumns={[phaseFilterColumn]}
|
|
482
|
-
/>,
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
486
|
-
await openFilterSelect();
|
|
487
|
-
|
|
488
|
-
await act(async () => {
|
|
489
|
-
fireEvent.click(await screen.findByRole('option', { name: '03 Manufacturing' }));
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
await waitFor(() => expect(lastFilter(ds).project_phase).toBe('manufacturing'));
|
|
493
|
-
expect(typeof lastFilter(ds).project_phase).toBe('string');
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* TIE-BREAK PIN. `1` and `'1'` both render as the control string `"1"`, so
|
|
498
|
-
* the control cannot tell them apart — Radix receives two `SelectItem`s with
|
|
499
|
-
* the same `value` and React two children with the same key. The lookup
|
|
500
|
-
* resolves to the FIRST declared match, whichever entry was clicked. This
|
|
501
|
-
* documents the rule; it does not claim such an option list is usable.
|
|
502
|
-
*/
|
|
503
|
-
it('resolves a String() collision to the first declared option', async () => {
|
|
504
|
-
const ds = makeTaskDataSource();
|
|
505
|
-
render(
|
|
506
|
-
<RecordPickerDialog
|
|
507
|
-
open
|
|
508
|
-
onOpenChange={() => {}}
|
|
509
|
-
dataSource={ds}
|
|
510
|
-
objectName="tasks"
|
|
511
|
-
onSelect={() => {}}
|
|
512
|
-
cellRenderer={getCellRenderer}
|
|
513
|
-
filterColumns={[
|
|
514
|
-
{
|
|
515
|
-
field: 'level',
|
|
516
|
-
label: 'Level',
|
|
517
|
-
type: 'select',
|
|
518
|
-
options: [
|
|
519
|
-
{ label: 'Number one', value: 1 },
|
|
520
|
-
{ label: 'String one', value: '1' },
|
|
521
|
-
],
|
|
522
|
-
},
|
|
523
|
-
]}
|
|
524
|
-
/>,
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
528
|
-
await openFilterSelect();
|
|
529
|
-
|
|
530
|
-
// Clicking the SECOND entry still yields the first match — that is the
|
|
531
|
-
// documented consequence of a collision, not a bug in this assertion.
|
|
532
|
-
await act(async () => {
|
|
533
|
-
fireEvent.click(await screen.findByRole('option', { name: 'String one' }));
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
await waitFor(() => expect(lastFilter(ds).level).toBe(1));
|
|
537
|
-
expect(typeof lastFilter(ds).level).toBe('number');
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* NO-OP PIN (green before and after). The `number` and `boolean` filter
|
|
542
|
-
* inputs already converted explicitly (`Number(raw)` / `Boolean(checked)`);
|
|
543
|
-
* the issue flagged the three branches disagreeing, and the fix resolves that
|
|
544
|
-
* by lifting `select` to their standard — these two must be untouched.
|
|
545
|
-
*/
|
|
546
|
-
it('leaves the number and boolean filter inputs converting as before', async () => {
|
|
547
|
-
const ds = makeTaskDataSource();
|
|
548
|
-
render(
|
|
549
|
-
<RecordPickerDialog
|
|
550
|
-
open
|
|
551
|
-
onOpenChange={() => {}}
|
|
552
|
-
dataSource={ds}
|
|
553
|
-
objectName="tasks"
|
|
554
|
-
onSelect={() => {}}
|
|
555
|
-
cellRenderer={getCellRenderer}
|
|
556
|
-
filterColumns={[
|
|
557
|
-
{ field: 'level', label: 'Level', type: 'number' },
|
|
558
|
-
{ field: 'active', label: 'Active', type: 'boolean' },
|
|
559
|
-
]}
|
|
560
|
-
/>,
|
|
561
|
-
);
|
|
562
|
-
|
|
563
|
-
await waitFor(() => expect(ds.find).toHaveBeenCalled());
|
|
564
|
-
const panel = await openFilterPanel();
|
|
565
|
-
|
|
566
|
-
await act(async () => {
|
|
567
|
-
fireEvent.change(panel.querySelector('input[type="number"]')!, { target: { value: '2' } });
|
|
568
|
-
});
|
|
569
|
-
await waitFor(() => expect(lastFilter(ds).level).toBe(2));
|
|
570
|
-
expect(typeof lastFilter(ds).level).toBe('number');
|
|
571
|
-
|
|
572
|
-
await act(async () => {
|
|
573
|
-
fireEvent.click(panel.querySelector('[role="checkbox"]')!);
|
|
574
|
-
});
|
|
575
|
-
await waitFor(() => expect(lastFilter(ds).active).toBe(true));
|
|
576
|
-
expect(typeof lastFilter(ds).active).toBe('boolean');
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
describe('LookupField → picker filter panel wiring (#3336)', () => {
|
|
581
|
-
const lookup = {
|
|
582
|
-
name: 'project',
|
|
583
|
-
label: 'Project',
|
|
584
|
-
type: 'lookup',
|
|
585
|
-
reference_to: 'projects',
|
|
586
|
-
reference_field: 'name',
|
|
587
|
-
} as any;
|
|
588
|
-
|
|
589
|
-
it('offers the referenced object schema options in the derived select filter', async () => {
|
|
590
|
-
render(
|
|
591
|
-
<LookupField
|
|
592
|
-
field={lookup}
|
|
593
|
-
value={undefined}
|
|
594
|
-
onChange={vi.fn()}
|
|
595
|
-
readonly={false}
|
|
596
|
-
dataSource={makeDataSource()}
|
|
597
|
-
/>,
|
|
598
|
-
);
|
|
599
|
-
|
|
600
|
-
// Open the "Browse all records" picker.
|
|
601
|
-
await act(async () => {
|
|
602
|
-
fireEvent.click(await screen.findByTestId('browse-all-records'));
|
|
603
|
-
});
|
|
604
|
-
await waitFor(() => expect(screen.getByTestId('record-picker-dialog')).toBeInTheDocument());
|
|
605
|
-
|
|
606
|
-
// `project_phase` reaches the filter bar because it is a typed picker column
|
|
607
|
-
// derived from `highlightFields`; its options come from the same schema.
|
|
608
|
-
await openFilterSelect();
|
|
609
|
-
expect(await screen.findByRole('option', { name: '03 Manufacturing' })).toBeTruthy();
|
|
610
|
-
});
|
|
611
|
-
});
|