@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,248 +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
|
-
* The REGISTERED path's fullscreen long-text dialog speaks the session locale —
|
|
11
|
-
* objectui#3404.
|
|
12
|
-
*
|
|
13
|
-
* This is the second half of objectui#3272. That issue translated the built-in
|
|
14
|
-
* branch (`FullscreenTextarea` in `components/src/renderers/form/form.tsx`,
|
|
15
|
-
* pinned next door in `form-fullscreen-textarea-i18n.test.tsx`); the shared
|
|
16
|
-
* `FullscreenFieldEditor` that `TextAreaField` and `RichTextField` render was
|
|
17
|
-
* left with four English literals — the toggle's accessible name
|
|
18
|
-
* (`Edit ${label} fullscreen`), the title fallback `Edit text`, `Cancel` and
|
|
19
|
-
* `Done`.
|
|
20
|
-
*
|
|
21
|
-
* Nothing about the keys was missing: `form.fullscreen.*` and `common.cancel`
|
|
22
|
-
* have shipped in all ten locale packs since #3272. This path simply never
|
|
23
|
-
* consumed them, so the defect was visible INSIDE A SINGLE FORM: with
|
|
24
|
-
* `ObjectFormSchema.mobile.fullscreenLongText` on, `ObjectForm` stamps
|
|
25
|
-
* `mobile_fullscreen` onto every long-text field, and a zh session got
|
|
26
|
-
* 「取消 / 完成」 from a built-in-rendered field and `Cancel / Done` from a
|
|
27
|
-
* registered-widget one.
|
|
28
|
-
*
|
|
29
|
-
* ## What the cases assert, and why in this shape
|
|
30
|
-
*
|
|
31
|
-
* - **Both widgets.** `TextAreaField` (`field:textarea`) and `RichTextField`
|
|
32
|
-
* (`field:markdown` / `field:html`) are the two hosts, and the copy lives in
|
|
33
|
-
* the component they share. Covering one would leave a regression that broke
|
|
34
|
-
* only the other's props path (`label`, `testIdPrefix`) unreported.
|
|
35
|
-
* - **`en` is a NO-OP pin, not a defect reproducer.** The four literals were
|
|
36
|
-
* byte-identical to the `en` pack, so the English cases below were green
|
|
37
|
-
* before this change too. They are here to prove the swap did not alter the
|
|
38
|
-
* English rendering — the direction that only a positive assertion can see.
|
|
39
|
-
* - **Non-`en` positive AND English-literal negative, together.** A re-inlined
|
|
40
|
-
* literal rendered next to a translated sibling still satisfies a
|
|
41
|
-
* positive-only assertion. `ja` joins `zh` because the toggle's name is
|
|
42
|
-
* INTERPOLATED (`{{label}}` + a translated generic noun) and the two packs
|
|
43
|
-
* put the noun on opposite sides — a build that dropped the interpolation
|
|
44
|
-
* passes a zh-only check by luck.
|
|
45
|
-
* - **Un-labelled fixtures for the generic copy.** `form.fullscreen.title` and
|
|
46
|
-
* `form.fullscreen.textFallback` are reachable only when the field has no
|
|
47
|
-
* `label` (a labelled field titles the dialog with its own label, in the
|
|
48
|
-
* author's language, on both paths). The labelled stack is pinned separately
|
|
49
|
-
* at the bottom, where what matters is that `{{label}}` is interpolated into
|
|
50
|
-
* translated surroundings rather than concatenated in English.
|
|
51
|
-
*/
|
|
52
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
53
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
54
|
-
import '@testing-library/jest-dom';
|
|
55
|
-
import { I18nProvider } from '@object-ui/i18n';
|
|
56
|
-
|
|
57
|
-
import { TextAreaField } from '../TextAreaField';
|
|
58
|
-
import { RichTextField } from '../RichTextField';
|
|
59
|
-
import type { FieldMetadata } from '@object-ui/types';
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* No `label` — the one authoring stack the generic copy is written for. The
|
|
63
|
-
* flag is spelled `mobile_fullscreen`, the single carrier `ObjectForm`
|
|
64
|
-
* produces (#3233/#3301).
|
|
65
|
-
*/
|
|
66
|
-
const textareaField = (extra: Record<string, unknown> = {}) =>
|
|
67
|
-
({ name: 'notes', type: 'textarea', mobile_fullscreen: true, ...extra }) as unknown as FieldMetadata;
|
|
68
|
-
|
|
69
|
-
const markdownField = (extra: Record<string, unknown> = {}) =>
|
|
70
|
-
({ name: 'notes', type: 'markdown', mobile_fullscreen: true, ...extra }) as unknown as FieldMetadata;
|
|
71
|
-
|
|
72
|
-
function renderIn(language: string, element: React.ReactElement) {
|
|
73
|
-
return render(
|
|
74
|
-
<I18nProvider config={{ defaultLanguage: language, detectBrowserLanguage: false }}>
|
|
75
|
-
{element}
|
|
76
|
-
</I18nProvider>,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Open the dialog and hand back the trigger, so its name can be asserted too. */
|
|
81
|
-
function openFullscreen(prefix: 'textarea' | 'richtext') {
|
|
82
|
-
const toggle = screen.getByTestId(`${prefix}-fullscreen-toggle`);
|
|
83
|
-
fireEvent.click(toggle);
|
|
84
|
-
return toggle;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
describe('TextAreaField fullscreen dialog is translated (objectui#3404)', () => {
|
|
88
|
-
it('renders the English copy under an en provider', () => {
|
|
89
|
-
renderIn('en', <TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
90
|
-
const toggle = openFullscreen('textarea');
|
|
91
|
-
|
|
92
|
-
// Byte-identical to the literals this replaced, so `en` is a no-op change.
|
|
93
|
-
expect(toggle).toHaveAttribute('aria-label', 'Edit text fullscreen');
|
|
94
|
-
expect(screen.getByText('Edit text')).toBeInTheDocument();
|
|
95
|
-
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
96
|
-
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('renders the whole dialog in Chinese under a zh provider', () => {
|
|
100
|
-
renderIn('zh', <TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
101
|
-
const toggle = openFullscreen('textarea');
|
|
102
|
-
|
|
103
|
-
expect(toggle).toHaveAttribute('aria-label', '全屏编辑文本');
|
|
104
|
-
expect(screen.getByText('编辑文本')).toBeInTheDocument();
|
|
105
|
-
expect(screen.getByRole('button', { name: '取消' })).toBeInTheDocument();
|
|
106
|
-
expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument();
|
|
107
|
-
|
|
108
|
-
// The four literals, asserted absent. `Edit text` is matched loosely
|
|
109
|
-
// because it was BOTH the dialog title and part of the trigger's name.
|
|
110
|
-
expect(screen.queryByText(/edit text/i)).not.toBeInTheDocument();
|
|
111
|
-
expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
|
|
112
|
-
expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument();
|
|
113
|
-
expect(toggle.getAttribute('aria-label')).not.toMatch(/fullscreen/i);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('renders the whole dialog in Japanese under a ja provider', () => {
|
|
117
|
-
renderIn('ja', <TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
118
|
-
const toggle = openFullscreen('textarea');
|
|
119
|
-
|
|
120
|
-
expect(toggle).toHaveAttribute('aria-label', 'テキストを全画面で編集');
|
|
121
|
-
expect(screen.getByText('テキストを編集')).toBeInTheDocument();
|
|
122
|
-
expect(screen.getByRole('button', { name: 'キャンセル' })).toBeInTheDocument();
|
|
123
|
-
expect(screen.getByRole('button', { name: '完了' })).toBeInTheDocument();
|
|
124
|
-
|
|
125
|
-
expect(screen.queryByText(/edit text/i)).not.toBeInTheDocument();
|
|
126
|
-
expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('RichTextField fullscreen dialog is translated (objectui#3404)', () => {
|
|
131
|
-
// The second host of the SAME component. The copy is shared, the props path
|
|
132
|
-
// into it (`label`, `testIdPrefix`) is not.
|
|
133
|
-
it('renders the English copy under an en provider', () => {
|
|
134
|
-
renderIn('en', <RichTextField value="# hello" onChange={vi.fn()} field={markdownField()} />);
|
|
135
|
-
const toggle = openFullscreen('richtext');
|
|
136
|
-
|
|
137
|
-
expect(toggle).toHaveAttribute('aria-label', 'Edit text fullscreen');
|
|
138
|
-
expect(screen.getByText('Edit text')).toBeInTheDocument();
|
|
139
|
-
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
140
|
-
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('renders the whole dialog in Chinese under a zh provider', () => {
|
|
144
|
-
renderIn('zh', <RichTextField value="# hello" onChange={vi.fn()} field={markdownField()} />);
|
|
145
|
-
const toggle = openFullscreen('richtext');
|
|
146
|
-
|
|
147
|
-
expect(toggle).toHaveAttribute('aria-label', '全屏编辑文本');
|
|
148
|
-
expect(screen.getByText('编辑文本')).toBeInTheDocument();
|
|
149
|
-
expect(screen.getByRole('button', { name: '取消' })).toBeInTheDocument();
|
|
150
|
-
expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument();
|
|
151
|
-
|
|
152
|
-
expect(screen.queryByText(/edit text/i)).not.toBeInTheDocument();
|
|
153
|
-
expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
|
|
154
|
-
expect(screen.queryByRole('button', { name: 'Done' })).not.toBeInTheDocument();
|
|
155
|
-
expect(toggle.getAttribute('aria-label')).not.toMatch(/fullscreen/i);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe('fullscreen dialog copy — the field label is interpolated, not concatenated (objectui#3404)', () => {
|
|
160
|
-
it("titles the dialog with the author's label and interpolates it into the trigger name", () => {
|
|
161
|
-
// A labelled field keeps its own label as the title on both paths — what
|
|
162
|
-
// must be translated around it is the trigger's sentence. `Edit Notes
|
|
163
|
-
// fullscreen` was the literal; zh wraps the label instead of prefixing it.
|
|
164
|
-
renderIn(
|
|
165
|
-
'zh',
|
|
166
|
-
<TextAreaField value="hello" onChange={vi.fn()} field={textareaField({ label: 'Notes' })} />,
|
|
167
|
-
);
|
|
168
|
-
const toggle = openFullscreen('textarea');
|
|
169
|
-
|
|
170
|
-
expect(toggle).toHaveAttribute('aria-label', '全屏编辑Notes');
|
|
171
|
-
expect(screen.getByText('Notes')).toBeInTheDocument();
|
|
172
|
-
expect(toggle.getAttribute('aria-label')).not.toMatch(/^Edit /);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('still commits the draft — translating the footer did not unwire it', () => {
|
|
176
|
-
// "Done" lost its literal child. This pins that the click handler still
|
|
177
|
-
// rides on the translated button and not on some node that merely used to
|
|
178
|
-
// carry the old text.
|
|
179
|
-
const onChange = vi.fn();
|
|
180
|
-
renderIn('zh', <TextAreaField value="hello" onChange={onChange} field={textareaField()} />);
|
|
181
|
-
openFullscreen('textarea');
|
|
182
|
-
|
|
183
|
-
fireEvent.change(screen.getByTestId('textarea-fullscreen-input'), {
|
|
184
|
-
target: { value: 'hello from fullscreen' },
|
|
185
|
-
});
|
|
186
|
-
fireEvent.click(screen.getByRole('button', { name: '完成' }));
|
|
187
|
-
|
|
188
|
-
expect(onChange).toHaveBeenCalledWith('hello from fullscreen');
|
|
189
|
-
expect(screen.queryByTestId('textarea-fullscreen-dialog')).not.toBeInTheDocument();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('still discards the draft on the translated Cancel', () => {
|
|
193
|
-
const onChange = vi.fn();
|
|
194
|
-
renderIn('zh', <TextAreaField value="hello" onChange={onChange} field={textareaField()} />);
|
|
195
|
-
openFullscreen('textarea');
|
|
196
|
-
|
|
197
|
-
fireEvent.change(screen.getByTestId('textarea-fullscreen-input'), {
|
|
198
|
-
target: { value: 'discarded' },
|
|
199
|
-
});
|
|
200
|
-
fireEvent.click(screen.getByRole('button', { name: '取消' }));
|
|
201
|
-
|
|
202
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
203
|
-
expect(screen.queryByTestId('textarea-fullscreen-dialog')).not.toBeInTheDocument();
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('fullscreen dialog has an accessible description (objectui#3404)', () => {
|
|
208
|
-
/**
|
|
209
|
-
* The issue listed this as an unverified aside. What was measured: Radix
|
|
210
|
-
* only sets `aria-describedby` when a `Dialog.Description` is present
|
|
211
|
-
* (`descriptionPresent ? descriptionId : undefined`), so before this change
|
|
212
|
-
* the registered path's dialog had NO accessible description while the
|
|
213
|
-
* byte-identical built-in one did, and `form.fullscreen.description` was a
|
|
214
|
-
* key with no consumer on this path.
|
|
215
|
-
*
|
|
216
|
-
* Explicitly NOT justified by a console warning: `@radix-ui/react-dialog`
|
|
217
|
-
* 1.1.23 (the pinned version) contains no `console.*` call at all — the
|
|
218
|
-
* missing-Description warning older versions emitted is gone upstream, and
|
|
219
|
-
* no warning fired in the baseline run.
|
|
220
|
-
*/
|
|
221
|
-
it('wires aria-describedby to the translated sr-only description', () => {
|
|
222
|
-
renderIn('zh', <TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
223
|
-
openFullscreen('textarea');
|
|
224
|
-
|
|
225
|
-
const dialog = screen.getByTestId('textarea-fullscreen-dialog');
|
|
226
|
-
const describedBy = dialog.getAttribute('aria-describedby');
|
|
227
|
-
expect(describedBy).toBeTruthy();
|
|
228
|
-
|
|
229
|
-
const description = document.getElementById(describedBy!);
|
|
230
|
-
expect(description).not.toBeNull();
|
|
231
|
-
expect(description).toHaveTextContent('编辑完整的文本内容,然后保存或取消更改。');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('renders the English description under an en provider', () => {
|
|
235
|
-
renderIn('en', <TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
236
|
-
openFullscreen('textarea');
|
|
237
|
-
|
|
238
|
-
expect(
|
|
239
|
-
screen.getByText('Edit the full text value, then save or cancel your changes.'),
|
|
240
|
-
).toBeInTheDocument();
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// The provider-LESS leg lives in `FullscreenFieldEditor.no-provider.test.tsx`.
|
|
245
|
-
// It cannot share this file: mounting `I18nProvider` installs its instance as
|
|
246
|
-
// react-i18next's GLOBAL default, so after any case above there is no
|
|
247
|
-
// "no provider" state left in this module graph to observe. See that file's
|
|
248
|
-
// header — it was measured, not assumed.
|
|
@@ -1,116 +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
|
-
* The shared fullscreen long-text dialog with NO `I18nProvider` mounted
|
|
11
|
-
* (objectui#3404) — standalone/embedded hosts, and most of this package's own
|
|
12
|
-
* bare-render tests.
|
|
13
|
-
*
|
|
14
|
-
* ## Why this leg decided the implementation
|
|
15
|
-
*
|
|
16
|
-
* Routing the copy through i18n must not turn it into raw keys where nothing
|
|
17
|
-
* is configured. MEASURED on the pre-change tree with a probe rendering bare
|
|
18
|
-
* `useObjectTranslation()` and no provider:
|
|
19
|
-
*
|
|
20
|
-
* {"cancel":"common.cancel","done":"form.fullscreen.done",
|
|
21
|
-
* "title":"form.fullscreen.title","toggle":"form.fullscreen.toggle"}
|
|
22
|
-
*
|
|
23
|
-
* Raw keys on every one, and the `{{label}}` interpolation gone entirely —
|
|
24
|
-
* strictly WORSE than the English literals being replaced. So
|
|
25
|
-
* `FullscreenFieldEditor` consumes `useFieldTranslation()`
|
|
26
|
-
* (`createSafeTranslation` + English defaults, the same shape the built-in
|
|
27
|
-
* branch has used since objectui#3272) rather than the bare hook, and the
|
|
28
|
-
* defaults are byte-identical to the old literals so this rendering is
|
|
29
|
-
* unchanged.
|
|
30
|
-
*
|
|
31
|
-
* ## Why a separate FILE and not another `describe`
|
|
32
|
-
*
|
|
33
|
-
* Mounting `I18nProvider` calls `initReactI18next`, which installs that
|
|
34
|
-
* instance as react-i18next's GLOBAL default. Once any sibling case has done
|
|
35
|
-
* so there is no "no provider" state left in the module graph, and these
|
|
36
|
-
* assertions would pass by reading that leaked instance instead of the
|
|
37
|
-
* fallback — green for the wrong reason. That is not a hypothetical: these
|
|
38
|
-
* cases were FIRST written inside `FullscreenFieldEditor.i18n.test.tsx`, and
|
|
39
|
-
* the reverse check (swap `useFieldTranslation` back to bare
|
|
40
|
-
* `useObjectTranslation`, expect red) left them GREEN there while it correctly
|
|
41
|
-
* turned `RichTextField.mobileFullscreen.test.tsx` red. Split out, the same
|
|
42
|
-
* swap turns them red — which is what makes them a pin.
|
|
43
|
-
*
|
|
44
|
-
* Same split, same reason as `TagsField.placeholder.no-provider.test.tsx` and
|
|
45
|
-
* `OptionsEmptyState.no-provider.test.tsx`; vitest's per-file isolation is
|
|
46
|
-
* what makes the observation honest.
|
|
47
|
-
*/
|
|
48
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
49
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
50
|
-
import '@testing-library/jest-dom';
|
|
51
|
-
|
|
52
|
-
import { TextAreaField } from '../TextAreaField';
|
|
53
|
-
import type { FieldMetadata } from '@object-ui/types';
|
|
54
|
-
|
|
55
|
-
const textareaField = (extra: Record<string, unknown> = {}) =>
|
|
56
|
-
({ name: 'notes', type: 'textarea', mobile_fullscreen: true, ...extra }) as unknown as FieldMetadata;
|
|
57
|
-
|
|
58
|
-
function openFullscreen() {
|
|
59
|
-
const toggle = screen.getByTestId('textarea-fullscreen-toggle');
|
|
60
|
-
fireEvent.click(toggle);
|
|
61
|
-
return toggle;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe('fullscreen dialog copy — English fallback survives with no i18n configured (objectui#3404)', () => {
|
|
65
|
-
it('renders the English defaults, not raw keys', () => {
|
|
66
|
-
render(<TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
67
|
-
const toggle = openFullscreen();
|
|
68
|
-
|
|
69
|
-
// Byte-identical to the literals this change replaced.
|
|
70
|
-
expect(toggle).toHaveAttribute('aria-label', 'Edit text fullscreen');
|
|
71
|
-
expect(screen.getByText('Edit text')).toBeInTheDocument();
|
|
72
|
-
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
73
|
-
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
|
|
74
|
-
|
|
75
|
-
// The measured failure mode, stated directly — a raw key reaching a user.
|
|
76
|
-
expect(document.body.textContent).not.toMatch(/form\.fullscreen\./);
|
|
77
|
-
expect(document.body.textContent).not.toMatch(/common\.cancel/);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('still interpolates the field label into the trigger name', () => {
|
|
81
|
-
// `createSafeTranslation`'s fallback does its own `{{k}}` replacement.
|
|
82
|
-
// Bare `useObjectTranslation` returns `form.fullscreen.toggle` verbatim
|
|
83
|
-
// here, label dropped.
|
|
84
|
-
render(
|
|
85
|
-
<TextAreaField value="hello" onChange={vi.fn()} field={textareaField({ label: 'Notes' })} />,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
expect(openFullscreen()).toHaveAttribute('aria-label', 'Edit Notes fullscreen');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('the code-shipped defaults carry no CJK (Commandment #-1)', () => {
|
|
92
|
-
// With no provider mounted, whatever renders here came from CODE
|
|
93
|
-
// (FIELD_DEFAULTS). Escaped ranges on purpose: a literal CJK class would
|
|
94
|
-
// itself violate the commandment this asserts (same shape as
|
|
95
|
-
// `TagsField.placeholder.no-provider.test.tsx`).
|
|
96
|
-
render(<TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
97
|
-
openFullscreen();
|
|
98
|
-
|
|
99
|
-
expect(screen.getByTestId('textarea-fullscreen-dialog').textContent).not.toMatch(
|
|
100
|
-
/[\u3000-\u30ff\u4e00-\u9fff]/,
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('still gives the dialog an accessible description', () => {
|
|
105
|
-
render(<TextAreaField value="hello" onChange={vi.fn()} field={textareaField()} />);
|
|
106
|
-
openFullscreen();
|
|
107
|
-
|
|
108
|
-
const describedBy = screen
|
|
109
|
-
.getByTestId('textarea-fullscreen-dialog')
|
|
110
|
-
.getAttribute('aria-describedby');
|
|
111
|
-
expect(describedBy).toBeTruthy();
|
|
112
|
-
expect(document.getElementById(describedBy!)).toHaveTextContent(
|
|
113
|
-
'Edit the full text value, then save or cancel your changes.',
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,244 +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
|
-
* A DISABLED `RichTextField` cannot be edited through its fullscreen dialog —
|
|
11
|
-
* objectui#3402.
|
|
12
|
-
*
|
|
13
|
-
* The rich-text half of the same defect the sibling
|
|
14
|
-
* `TextAreaField.fullscreenDisabled.test.tsx` pins, and it is a genuinely
|
|
15
|
-
* separate code path rather than a copy of the assertions: this widget reaches
|
|
16
|
-
* the affordance through `RichTextEditorSurface`'s `overlay` slot, and the
|
|
17
|
-
* dialog renders the SAME surface again. `disabled` therefore has to be
|
|
18
|
-
* resolved once and given to both renderings, exactly like `formatLabel` /
|
|
19
|
-
* `hint` / `placeholder` already were — otherwise the two surfaces disagree
|
|
20
|
-
* about whether the field is editable, which is the drift #3301 built the
|
|
21
|
-
* shared surface to prevent.
|
|
22
|
-
*
|
|
23
|
-
* Measured on `origin/main` before the fix, by a dynamic probe (the issue
|
|
24
|
-
* carried static evidence only):
|
|
25
|
-
*
|
|
26
|
-
* PROBE richtext: inline disabled = true , toggle disabled = false
|
|
27
|
-
* PROBE richtext: dialog opened = true , dialog input disabled = false
|
|
28
|
-
* PROBE richtext: onChange calls = [["# EDITED WHILE DISABLED"]]
|
|
29
|
-
*
|
|
30
|
-
* `readonly` was never part of this: the widget early-returns a read-only
|
|
31
|
-
* display before the affordance is computed. That path is untouched by the fix
|
|
32
|
-
* and pinned below as a non-regression.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
36
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
37
|
-
import '@testing-library/jest-dom';
|
|
38
|
-
|
|
39
|
-
import { RichTextField } from '../RichTextField';
|
|
40
|
-
import type { FieldMetadata } from '@object-ui/types';
|
|
41
|
-
|
|
42
|
-
const fieldMeta = (extra: Record<string, unknown> = {}) =>
|
|
43
|
-
({
|
|
44
|
-
name: 'notes',
|
|
45
|
-
label: 'Notes',
|
|
46
|
-
type: 'markdown',
|
|
47
|
-
mobile_fullscreen: true,
|
|
48
|
-
...extra,
|
|
49
|
-
}) as unknown as FieldMetadata;
|
|
50
|
-
|
|
51
|
-
const toggle = () =>
|
|
52
|
-
screen.queryByTestId('richtext-fullscreen-toggle') as HTMLButtonElement | null;
|
|
53
|
-
const dialogInput = () =>
|
|
54
|
-
screen.queryByTestId('richtext-fullscreen-input') as HTMLTextAreaElement | null;
|
|
55
|
-
const saveButton = () =>
|
|
56
|
-
screen.queryByTestId('richtext-fullscreen-save') as HTMLButtonElement | null;
|
|
57
|
-
|
|
58
|
-
describe('RichTextField fullscreen — disabled keeps the affordance but makes it inert (objectui#3402)', () => {
|
|
59
|
-
it('keeps the expand button and disables it', () => {
|
|
60
|
-
render(
|
|
61
|
-
<RichTextField value="# hello" onChange={() => {}} field={fieldMeta()} disabled />,
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
expect(toggle()).not.toBeNull();
|
|
65
|
-
expect(toggle()!.disabled).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('does not open the dialog when that button is clicked anyway', () => {
|
|
69
|
-
render(
|
|
70
|
-
<RichTextField value="# hello" onChange={() => {}} field={fieldMeta()} disabled />,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
fireEvent.click(toggle()!);
|
|
74
|
-
|
|
75
|
-
expect(screen.queryByTestId('richtext-fullscreen-dialog')).not.toBeInTheDocument();
|
|
76
|
-
expect(dialogInput()).toBeNull();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('still disables the inline editing surface', () => {
|
|
80
|
-
render(
|
|
81
|
-
<RichTextField value="# hello" onChange={() => {}} field={fieldMeta()} disabled />,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const inline = screen.getAllByRole('textbox')[0] as HTMLTextAreaElement;
|
|
85
|
-
expect(inline.disabled).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('never commits a value while disabled', () => {
|
|
89
|
-
const onChange = vi.fn();
|
|
90
|
-
render(
|
|
91
|
-
<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} disabled />,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
fireEvent.click(toggle()!);
|
|
95
|
-
|
|
96
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('RichTextField fullscreen — the dialog holds on its own when disabled arrives mid-edit (objectui#3402)', () => {
|
|
101
|
-
/**
|
|
102
|
-
* The `isSubmitting` shape at widget level: the form renderer has already
|
|
103
|
-
* OR-ed `isSubmitting` into the `disabled` this widget receives, so a submit
|
|
104
|
-
* starting under an open dialog looks exactly like this rerender.
|
|
105
|
-
*/
|
|
106
|
-
function openThenDisable(onChange: () => void) {
|
|
107
|
-
const { rerender } = render(
|
|
108
|
-
<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} />,
|
|
109
|
-
);
|
|
110
|
-
fireEvent.click(toggle()!);
|
|
111
|
-
expect(dialogInput()).not.toBeNull();
|
|
112
|
-
|
|
113
|
-
rerender(
|
|
114
|
-
<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} disabled />,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
it('locks the dialog controls', () => {
|
|
119
|
-
openThenDisable(vi.fn());
|
|
120
|
-
|
|
121
|
-
expect(dialogInput()).not.toBeNull();
|
|
122
|
-
expect(dialogInput()!.disabled).toBe(true);
|
|
123
|
-
expect(saveButton()!.disabled).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
/** Split from the attribute assertions for the reason its textarea sibling
|
|
127
|
-
* spells out: as a tail on them it could only run when the native attribute
|
|
128
|
-
* was already doing the work. */
|
|
129
|
-
it('refuses the write-back even when "Done" is driven directly', () => {
|
|
130
|
-
const onChange = vi.fn();
|
|
131
|
-
openThenDisable(onChange);
|
|
132
|
-
|
|
133
|
-
fireEvent.change(dialogInput()!, { target: { value: '# EDITED WHILE SUBMITTING' } });
|
|
134
|
-
fireEvent.click(saveButton()!);
|
|
135
|
-
|
|
136
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('still lets the user out of the dialog', () => {
|
|
140
|
-
const onChange = vi.fn();
|
|
141
|
-
const { rerender } = render(
|
|
142
|
-
<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} />,
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
fireEvent.click(toggle()!);
|
|
146
|
-
rerender(
|
|
147
|
-
<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} disabled />,
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
|
|
151
|
-
|
|
152
|
-
expect(screen.queryByTestId('richtext-fullscreen-dialog')).not.toBeInTheDocument();
|
|
153
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('RichTextField fullscreen — the states the fix must not have moved (objectui#3402)', () => {
|
|
158
|
-
it('still edits fullscreen and commits when the field is editable', () => {
|
|
159
|
-
const onChange = vi.fn();
|
|
160
|
-
render(<RichTextField value="# hello" onChange={onChange} field={fieldMeta()} />);
|
|
161
|
-
|
|
162
|
-
expect(toggle()!.disabled).toBe(false);
|
|
163
|
-
fireEvent.click(toggle()!);
|
|
164
|
-
|
|
165
|
-
const input = dialogInput()!;
|
|
166
|
-
expect(input.disabled).toBe(false);
|
|
167
|
-
expect(saveButton()!.disabled).toBe(false);
|
|
168
|
-
|
|
169
|
-
fireEvent.change(input, { target: { value: '# hello from fullscreen' } });
|
|
170
|
-
fireEvent.click(saveButton()!);
|
|
171
|
-
|
|
172
|
-
expect(onChange).toHaveBeenCalledWith('# hello from fullscreen');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('keeps the dialog holding the REAL editor, disabled and not', () => {
|
|
176
|
-
// #3301's acceptance criterion, re-pinned under `disabled`: the dialog
|
|
177
|
-
// shows the same surface as the inline one (same format indicator), and now
|
|
178
|
-
// the same interactivity too — the very thing that had drifted.
|
|
179
|
-
const onChange = vi.fn();
|
|
180
|
-
const { rerender } = render(
|
|
181
|
-
<RichTextField
|
|
182
|
-
value=""
|
|
183
|
-
onChange={onChange}
|
|
184
|
-
field={fieldMeta({ format: 'html' })}
|
|
185
|
-
/>,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
fireEvent.click(toggle()!);
|
|
189
|
-
expect(screen.getAllByText('Format: html')).toHaveLength(2);
|
|
190
|
-
|
|
191
|
-
rerender(
|
|
192
|
-
<RichTextField
|
|
193
|
-
value=""
|
|
194
|
-
onChange={onChange}
|
|
195
|
-
field={fieldMeta({ format: 'html' })}
|
|
196
|
-
disabled
|
|
197
|
-
/>,
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// Both surfaces present, both inert.
|
|
201
|
-
expect(screen.getAllByText('Format: html')).toHaveLength(2);
|
|
202
|
-
// Queried off the document rather than by role: the dialog is modal, so
|
|
203
|
-
// Radix marks the background `aria-hidden` and a role query sees only the
|
|
204
|
-
// dialog's own control — which is precisely the textarea NOT at issue here.
|
|
205
|
-
// Both surfaces have to be disabled, so both are read directly.
|
|
206
|
-
const textareas = Array.from(
|
|
207
|
-
document.body.querySelectorAll('textarea'),
|
|
208
|
-
) as HTMLTextAreaElement[];
|
|
209
|
-
expect(textareas).toHaveLength(2);
|
|
210
|
-
expect(textareas.every((el) => el.disabled)).toBe(true);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('still renders no affordance at all in readonly mode', () => {
|
|
214
|
-
// The untouched early return — why readonly was never part of this defect.
|
|
215
|
-
render(
|
|
216
|
-
<RichTextField value="# hello" onChange={() => {}} readonly field={fieldMeta()} />,
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
expect(toggle()).toBeNull();
|
|
220
|
-
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
|
|
221
|
-
expect(screen.getByText('# hello')).toBeInTheDocument();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('still renders no affordance when the metadata does not opt in, disabled or not', () => {
|
|
225
|
-
const { rerender } = render(
|
|
226
|
-
<RichTextField
|
|
227
|
-
value="# hello"
|
|
228
|
-
onChange={() => {}}
|
|
229
|
-
field={fieldMeta({ mobile_fullscreen: false })}
|
|
230
|
-
/>,
|
|
231
|
-
);
|
|
232
|
-
expect(toggle()).toBeNull();
|
|
233
|
-
|
|
234
|
-
rerender(
|
|
235
|
-
<RichTextField
|
|
236
|
-
value="# hello"
|
|
237
|
-
onChange={() => {}}
|
|
238
|
-
field={fieldMeta({ mobile_fullscreen: false })}
|
|
239
|
-
disabled
|
|
240
|
-
/>,
|
|
241
|
-
);
|
|
242
|
-
expect(toggle()).toBeNull();
|
|
243
|
-
});
|
|
244
|
-
});
|