@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,146 +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
|
-
* objectui#4033 (source thread objectstack#5067) — the number cell renderer
|
|
11
|
-
* used to build `new Intl.NumberFormat('en-US', …)` with grouping left at the
|
|
12
|
-
* Intl default, so a four-digit YEAR stored as `Field.number({ scale: 0 })`
|
|
13
|
-
* rendered as `2,026` in every locale and no field property could turn it off.
|
|
14
|
-
*
|
|
15
|
-
* Two independent pins live here:
|
|
16
|
-
*
|
|
17
|
-
* 1. **Ordinal grouping** — a declared `scale: 0` with no currency is an
|
|
18
|
-
* integer/ordinal surface, so grouping is suppressed. Interim default; a
|
|
19
|
-
* future spec-level presentation hint overrides it.
|
|
20
|
-
* 2. **Locale** — grouping and decimal marks follow the ACTIVE display locale,
|
|
21
|
-
* never a hardcoded `en-US`.
|
|
22
|
-
*
|
|
23
|
-
* The controls are as load-bearing as the fixes: currency keeps grouping, a
|
|
24
|
-
* non-zero `scale` keeps grouping, and an UNDECLARED scale keeps grouping
|
|
25
|
-
* (`scale` is optional in the spec and absent means "decimals unknown", not
|
|
26
|
-
* "integer" — see the renderer's own comment).
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { describe, it, expect } from 'vitest';
|
|
30
|
-
import React from 'react';
|
|
31
|
-
import { render, screen } from '@testing-library/react';
|
|
32
|
-
import '@testing-library/jest-dom';
|
|
33
|
-
import { LocalizationProvider } from '@object-ui/i18n';
|
|
34
|
-
import { NumberCellRenderer, CurrencyCellRenderer } from '../index';
|
|
35
|
-
|
|
36
|
-
const renderNumber = (
|
|
37
|
-
value: unknown,
|
|
38
|
-
field: Record<string, unknown> = {},
|
|
39
|
-
locale?: string,
|
|
40
|
-
) =>
|
|
41
|
-
render(
|
|
42
|
-
<LocalizationProvider value={{ locale }}>
|
|
43
|
-
<NumberCellRenderer value={value as any} field={{ type: 'number', ...field } as any} />
|
|
44
|
-
</LocalizationProvider>,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const renderCurrency = (
|
|
48
|
-
value: unknown,
|
|
49
|
-
field: Record<string, unknown> = {},
|
|
50
|
-
locale?: string,
|
|
51
|
-
) =>
|
|
52
|
-
render(
|
|
53
|
-
<LocalizationProvider value={{ locale }}>
|
|
54
|
-
<CurrencyCellRenderer value={value as any} field={{ type: 'currency', ...field } as any} />
|
|
55
|
-
</LocalizationProvider>,
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
describe('NumberCellRenderer — ordinal grouping (objectui#4033)', () => {
|
|
59
|
-
// The card's exact fixture: `year: Field.number({ scale: 0, min: 1900 })`.
|
|
60
|
-
it.each(['en-US', 'de-DE', 'zh-CN', 'fr-FR'])(
|
|
61
|
-
'renders a scale-0 year ungrouped in %s',
|
|
62
|
-
(locale) => {
|
|
63
|
-
renderNumber(2026, { scale: 0 }, locale);
|
|
64
|
-
expect(screen.getByText('2026')).toBeInTheDocument();
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
it('suppresses grouping for every scale-0 magnitude, not just four digits', () => {
|
|
69
|
-
renderNumber(1234567, { scale: 0 }, 'en-US');
|
|
70
|
-
expect(screen.getByText('1234567')).toBeInTheDocument();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('CONTROL: a non-zero scale keeps grouping', () => {
|
|
74
|
-
renderNumber(1234.5, { scale: 2 }, 'en-US');
|
|
75
|
-
expect(screen.getByText('1,234.50')).toBeInTheDocument();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('CONTROL: an UNDECLARED scale keeps grouping (absent ≠ integer)', () => {
|
|
79
|
-
renderNumber(2026, {}, 'en-US');
|
|
80
|
-
expect(screen.getByText('2,026')).toBeInTheDocument();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('NumberCellRenderer — active display locale (objectui#4033)', () => {
|
|
85
|
-
it('uses the active locale decimal + grouping marks (de-DE)', () => {
|
|
86
|
-
renderNumber(1234.5, { scale: 1 }, 'de-DE');
|
|
87
|
-
expect(screen.getByText('1.234,5')).toBeInTheDocument();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('uses the active locale decimal + grouping marks (en-US)', () => {
|
|
91
|
-
renderNumber(1234.5, { scale: 1 }, 'en-US');
|
|
92
|
-
expect(screen.getByText('1,234.5')).toBeInTheDocument();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('uses the active locale decimal + grouping marks (fr-FR)', () => {
|
|
96
|
-
// fr-FR groups with U+202F (NARROW NO-BREAK SPACE) and uses "," for the
|
|
97
|
-
// decimal mark — nothing an en-US formatter can produce.
|
|
98
|
-
//
|
|
99
|
-
// Asserted against raw `textContent`, NOT `getByText`: testing-library's
|
|
100
|
-
// default normalizer collapses `\s+` (which includes U+202F) down to an
|
|
101
|
-
// ASCII space, so a `getByText` assertion here can only ever prove "some
|
|
102
|
-
// whitespace" and would pass on the wrong separator. The escape sequence is
|
|
103
|
-
// written out rather than pasted — a raw U+202F in source renders as
|
|
104
|
-
// nothing and is unfindable by grep.
|
|
105
|
-
const { container } = renderNumber(1234.5, { scale: 1 }, 'fr-FR');
|
|
106
|
-
expect(container.textContent).toBe('1 234,5');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('does not take a malformed tenant locale down with it', () => {
|
|
110
|
-
// `locale` arrives from a server response (ADR-0053); a junk tag must
|
|
111
|
-
// degrade to the runtime default, never throw out of a grid cell.
|
|
112
|
-
renderNumber(1234.5, { scale: 1 }, 'not a locale');
|
|
113
|
-
expect(screen.getByText(/1.?234.5/)).toBeInTheDocument();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('CurrencyCellRenderer — CONTROL, unaffected by the ordinal default', () => {
|
|
118
|
-
it('keeps grouping and the currency symbol for a whole amount', () => {
|
|
119
|
-
renderCurrency(5000000, { currency: 'USD' }, 'en-US');
|
|
120
|
-
expect(screen.getByText('$5,000,000')).toBeInTheDocument();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('keeps grouping and the fractional part when the amount is not whole', () => {
|
|
124
|
-
renderCurrency(1234.5, { currency: 'USD' }, 'en-US');
|
|
125
|
-
// This control pinned the CURRENT behaviour when #4033 landed — `$1,234.5`,
|
|
126
|
-
// NOT the `$1,234.50` that `formatCurrency`'s own doc comment promises
|
|
127
|
-
// ("Salesforce convention: `$1,234.50` keeps cents"). That was a real
|
|
128
|
-
// defect, deliberately filed rather than folded in (objectui#4332) and
|
|
129
|
-
// pinned here so its fix would flip a watched test instead of silently
|
|
130
|
-
// changing an unwatched surface. #4332 is now fixed, so the expectation
|
|
131
|
-
// moves to the promised form; grouping — this control's actual subject —
|
|
132
|
-
// is unchanged, which is still what #4033 needs it to prove.
|
|
133
|
-
expect(screen.getByText('$1,234.50')).toBeInTheDocument();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('follows the active locale for currency too', () => {
|
|
137
|
-
renderCurrency(5000000, { currency: 'EUR' }, 'de-DE');
|
|
138
|
-
// de-DE: grouped with dots and a TRAILING symbol, vs en-US's leading one.
|
|
139
|
-
expect(screen.getByText(/^5\.000\.000\s*€$/)).toBeInTheDocument();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('a currency field with NO resolved currency still groups (money, not an ordinal)', () => {
|
|
143
|
-
renderCurrency(5000000, {}, 'en-US');
|
|
144
|
-
expect(screen.getByText('5,000,000')).toBeInTheDocument();
|
|
145
|
-
});
|
|
146
|
-
});
|
|
@@ -1,60 +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 { describe, it, expect } from 'vitest';
|
|
10
|
-
import React from 'react';
|
|
11
|
-
import { render, screen } from '@testing-library/react';
|
|
12
|
-
import '@testing-library/jest-dom';
|
|
13
|
-
import { NumberCellRenderer } from '../index';
|
|
14
|
-
|
|
15
|
-
const renderNumber = (value: unknown, field: Record<string, unknown> = {}) =>
|
|
16
|
-
render(<NumberCellRenderer value={value as any} field={{ type: 'number', ...field } as any} />);
|
|
17
|
-
|
|
18
|
-
describe('NumberCellRenderer', () => {
|
|
19
|
-
it('does not pad trailing zeros using `precision` (total digits, not decimal places)', () => {
|
|
20
|
-
// Regression: a decimal(10, 0) column exposes precision: 10, which used to
|
|
21
|
-
// render `1` as "1.0000000000".
|
|
22
|
-
renderNumber(1, { precision: 10 });
|
|
23
|
-
expect(screen.getByText('1')).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('renders an integer without decimals when no scale is declared', () => {
|
|
27
|
-
renderNumber(16);
|
|
28
|
-
expect(screen.getByText('16')).toBeInTheDocument();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('preserves the value\'s natural decimals when no scale is declared', () => {
|
|
32
|
-
renderNumber(16.5);
|
|
33
|
-
expect(screen.getByText('16.5')).toBeInTheDocument();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('pads to a fixed number of decimals when `scale` is declared', () => {
|
|
37
|
-
renderNumber(16, { scale: 2 });
|
|
38
|
-
expect(screen.getByText('16.00')).toBeInTheDocument();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('honours a scale of 3 with zero padding', () => {
|
|
42
|
-
renderNumber(3.14, { scale: 3 });
|
|
43
|
-
expect(screen.getByText('3.140')).toBeInTheDocument();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('rounds to the declared scale', () => {
|
|
47
|
-
renderNumber(3.14159, { scale: 2 });
|
|
48
|
-
expect(screen.getByText('3.14')).toBeInTheDocument();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('treats scale: 0 as an integer display', () => {
|
|
52
|
-
renderNumber(2, { scale: 0 });
|
|
53
|
-
expect(screen.getByText('2')).toBeInTheDocument();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('renders an empty indicator for nullish values', () => {
|
|
57
|
-
const { container } = renderNumber(null);
|
|
58
|
-
expect(container.textContent).not.toContain('0');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,152 +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
|
-
* objectui#4553 phase 2 — the percent CELL, the fields-internal consumer of
|
|
11
|
-
* `formatPercent`.
|
|
12
|
-
*
|
|
13
|
-
* `PercentCellRenderer` had two rendering paths and BOTH were outside the
|
|
14
|
-
* display-locale channel:
|
|
15
|
-
*
|
|
16
|
-
* const formatted = isWholePercentField
|
|
17
|
-
* ? `${numValue.toFixed(precision)}%` // a second bare toFixed path
|
|
18
|
-
* : formatPercent(numValue, precision); // no locale to pass
|
|
19
|
-
*
|
|
20
|
-
* The branch exists for a real reason — a field named `progress` / `completion`
|
|
21
|
-
* stores 0-100, so it must NOT go through `percentDisplayValue`'s fraction
|
|
22
|
-
* scaling — but it had quietly become a second place where a percent was
|
|
23
|
-
* formatted, and it was the more primitive of the two. Threading only the
|
|
24
|
-
* `formatPercent` half would have made ONE grid internally inconsistent: a
|
|
25
|
-
* `progress` column ungrouped and unlocalized beside a `rate` column that was
|
|
26
|
-
* neither. So both branches now render through the same locale-aware body and
|
|
27
|
-
* differ only in the scaling policy, which is all the branch was ever about.
|
|
28
|
-
*
|
|
29
|
-
* ── Directions, predicted in writing BEFORE the run ──────────────────────
|
|
30
|
-
* Runner machine locale en-US.
|
|
31
|
-
*
|
|
32
|
-
* de, ordinary percent 1234.5 `1235%` → `1.235 %` RED
|
|
33
|
-
* de, `progress` 1234.5 `1235%` → `1.235 %` RED (the second path)
|
|
34
|
-
* en, ordinary percent 1234.5 `1235%` → `1,235%` RED (grouping move)
|
|
35
|
-
* en, `progress` 1234.5 `1235%` → `1,235%` RED (grouping move)
|
|
36
|
-
* the fraction/whole SPLIT itself PIN, green both sides
|
|
37
|
-
* small-value en output PIN, green both sides
|
|
38
|
-
*
|
|
39
|
-
* Provider-mounted by construction (objectui#4514) — the pure-function cases
|
|
40
|
-
* live in `percent-formatter-locale-4553.test.ts`.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
import React from 'react';
|
|
44
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
45
|
-
import { render, screen, cleanup } from '@testing-library/react';
|
|
46
|
-
import '@testing-library/jest-dom';
|
|
47
|
-
import { I18nProvider, LocalizationProvider } from '@object-ui/i18n';
|
|
48
|
-
import { PercentCellRenderer } from '../index';
|
|
49
|
-
|
|
50
|
-
/** German writes a NO-BREAK SPACE (U+00A0) before the percent sign. */
|
|
51
|
-
const NBSP = '\u00a0';
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* A session: the UI language the user picked plus the tenant's regional
|
|
55
|
-
* default. `useDisplayLocale()` resolves tenant regional default → active UI
|
|
56
|
-
* language → 'en'.
|
|
57
|
-
*/
|
|
58
|
-
function renderCell(
|
|
59
|
-
value: unknown,
|
|
60
|
-
field: Record<string, unknown>,
|
|
61
|
-
language: string,
|
|
62
|
-
tenantLocale?: string,
|
|
63
|
-
) {
|
|
64
|
-
return render(
|
|
65
|
-
<I18nProvider
|
|
66
|
-
config={{ defaultLanguage: language, detectBrowserLanguage: false }}
|
|
67
|
-
persistLanguage={false}
|
|
68
|
-
>
|
|
69
|
-
<LocalizationProvider value={{ locale: tenantLocale }}>
|
|
70
|
-
<PercentCellRenderer
|
|
71
|
-
value={value as any}
|
|
72
|
-
field={{ type: 'percent', ...field } as any}
|
|
73
|
-
/>
|
|
74
|
-
</LocalizationProvider>
|
|
75
|
-
</I18nProvider>,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** The cell's text, read off the value span beside the decorative bar. */
|
|
80
|
-
function cellText(): string {
|
|
81
|
-
return document.body.textContent ?? '';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
afterEach(() => cleanup());
|
|
85
|
-
|
|
86
|
-
describe('PercentCellRenderer follows the display locale (objectui#4553)', () => {
|
|
87
|
-
it('de renders the German percent form', () => {
|
|
88
|
-
renderCell(1234.5, { name: 'win_rate' }, 'de');
|
|
89
|
-
expect(cellText()).toContain(`1.235${NBSP}%`);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The SECOND path — `progress` matches WHOLE_PERCENT_FIELD_PATTERN, so this
|
|
94
|
-
* value skips fraction scaling and used to skip `formatPercent` entirely.
|
|
95
|
-
*/
|
|
96
|
-
it('de renders the German form on the whole-percent path too', () => {
|
|
97
|
-
renderCell(1234.5, { name: 'progress' }, 'de');
|
|
98
|
-
expect(cellText()).toContain(`1.235${NBSP}%`);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('en groups at four digits on both paths — the deliberate output move', () => {
|
|
102
|
-
const { unmount } = renderCell(1234.5, { name: 'win_rate' }, 'en');
|
|
103
|
-
expect(cellText()).toContain('1,235%');
|
|
104
|
-
unmount();
|
|
105
|
-
|
|
106
|
-
renderCell(1234.5, { name: 'progress' }, 'en');
|
|
107
|
-
expect(cellText()).toContain('1,235%');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('an explicit tenant locale outranks the active UI language', () => {
|
|
111
|
-
renderCell(1234.5, { name: 'win_rate' }, 'en', 'de');
|
|
112
|
-
expect(cellText()).toContain(`1.235${NBSP}%`);
|
|
113
|
-
expect(cellText()).not.toContain('1,235%');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('PercentCellRenderer keeps its scaling contract (objectui#4553 must-not-change)', () => {
|
|
118
|
-
/**
|
|
119
|
-
* PIN, green both sides — and the reason the whole-percent branch was kept
|
|
120
|
-
* rather than collapsed into `formatPercent`. The same stored number means
|
|
121
|
-
* different things in the two columns, and that must not have changed.
|
|
122
|
-
*/
|
|
123
|
-
it('an ordinary percent scales a fraction; a progress field does not', () => {
|
|
124
|
-
const { unmount } = renderCell(0.5, { name: 'win_rate' }, 'en');
|
|
125
|
-
// Fraction-stored: 0.5 → 50%.
|
|
126
|
-
expect(cellText()).toContain('50%');
|
|
127
|
-
unmount();
|
|
128
|
-
|
|
129
|
-
renderCell(0.5, { name: 'progress' }, 'en');
|
|
130
|
-
// Whole-percent field: 0.5 really is half a percent, rounded to 1% at
|
|
131
|
-
// precision 0 — NOT 50%.
|
|
132
|
-
expect(cellText()).toContain('1%');
|
|
133
|
-
expect(cellText()).not.toContain('50%');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
/** PIN: small-value English output is byte-identical across the change. */
|
|
137
|
-
it('en small-value output is unchanged (must-not-change)', () => {
|
|
138
|
-
renderCell(33.33, { name: 'win_rate', precision: 2 }, 'en');
|
|
139
|
-
expect(cellText()).toContain('33.33%');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
/** PIN: the decorative bar still reports the un-formatted magnitude. */
|
|
143
|
-
it('the progressbar still carries the numeric value', () => {
|
|
144
|
-
renderCell(33.33, { name: 'win_rate' }, 'en');
|
|
145
|
-
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '33.33');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
/** PIN: the null path is untouched — and the hook runs before it. */
|
|
149
|
-
it('renders the empty value for null without crashing on the hook', () => {
|
|
150
|
-
expect(() => renderCell(null, { name: 'win_rate' }, 'de')).not.toThrow();
|
|
151
|
-
});
|
|
152
|
-
});
|
|
@@ -1,115 +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
|
-
* Regression for objectstack-ai/objectstack#5066: the percent / progress cell
|
|
11
|
-
* renderer emitted a FIXED-WIDTH, NON-SHRINKABLE bar (`w-16 shrink-0`) next to a
|
|
12
|
-
* shrinkable value span. Inside a narrow clipping container — a
|
|
13
|
-
* `record:highlights` chip is `basis-[9rem]` / `min-w-[7rem]` and clips with
|
|
14
|
-
* `truncate` — the 64px bar won the space and the VALUE was cut mid-digit: a
|
|
15
|
-
* stored `33.33` carried `33%` in the DOM but read as `3` on screen (measured:
|
|
16
|
-
* 79px clipping box, 32px text node, 25px overflow), with no ellipsis and no
|
|
17
|
-
* signal in the accessible name. A silently wrong number, not a cosmetic glitch.
|
|
18
|
-
*
|
|
19
|
-
* Pin: the number is the CONTENT (`shrink-0`, never sacrificed) and the bar is
|
|
20
|
-
* DECORATION (`w-16` as its preferred width, free to shrink away under
|
|
21
|
-
* pressure — and never `flex-1`, so a wide grid cell keeps today's 64px bar).
|
|
22
|
-
*
|
|
23
|
-
* jsdom has no layout engine, so these are class-semantics assertions (same
|
|
24
|
-
* approach as `cell-truncation.test.tsx` for objectui#3466) plus DOM-text
|
|
25
|
-
* assertions — deliberately not brittle pixel measurements.
|
|
26
|
-
*/
|
|
27
|
-
import { describe, it, expect } from 'vitest';
|
|
28
|
-
import { render, screen } from '@testing-library/react';
|
|
29
|
-
import '@testing-library/jest-dom';
|
|
30
|
-
import React from 'react';
|
|
31
|
-
|
|
32
|
-
import { PercentCellRenderer } from '../index';
|
|
33
|
-
|
|
34
|
-
const renderPercent = (value: unknown, field: Record<string, unknown> = {}) =>
|
|
35
|
-
render(
|
|
36
|
-
<PercentCellRenderer value={value as any} field={{ type: 'percent', ...field } as any} />,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
/** The decorative bar: `role="progressbar"` wrapper the renderer emits. */
|
|
40
|
-
const bar = () => screen.getByRole('progressbar');
|
|
41
|
-
/** The row that holds bar + value. */
|
|
42
|
-
const row = () => bar().parentElement!;
|
|
43
|
-
|
|
44
|
-
describe('PercentCellRenderer — value beats the decorative bar (issue #5066)', () => {
|
|
45
|
-
it('renders BOTH the bar and the value at normal width', () => {
|
|
46
|
-
renderPercent(33.33);
|
|
47
|
-
|
|
48
|
-
expect(bar()).toBeInTheDocument();
|
|
49
|
-
expect(bar()).toHaveAttribute('aria-valuenow', '33.33');
|
|
50
|
-
expect(screen.getByText('33%')).toBeInTheDocument();
|
|
51
|
-
// Bar and value are siblings in one flex row (nothing was dropped).
|
|
52
|
-
expect(row()).toHaveClass('flex');
|
|
53
|
-
expect(row()).toContainElement(screen.getByText('33%'));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('gives the value span shrink priority so a narrow chip cannot clip it', () => {
|
|
57
|
-
renderPercent(33.33);
|
|
58
|
-
|
|
59
|
-
const value = screen.getByText('33%');
|
|
60
|
-
// The number never shrinks — it is the content of the cell.
|
|
61
|
-
expect(value).toHaveClass('shrink-0');
|
|
62
|
-
expect(value).toHaveClass('whitespace-nowrap', 'tabular-nums');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('lets the bar shrink away instead of pushing the value out of the clip box', () => {
|
|
66
|
-
renderPercent(33.33);
|
|
67
|
-
|
|
68
|
-
// The bar keeps `w-16` as its PREFERRED width but is shrinkable, and
|
|
69
|
-
// `min-w-0` stops the automatic minimum size from pinning it at 64px.
|
|
70
|
-
expect(bar()).toHaveClass('w-16', 'min-w-0', 'shrink');
|
|
71
|
-
// The pre-fix spelling: a non-shrinkable bar is exactly the bug.
|
|
72
|
-
expect(bar()).not.toHaveClass('shrink-0');
|
|
73
|
-
// The row itself must be able to shrink below its content width inside the
|
|
74
|
-
// chip's flex column, or the deficit never reaches the bar.
|
|
75
|
-
expect(row()).toHaveClass('min-w-0');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('does not let the bar GROW in a wide cell (no flex-1 / grow)', () => {
|
|
79
|
-
renderPercent(33.33);
|
|
80
|
-
|
|
81
|
-
// `flex-1` would also make the bar stretch to fill every wide grid cell —
|
|
82
|
-
// `w-16` stays the upper bound, only the shrink direction changed.
|
|
83
|
-
expect(bar().className).not.toMatch(/(^|\s)(flex-1|grow|basis-)/);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('keeps the full formatted value in the DOM (fraction, whole, precision)', () => {
|
|
87
|
-
const first = renderPercent(33.33);
|
|
88
|
-
expect(screen.getByText('33%')).toBeInTheDocument();
|
|
89
|
-
first.unmount();
|
|
90
|
-
|
|
91
|
-
// Declared precision keeps the decimals — the widest text, worst overflow.
|
|
92
|
-
renderPercent(33.33, { precision: 2 });
|
|
93
|
-
const wide = screen.getByText('33.33%');
|
|
94
|
-
expect(wide).toBeInTheDocument();
|
|
95
|
-
expect(wide).toHaveClass('shrink-0');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('scales a fraction-stored percent and still renders both parts', () => {
|
|
99
|
-
renderPercent(0.8);
|
|
100
|
-
|
|
101
|
-
expect(screen.getByText('80%')).toBeInTheDocument();
|
|
102
|
-
expect(bar()).toHaveAttribute('aria-valuenow', '80');
|
|
103
|
-
expect(screen.getByText('80%')).toHaveClass('shrink-0');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('applies the same contract to whole-percent `progress` fields', () => {
|
|
107
|
-
renderPercent(33.33, { type: 'progress', name: 'progress' });
|
|
108
|
-
|
|
109
|
-
// 0-100 storage: no fraction scaling, value rendered as-is.
|
|
110
|
-
expect(screen.getByText('33%')).toBeInTheDocument();
|
|
111
|
-
expect(screen.getByText('33%')).toHaveClass('shrink-0');
|
|
112
|
-
expect(bar()).toHaveClass('min-w-0', 'shrink');
|
|
113
|
-
expect(bar()).not.toHaveClass('shrink-0');
|
|
114
|
-
});
|
|
115
|
-
});
|