@object-ui/fields 17.5.0 → 17.6.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 +714 -0
- package/dist/FieldEditWidget.d.ts +16 -2
- package/dist/field-type-alias.d.ts +16 -0
- package/dist/index.cjs +4 -4
- package/dist/index.css +27 -9
- package/dist/index.d.ts +425 -72
- package/dist/index.js +2530 -2228
- package/dist/widgets/AddressField.d.ts +3 -3
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +1 -1
- package/dist/widgets/BooleanField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +28 -1
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +9 -2
- package/dist/widgets/GeolocationField.d.ts +1 -1
- package/dist/widgets/GridField.d.ts +56 -4
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +1 -1
- package/dist/widgets/LookupField.d.ts +1 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +1 -1
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +13 -7
- package/dist/widgets/PasswordField.d.ts +1 -1
- package/dist/widgets/PercentField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/RecordPickerDialog.d.ts +2 -4
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +1 -1
- 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 +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- 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/lookupColumnDisplay.d.ts +93 -0
- package/dist/widgets/richTextDisplay.d.ts +80 -0
- package/dist/widgets/toHostGroupProps.d.ts +119 -21
- package/dist/widgets/types.d.ts +8 -3
- package/package.json +9 -10
- 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,360 +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
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
10
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
-
import { PeoplePicker } from './PeoplePicker';
|
|
12
|
-
import { pushRecentLookupId } from './recentLookups';
|
|
13
|
-
|
|
14
|
-
const users = [
|
|
15
|
-
{ id: 'u1', name: 'Amy Lin', email: 'amy@x.io', primary_business_unit_id: { name: 'Sales' } },
|
|
16
|
-
{ id: 'u2', name: 'Bob Wu', email: 'bob@x.io' },
|
|
17
|
-
{ id: 'u3', name: 'Cara Xu', email: 'cara@x.io' },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
function makeDataSource() {
|
|
21
|
-
const find = vi.fn(async (_obj: string, params: any) => {
|
|
22
|
-
const idIn = params?.$filter?.id?.$in as any[] | undefined;
|
|
23
|
-
if (idIn) {
|
|
24
|
-
const set = new Set(idIn.map(String));
|
|
25
|
-
const data = users.filter(u => set.has(String(u.id)));
|
|
26
|
-
return { data, total: data.length };
|
|
27
|
-
}
|
|
28
|
-
const search: string | undefined = params?.$search;
|
|
29
|
-
const data = search
|
|
30
|
-
? users.filter(u => u.name.toLowerCase().includes(search.toLowerCase()))
|
|
31
|
-
: users;
|
|
32
|
-
return { data, total: data.length };
|
|
33
|
-
});
|
|
34
|
-
return { find } as any;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const baseProps = {
|
|
38
|
-
open: true,
|
|
39
|
-
objectName: 'sys_user',
|
|
40
|
-
subtitleFields: ['primary_business_unit_id.name', 'email'],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
// jsdom has no matchMedia; useIsMobile needs it. Default to desktop width.
|
|
45
|
-
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1280 });
|
|
46
|
-
window.matchMedia = ((query: string) => ({
|
|
47
|
-
matches: window.innerWidth < 768,
|
|
48
|
-
media: query,
|
|
49
|
-
onchange: null,
|
|
50
|
-
addEventListener: () => {},
|
|
51
|
-
removeEventListener: () => {},
|
|
52
|
-
addListener: () => {},
|
|
53
|
-
removeListener: () => {},
|
|
54
|
-
dispatchEvent: () => false,
|
|
55
|
-
})) as any;
|
|
56
|
-
try {
|
|
57
|
-
window.localStorage.clear();
|
|
58
|
-
} catch {
|
|
59
|
-
/* ignore */
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('PeoplePicker', () => {
|
|
64
|
-
it('lists candidate rows on open and filters by $search', async () => {
|
|
65
|
-
const ds = makeDataSource();
|
|
66
|
-
render(
|
|
67
|
-
<PeoplePicker
|
|
68
|
-
{...baseProps}
|
|
69
|
-
dataSource={ds}
|
|
70
|
-
onOpenChange={vi.fn()}
|
|
71
|
-
onSelect={vi.fn()}
|
|
72
|
-
/>,
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
76
|
-
expect(screen.getByText('Sales · amy@x.io')).toBeTruthy();
|
|
77
|
-
expect(screen.getAllByTestId('person-row')).toHaveLength(3);
|
|
78
|
-
|
|
79
|
-
fireEvent.change(screen.getByTestId('people-picker-search'), { target: { value: 'amy' } });
|
|
80
|
-
await waitFor(() => expect(screen.getAllByTestId('person-row')).toHaveLength(1));
|
|
81
|
-
// Relation subtitle (primary_business_unit_id.name) auto-adds $expand.
|
|
82
|
-
expect(ds.find).toHaveBeenLastCalledWith('sys_user', {
|
|
83
|
-
$top: 25,
|
|
84
|
-
$search: 'amy',
|
|
85
|
-
$expand: ['primary_business_unit_id'],
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('applies base candidate filters (banned != true)', async () => {
|
|
90
|
-
const ds = makeDataSource();
|
|
91
|
-
render(
|
|
92
|
-
<PeoplePicker
|
|
93
|
-
{...baseProps}
|
|
94
|
-
dataSource={ds}
|
|
95
|
-
lookupFilters={[{ field: 'banned', operator: 'ne', value: true }]}
|
|
96
|
-
onOpenChange={vi.fn()}
|
|
97
|
-
onSelect={vi.fn()}
|
|
98
|
-
/>,
|
|
99
|
-
);
|
|
100
|
-
await waitFor(() =>
|
|
101
|
-
expect(ds.find).toHaveBeenCalledWith('sys_user', {
|
|
102
|
-
$top: 25,
|
|
103
|
-
$filter: { banned: { $ne: true } },
|
|
104
|
-
$expand: ['primary_business_unit_id'],
|
|
105
|
-
}),
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('single-select commits immediately and closes', async () => {
|
|
110
|
-
const ds = makeDataSource();
|
|
111
|
-
const onSelect = vi.fn();
|
|
112
|
-
const onSelectRecords = vi.fn();
|
|
113
|
-
const onOpenChange = vi.fn();
|
|
114
|
-
render(
|
|
115
|
-
<PeoplePicker
|
|
116
|
-
{...baseProps}
|
|
117
|
-
dataSource={ds}
|
|
118
|
-
onSelect={onSelect}
|
|
119
|
-
onSelectRecords={onSelectRecords}
|
|
120
|
-
onOpenChange={onOpenChange}
|
|
121
|
-
/>,
|
|
122
|
-
);
|
|
123
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
124
|
-
|
|
125
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
126
|
-
expect(onSelect).toHaveBeenCalledWith('u1');
|
|
127
|
-
expect(onSelectRecords).toHaveBeenCalledWith([users[0]]);
|
|
128
|
-
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('multi-select echoes chips and confirms ids + records', async () => {
|
|
132
|
-
const ds = makeDataSource();
|
|
133
|
-
const onSelect = vi.fn();
|
|
134
|
-
const onSelectRecords = vi.fn();
|
|
135
|
-
render(
|
|
136
|
-
<PeoplePicker
|
|
137
|
-
{...baseProps}
|
|
138
|
-
multiple
|
|
139
|
-
dataSource={ds}
|
|
140
|
-
onSelect={onSelect}
|
|
141
|
-
onSelectRecords={onSelectRecords}
|
|
142
|
-
onOpenChange={vi.fn()}
|
|
143
|
-
/>,
|
|
144
|
-
);
|
|
145
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
146
|
-
|
|
147
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
148
|
-
fireEvent.click(screen.getByText('Bob Wu'));
|
|
149
|
-
await waitFor(() => expect(screen.getAllByTestId('selection-chip')).toHaveLength(2));
|
|
150
|
-
|
|
151
|
-
fireEvent.click(screen.getByText('Confirm'));
|
|
152
|
-
expect(onSelect).toHaveBeenCalledWith(['u1', 'u2']);
|
|
153
|
-
expect(onSelectRecords).toHaveBeenCalledWith([users[0], users[1]]);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('surfaces recent contacts in their own section when idle', async () => {
|
|
157
|
-
pushRecentLookupId('sys_user', 'u3');
|
|
158
|
-
const ds = makeDataSource();
|
|
159
|
-
render(
|
|
160
|
-
<PeoplePicker
|
|
161
|
-
{...baseProps}
|
|
162
|
-
dataSource={ds}
|
|
163
|
-
onOpenChange={vi.fn()}
|
|
164
|
-
onSelect={vi.fn()}
|
|
165
|
-
/>,
|
|
166
|
-
);
|
|
167
|
-
await waitFor(() => expect(screen.getByText('Recently used')).toBeTruthy());
|
|
168
|
-
// Cara appears under "Recently used" and is de-duped out of the main list.
|
|
169
|
-
expect(screen.getByText('Cara Xu')).toBeTruthy();
|
|
170
|
-
expect(screen.getByText('All results')).toBeTruthy();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('keyboard: ArrowDown then Enter commits the active row (single)', async () => {
|
|
174
|
-
const ds = makeDataSource();
|
|
175
|
-
const onSelect = vi.fn();
|
|
176
|
-
render(
|
|
177
|
-
<PeoplePicker {...baseProps} dataSource={ds} onSelect={onSelect} onSelectRecords={vi.fn()} onOpenChange={vi.fn()} />,
|
|
178
|
-
);
|
|
179
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
180
|
-
const search = screen.getByTestId('people-picker-search');
|
|
181
|
-
fireEvent.keyDown(search, { key: 'ArrowDown' });
|
|
182
|
-
// Wait for the cursor to actually land on the first row before committing —
|
|
183
|
-
// guards against a late background re-render swallowing the keypress (the
|
|
184
|
-
// old identity-keyed cursor reset made this flaky on slow CI).
|
|
185
|
-
await waitFor(() =>
|
|
186
|
-
expect(screen.getAllByTestId('person-row')[0].getAttribute('data-active')).toBe('true'),
|
|
187
|
-
);
|
|
188
|
-
fireEvent.keyDown(search, { key: 'Enter' });
|
|
189
|
-
expect(onSelect).toHaveBeenCalledWith('u1');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('keyboard: cursor resets when the results are replaced by a search', async () => {
|
|
193
|
-
// Pins the semantics the render-phase reset must preserve: a NEW result
|
|
194
|
-
// set does not inherit the previous set's cursor. (The reset moved out of
|
|
195
|
-
// an effect into the render phase so it can never land AFTER a subsequent
|
|
196
|
-
// ArrowDown — the CI flake on the test above.)
|
|
197
|
-
const ds = makeDataSource();
|
|
198
|
-
render(
|
|
199
|
-
<PeoplePicker {...baseProps} dataSource={ds} onSelect={vi.fn()} onSelectRecords={vi.fn()} onOpenChange={vi.fn()} />,
|
|
200
|
-
);
|
|
201
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
202
|
-
const search = screen.getByTestId('people-picker-search');
|
|
203
|
-
fireEvent.keyDown(search, { key: 'ArrowDown' });
|
|
204
|
-
await waitFor(() =>
|
|
205
|
-
expect(screen.getAllByTestId('person-row')[0].getAttribute('data-active')).toBe('true'),
|
|
206
|
-
);
|
|
207
|
-
fireEvent.change(search, { target: { value: 'bob' } });
|
|
208
|
-
await waitFor(() => expect(screen.getAllByTestId('person-row')).toHaveLength(1));
|
|
209
|
-
expect(screen.getAllByTestId('person-row')[0].getAttribute('data-active')).not.toBe('true');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('keyboard: Backspace on empty search removes the last chip (multi)', async () => {
|
|
213
|
-
const ds = makeDataSource();
|
|
214
|
-
render(
|
|
215
|
-
<PeoplePicker {...baseProps} multiple dataSource={ds} onSelect={vi.fn()} onSelectRecords={vi.fn()} onOpenChange={vi.fn()} />,
|
|
216
|
-
);
|
|
217
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
218
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
219
|
-
fireEvent.click(screen.getByText('Bob Wu'));
|
|
220
|
-
await waitFor(() => expect(screen.getAllByTestId('selection-chip')).toHaveLength(2));
|
|
221
|
-
fireEvent.keyDown(screen.getByTestId('people-picker-search'), { key: 'Backspace' });
|
|
222
|
-
await waitFor(() => expect(screen.getAllByTestId('selection-chip')).toHaveLength(1));
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('multi: Clear all empties the tray', async () => {
|
|
226
|
-
const ds = makeDataSource();
|
|
227
|
-
render(
|
|
228
|
-
<PeoplePicker {...baseProps} multiple dataSource={ds} onSelect={vi.fn()} onSelectRecords={vi.fn()} onOpenChange={vi.fn()} />,
|
|
229
|
-
);
|
|
230
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
231
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
232
|
-
fireEvent.click(screen.getByText('Bob Wu'));
|
|
233
|
-
await waitFor(() => expect(screen.getAllByTestId('selection-chip')).toHaveLength(2));
|
|
234
|
-
fireEvent.click(screen.getByTestId('selection-clear'));
|
|
235
|
-
await waitFor(() => expect(screen.queryAllByTestId('selection-chip')).toHaveLength(0));
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('highlights the matched term in rows while searching', async () => {
|
|
239
|
-
const ds = makeDataSource();
|
|
240
|
-
// Dialog content portals onto document.body, so query the document, not the
|
|
241
|
-
// render container.
|
|
242
|
-
render(<PeoplePicker {...baseProps} dataSource={ds} onSelect={vi.fn()} onOpenChange={vi.fn()} />);
|
|
243
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
244
|
-
fireEvent.change(screen.getByTestId('people-picker-search'), { target: { value: 'amy' } });
|
|
245
|
-
await waitFor(() => expect(document.querySelector('mark')).toBeTruthy());
|
|
246
|
-
expect(document.querySelector('mark')?.textContent?.toLowerCase()).toContain('am');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('shows an error with a retry that refetches', async () => {
|
|
250
|
-
let calls = 0;
|
|
251
|
-
const ds = {
|
|
252
|
-
find: vi.fn(async () => {
|
|
253
|
-
calls += 1;
|
|
254
|
-
if (calls === 1) throw new Error('boom');
|
|
255
|
-
return { data: [users[0]], total: 1 };
|
|
256
|
-
}),
|
|
257
|
-
} as any;
|
|
258
|
-
render(
|
|
259
|
-
<PeoplePicker {...baseProps} dataSource={ds} onSelect={vi.fn()} onOpenChange={vi.fn()} />,
|
|
260
|
-
);
|
|
261
|
-
await waitFor(() => expect(screen.getByText('boom')).toBeTruthy());
|
|
262
|
-
fireEvent.click(screen.getByText('Retry'));
|
|
263
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('renders a Dialog on desktop and a bottom Sheet on mobile', async () => {
|
|
267
|
-
const ds = makeDataSource();
|
|
268
|
-
const { unmount } = render(
|
|
269
|
-
<PeoplePicker {...baseProps} dataSource={ds} onSelect={vi.fn()} onOpenChange={vi.fn()} />,
|
|
270
|
-
);
|
|
271
|
-
await waitFor(() => expect(screen.getByTestId('people-picker-dialog')).toBeTruthy());
|
|
272
|
-
expect(screen.queryByTestId('people-picker-sheet')).toBeNull();
|
|
273
|
-
unmount();
|
|
274
|
-
|
|
275
|
-
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 375 });
|
|
276
|
-
render(<PeoplePicker {...baseProps} dataSource={ds} onSelect={vi.fn()} onOpenChange={vi.fn()} />);
|
|
277
|
-
await waitFor(() => expect(screen.getByTestId('people-picker-sheet')).toBeTruthy());
|
|
278
|
-
expect(screen.queryByTestId('people-picker-dialog')).toBeNull();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('seeds the tray from an existing multi value (edit mode)', async () => {
|
|
282
|
-
const ds = makeDataSource();
|
|
283
|
-
render(
|
|
284
|
-
<PeoplePicker
|
|
285
|
-
{...baseProps}
|
|
286
|
-
multiple
|
|
287
|
-
dataSource={ds}
|
|
288
|
-
value={['u1']}
|
|
289
|
-
onSelect={vi.fn()}
|
|
290
|
-
onSelectRecords={vi.fn()}
|
|
291
|
-
onOpenChange={vi.fn()}
|
|
292
|
-
/>,
|
|
293
|
-
);
|
|
294
|
-
await waitFor(() => expect(screen.getAllByTestId('selection-chip')).toHaveLength(1));
|
|
295
|
-
expect(screen.getByTestId('selection-tray').textContent).toContain('Amy Lin');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('inline: renders an anchored popover, not a modal dialog', async () => {
|
|
299
|
-
const ds = makeDataSource();
|
|
300
|
-
render(
|
|
301
|
-
<PeoplePicker
|
|
302
|
-
{...baseProps}
|
|
303
|
-
inline
|
|
304
|
-
trigger={<button data-testid="inline-trigger">open</button>}
|
|
305
|
-
dataSource={ds}
|
|
306
|
-
onOpenChange={vi.fn()}
|
|
307
|
-
onSelect={vi.fn()}
|
|
308
|
-
/>,
|
|
309
|
-
);
|
|
310
|
-
await waitFor(() => expect(screen.getByTestId('people-picker-inline')).toBeTruthy());
|
|
311
|
-
expect(screen.queryByTestId('people-picker-dialog')).toBeNull();
|
|
312
|
-
expect(screen.getByTestId('inline-trigger')).toBeTruthy();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('inline multi: toggling a row commits live, with no confirm button', async () => {
|
|
316
|
-
const ds = makeDataSource();
|
|
317
|
-
const onSelect = vi.fn();
|
|
318
|
-
render(
|
|
319
|
-
<PeoplePicker
|
|
320
|
-
{...baseProps}
|
|
321
|
-
inline
|
|
322
|
-
multiple
|
|
323
|
-
value={[]}
|
|
324
|
-
dataSource={ds}
|
|
325
|
-
trigger={<button data-testid="inline-trigger" />}
|
|
326
|
-
onOpenChange={vi.fn()}
|
|
327
|
-
onSelect={onSelect}
|
|
328
|
-
onSelectRecords={vi.fn()}
|
|
329
|
-
/>,
|
|
330
|
-
);
|
|
331
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
332
|
-
// Inline has no staging tray / confirm — selection lives in the field chips.
|
|
333
|
-
expect(screen.queryByText('Confirm')).toBeNull();
|
|
334
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
335
|
-
expect(onSelect).toHaveBeenLastCalledWith(['u1']);
|
|
336
|
-
fireEvent.click(screen.getByText('Bob Wu'));
|
|
337
|
-
expect(onSelect).toHaveBeenLastCalledWith(['u1', 'u2']);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('inline single: clicking a row commits and closes', async () => {
|
|
341
|
-
const ds = makeDataSource();
|
|
342
|
-
const onSelect = vi.fn();
|
|
343
|
-
const onOpenChange = vi.fn();
|
|
344
|
-
render(
|
|
345
|
-
<PeoplePicker
|
|
346
|
-
{...baseProps}
|
|
347
|
-
inline
|
|
348
|
-
dataSource={ds}
|
|
349
|
-
trigger={<button data-testid="inline-trigger" />}
|
|
350
|
-
onOpenChange={onOpenChange}
|
|
351
|
-
onSelect={onSelect}
|
|
352
|
-
onSelectRecords={vi.fn()}
|
|
353
|
-
/>,
|
|
354
|
-
);
|
|
355
|
-
await waitFor(() => expect(screen.getByText('Amy Lin')).toBeTruthy());
|
|
356
|
-
fireEvent.click(screen.getByText('Amy Lin'));
|
|
357
|
-
expect(onSelect).toHaveBeenCalledWith('u1');
|
|
358
|
-
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
359
|
-
});
|
|
360
|
-
});
|