@object-ui/fields 17.3.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.
Files changed (265) hide show
  1. package/CHANGELOG.md +1655 -0
  2. package/README.md +1 -12
  3. package/dist/FieldEditWidget.d.ts +23 -3
  4. package/dist/currency.d.ts +46 -0
  5. package/dist/field-type-alias.d.ts +35 -1
  6. package/dist/index.cjs +4 -4
  7. package/dist/index.css +651 -0
  8. package/dist/index.d.ts +474 -85
  9. package/dist/index.js +5418 -5023
  10. package/dist/widgets/AddressField.d.ts +4 -12
  11. package/dist/widgets/AutoNumberField.d.ts +1 -1
  12. package/dist/widgets/AvatarField.d.ts +1 -1
  13. package/dist/widgets/BooleanField.d.ts +1 -1
  14. package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
  15. package/dist/widgets/CheckboxesField.d.ts +1 -1
  16. package/dist/widgets/CodeField.d.ts +1 -1
  17. package/dist/widgets/ColorField.d.ts +1 -1
  18. package/dist/widgets/CurrencyField.d.ts +1 -1
  19. package/dist/widgets/DateField.d.ts +1 -1
  20. package/dist/widgets/DateTimeField.d.ts +1 -1
  21. package/dist/widgets/EmailField.d.ts +1 -1
  22. package/dist/widgets/FileField.d.ts +18 -2
  23. package/dist/widgets/FilterConditionField.d.ts +28 -1
  24. package/dist/widgets/FormulaField.d.ts +1 -1
  25. package/dist/widgets/FullscreenFieldEditor.d.ts +49 -152
  26. package/dist/widgets/GeolocationField.d.ts +1 -1
  27. package/dist/widgets/GridField.d.ts +68 -6
  28. package/dist/widgets/ImageField.d.ts +1 -1
  29. package/dist/widgets/LocationField.d.ts +1 -1
  30. package/dist/widgets/LookupField.d.ts +1 -1
  31. package/dist/widgets/MasterDetailField.d.ts +1 -1
  32. package/dist/widgets/MultiSelectField.d.ts +1 -1
  33. package/dist/widgets/NumberField.d.ts +1 -1
  34. package/dist/widgets/ObjectField.d.ts +1 -1
  35. package/dist/widgets/ObjectRefField.d.ts +1 -1
  36. package/dist/widgets/OptionsEmptyState.d.ts +21 -1
  37. package/dist/widgets/PasswordField.d.ts +1 -1
  38. package/dist/widgets/PercentField.d.ts +1 -1
  39. package/dist/widgets/PhoneField.d.ts +1 -1
  40. package/dist/widgets/QRCodeField.d.ts +1 -1
  41. package/dist/widgets/RadioField.d.ts +1 -1
  42. package/dist/widgets/RatingField.d.ts +1 -1
  43. package/dist/widgets/RecipientPickerField.d.ts +1 -1
  44. package/dist/widgets/RecordPickerDialog.d.ts +34 -10
  45. package/dist/widgets/RichTextField.d.ts +37 -1
  46. package/dist/widgets/SelectField.d.ts +26 -6
  47. package/dist/widgets/SignatureField.d.ts +32 -2
  48. package/dist/widgets/SliderField.d.ts +37 -2
  49. package/dist/widgets/SummaryField.d.ts +1 -1
  50. package/dist/widgets/TagsField.d.ts +1 -1
  51. package/dist/widgets/TextAreaField.d.ts +6 -2
  52. package/dist/widgets/TextField.d.ts +1 -1
  53. package/dist/widgets/TimeField.d.ts +1 -1
  54. package/dist/widgets/UrlField.d.ts +1 -1
  55. package/dist/widgets/UserField.d.ts +1 -1
  56. package/dist/widgets/VectorField.d.ts +1 -1
  57. package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
  58. package/dist/widgets/address-format.d.ts +88 -0
  59. package/dist/widgets/file-size-guard.d.ts +41 -0
  60. package/dist/widgets/lookupColumnDisplay.d.ts +93 -0
  61. package/dist/widgets/nativeDateValue.d.ts +37 -0
  62. package/dist/widgets/richTextDisplay.d.ts +80 -0
  63. package/dist/widgets/toDomProps.d.ts +30 -10
  64. package/dist/widgets/toHostGroupProps.d.ts +189 -0
  65. package/dist/widgets/types.d.ts +8 -3
  66. package/dist/widgets/useRecordQuery.d.ts +7 -1
  67. package/package.json +17 -14
  68. package/dist/FieldEditWidget.test.d.ts +0 -8
  69. package/dist/__tests__/DateCellRenderer.test.d.ts +0 -0
  70. package/dist/__tests__/NumberCellRenderer.test.d.ts +0 -0
  71. package/dist/__tests__/cell-truncation.test.d.ts +0 -0
  72. package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
  73. package/dist/__tests__/lookupCellDisplayField.test.d.ts +0 -0
  74. package/dist/__tests__/lookupCellDisplayName.test.d.ts +0 -0
  75. package/dist/__tests__/lookupCellReferenceKey.test.d.ts +0 -0
  76. package/dist/__tests__/registry-collision.test.d.ts +0 -0
  77. package/dist/__tests__/resolveFieldCurrency.test.d.ts +0 -1
  78. package/dist/__tests__/spec-symbol-batch7.test.d.ts +0 -8
  79. package/dist/__tests__/validation-feedback.test.d.ts +0 -0
  80. package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
  81. package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
  82. package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
  83. package/dist/__tests__/widget-props-contract.test.d.ts +0 -8
  84. package/dist/coerce-safe-value.test.d.ts +0 -1
  85. package/dist/complex-widgets.test.d.ts +0 -1
  86. package/dist/datetime-widgets.test.d.ts +0 -1
  87. package/dist/field-type-coverage.test.d.ts +0 -1
  88. package/dist/standard-widgets.test.d.ts +0 -0
  89. package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
  90. package/dist/widgets/CapabilityMultiSelectField.test.d.ts +0 -0
  91. package/dist/widgets/CharacterCount.d.ts +0 -56
  92. package/dist/widgets/CheckboxesField.cascade.test.d.ts +0 -0
  93. package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
  94. package/dist/widgets/GridField.test.d.ts +0 -1
  95. package/dist/widgets/ImageLightbox.test.d.ts +0 -1
  96. package/dist/widgets/LookupField.dependsOn.test.d.ts +0 -8
  97. package/dist/widgets/LookupField.gateHintLabel.test.d.ts +0 -8
  98. package/dist/widgets/LookupField.hydration.test.d.ts +0 -1
  99. package/dist/widgets/LookupField.idField.test.d.ts +0 -1
  100. package/dist/widgets/MultiSelectField.cascade.test.d.ts +0 -0
  101. package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
  102. package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
  103. package/dist/widgets/PeoplePicker.test.d.ts +0 -8
  104. package/dist/widgets/PeoplePickerComponents.test.d.ts +0 -8
  105. package/dist/widgets/RadioField.cascade.test.d.ts +0 -0
  106. package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
  107. package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +0 -8
  108. package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +0 -8
  109. package/dist/widgets/SelectField.cascade.test.d.ts +0 -0
  110. package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
  111. package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
  112. package/dist/widgets/UserFieldPicker.test.d.ts +0 -8
  113. package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +0 -8
  114. package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +0 -8
  115. package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
  116. package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
  117. package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
  118. package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
  119. package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
  120. package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
  121. package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
  122. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
  123. package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
  124. package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
  125. package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
  126. package/dist/widgets/deriveLookupColumns.test.d.ts +0 -1
  127. package/dist/widgets/file-value.test.d.ts +0 -1
  128. package/dist/widgets/personDisplay.test.d.ts +0 -8
  129. package/dist/widgets/recentLookups.test.d.ts +0 -1
  130. package/dist/widgets/useRecordQuery.test.d.ts +0 -8
  131. package/dist/widgets/useUploadingSignal.test.d.ts +0 -1
  132. package/src/FieldEditWidget.test.ts +0 -133
  133. package/src/FieldEditWidget.tsx +0 -191
  134. package/src/__tests__/DateCellRenderer.test.tsx +0 -73
  135. package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
  136. package/src/__tests__/cell-truncation.test.tsx +0 -96
  137. package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
  138. package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
  139. package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
  140. package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
  141. package/src/__tests__/registry-collision.test.ts +0 -91
  142. package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
  143. package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
  144. package/src/__tests__/validation-feedback.test.tsx +0 -361
  145. package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
  146. package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
  147. package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -598
  148. package/src/__tests__/widget-props-contract.test.tsx +0 -142
  149. package/src/coerce-safe-value.test.ts +0 -34
  150. package/src/complex-widgets.test.tsx +0 -908
  151. package/src/currency.ts +0 -14
  152. package/src/datetime-widgets.test.tsx +0 -93
  153. package/src/field-type-alias.ts +0 -100
  154. package/src/field-type-coverage.test.ts +0 -67
  155. package/src/index.tsx +0 -2503
  156. package/src/standard-widgets.test.tsx +0 -357
  157. package/src/widgets/AddressField.tsx +0 -132
  158. package/src/widgets/AddressField.uniqueIds.test.tsx +0 -92
  159. package/src/widgets/AutoNumberField.tsx +0 -18
  160. package/src/widgets/AvatarField.tsx +0 -117
  161. package/src/widgets/BooleanField.tsx +0 -52
  162. package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
  163. package/src/widgets/CapabilityMultiSelectField.tsx +0 -229
  164. package/src/widgets/CharacterCount.tsx +0 -263
  165. package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
  166. package/src/widgets/CheckboxesField.tsx +0 -136
  167. package/src/widgets/CodeField.tsx +0 -41
  168. package/src/widgets/ColorField.tsx +0 -53
  169. package/src/widgets/CurrencyField.tsx +0 -95
  170. package/src/widgets/DateField.tsx +0 -31
  171. package/src/widgets/DateTimeField.tsx +0 -37
  172. package/src/widgets/EmailField.tsx +0 -37
  173. package/src/widgets/FileField.tsx +0 -494
  174. package/src/widgets/FilterConditionField.tsx +0 -454
  175. package/src/widgets/FormulaField.tsx +0 -33
  176. package/src/widgets/FullscreenFieldEditor.tsx +0 -271
  177. package/src/widgets/GeolocationField.tsx +0 -167
  178. package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
  179. package/src/widgets/GridField.test.tsx +0 -406
  180. package/src/widgets/GridField.tsx +0 -966
  181. package/src/widgets/ImageCropperDialog.tsx +0 -245
  182. package/src/widgets/ImageField.tsx +0 -255
  183. package/src/widgets/ImageLightbox.test.tsx +0 -52
  184. package/src/widgets/ImageLightbox.tsx +0 -108
  185. package/src/widgets/LocationField.tsx +0 -57
  186. package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
  187. package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
  188. package/src/widgets/LookupField.hydration.test.tsx +0 -136
  189. package/src/widgets/LookupField.idField.test.tsx +0 -61
  190. package/src/widgets/LookupField.tsx +0 -1291
  191. package/src/widgets/MarkdownContent.tsx +0 -19
  192. package/src/widgets/MasterDetailField.tsx +0 -131
  193. package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
  194. package/src/widgets/MultiSelectField.tsx +0 -142
  195. package/src/widgets/NumberField.tsx +0 -49
  196. package/src/widgets/ObjectField.tsx +0 -94
  197. package/src/widgets/ObjectRefField.tsx +0 -121
  198. package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
  199. package/src/widgets/OptionsEmptyState.test.tsx +0 -169
  200. package/src/widgets/OptionsEmptyState.tsx +0 -83
  201. package/src/widgets/PasswordField.tsx +0 -49
  202. package/src/widgets/PeoplePicker.test.tsx +0 -360
  203. package/src/widgets/PeoplePicker.tsx +0 -579
  204. package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
  205. package/src/widgets/PercentField.tsx +0 -95
  206. package/src/widgets/PersonRow.tsx +0 -117
  207. package/src/widgets/PhoneField.tsx +0 -37
  208. package/src/widgets/QRCodeField.tsx +0 -103
  209. package/src/widgets/RadioField.cascade.test.tsx +0 -141
  210. package/src/widgets/RadioField.tsx +0 -111
  211. package/src/widgets/RatingField.tsx +0 -81
  212. package/src/widgets/RecipientPickerField.test.tsx +0 -157
  213. package/src/widgets/RecipientPickerField.tsx +0 -188
  214. package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
  215. package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
  216. package/src/widgets/RecordPickerDialog.tsx +0 -1316
  217. package/src/widgets/RichTextField.tsx +0 -176
  218. package/src/widgets/SelectField.cascade.test.tsx +0 -138
  219. package/src/widgets/SelectField.tsx +0 -162
  220. package/src/widgets/SelectionTray.tsx +0 -110
  221. package/src/widgets/SignatureField.tsx +0 -140
  222. package/src/widgets/SliderField.tsx +0 -39
  223. package/src/widgets/SummaryField.tsx +0 -31
  224. package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
  225. package/src/widgets/TagsField.placeholder.test.tsx +0 -95
  226. package/src/widgets/TagsField.tsx +0 -88
  227. package/src/widgets/TextAreaField.tsx +0 -213
  228. package/src/widgets/TextField.tsx +0 -45
  229. package/src/widgets/TimeField.tsx +0 -31
  230. package/src/widgets/UrlField.tsx +0 -46
  231. package/src/widgets/UserField.tsx +0 -58
  232. package/src/widgets/UserFieldPicker.test.tsx +0 -78
  233. package/src/widgets/VectorField.tsx +0 -29
  234. package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
  235. package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -142
  236. package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
  237. package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
  238. package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
  239. package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
  240. package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
  241. package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
  242. package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
  243. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
  244. package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
  245. package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
  246. package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
  247. package/src/widgets/_cell-renderer-bridge.ts +0 -19
  248. package/src/widgets/deriveLookupColumns.test.ts +0 -112
  249. package/src/widgets/deriveLookupColumns.ts +0 -108
  250. package/src/widgets/file-value.test.ts +0 -230
  251. package/src/widgets/file-value.ts +0 -246
  252. package/src/widgets/openNativePicker.ts +0 -18
  253. package/src/widgets/personDisplay.test.ts +0 -113
  254. package/src/widgets/personDisplay.ts +0 -92
  255. package/src/widgets/recentLookups.test.ts +0 -45
  256. package/src/widgets/recentLookups.ts +0 -47
  257. package/src/widgets/toDomProps.ts +0 -175
  258. package/src/widgets/types.ts +0 -242
  259. package/src/widgets/useCascadingOptions.ts +0 -46
  260. package/src/widgets/useFieldTranslation.ts +0 -150
  261. package/src/widgets/useRecordQuery.test.ts +0 -174
  262. package/src/widgets/useRecordQuery.ts +0 -264
  263. package/src/widgets/useUploadingSignal.test.tsx +0 -62
  264. package/src/widgets/useUploadingSignal.ts +0 -20
  265. package/src/withFieldCarrier.tsx +0 -75
@@ -1,96 +0,0 @@
1
- /**
2
- * Regression for objectstack-ai/objectui#3466: single-line cell renderers
3
- * emitted a BARE INLINE `span.truncate`. An inline box has no width box, so
4
- * `overflow:hidden` / `text-overflow:ellipsis` never engage — the value
5
- * rendered at full content width and its tail was silently clipped by
6
- * whatever ancestor happened to clip (the record-detail card edge), with no
7
- * ellipsis and no way to read the full text (the detail row's `title` is the
8
- * inline-edit hint, not the value). Same mechanism as the JSON cell fix in
9
- * objectui#2578.
10
- *
11
- * Pin: every single-line value renderer emits a BLOCK-level, `max-w-full`
12
- * truncating span that carries the full text in `title` (so hovering the
13
- * value shows the full text, taking precedence over any ancestor `title`).
14
- */
15
- import { describe, it, expect, vi } from 'vitest';
16
- import { render, screen } from '@testing-library/react';
17
- import '@testing-library/jest-dom';
18
- import React from 'react';
19
-
20
- import {
21
- TextCellRenderer,
22
- LookupCellRenderer,
23
- UserCellRenderer,
24
- FileCellRenderer,
25
- SelectCellRenderer,
26
- } from '../index';
27
- import { SchemaRendererProvider } from '@object-ui/react';
28
-
29
- const LONG =
30
- 'A remarkably long referenced-record title that a detail card column can never fit on a single line without an ellipsis';
31
-
32
- /** The block-level truncation contract every single-line value span must meet. */
33
- function expectTruncating(el: HTMLElement, fullText: string) {
34
- expect(el).toHaveClass('block', 'max-w-full', 'truncate');
35
- expect(el).toHaveAttribute('title', fullText);
36
- }
37
-
38
- describe('cell renderers truncate for real and expose the full text (issue #3466)', () => {
39
- it('TextCellRenderer: block-level truncating span with title fallback', () => {
40
- render(<TextCellRenderer value={LONG} field={{ type: 'text' } as any} />);
41
- expectTruncating(screen.getByText(LONG), LONG);
42
- });
43
-
44
- it('LookupCellRenderer: expanded record object', () => {
45
- const ds = { find: vi.fn(), findOne: vi.fn() } as any;
46
- render(
47
- <SchemaRendererProvider dataSource={ds}>
48
- <LookupCellRenderer
49
- value={{ id: 'rec1', name: LONG }}
50
- field={{ type: 'lookup', reference_to: 'obj_a' } as any}
51
- />
52
- </SchemaRendererProvider>,
53
- );
54
- expectTruncating(screen.getByText(LONG), LONG);
55
- });
56
-
57
- it('LookupCellRenderer: primitive non-opaque value', () => {
58
- const ds = { find: vi.fn(), findOne: vi.fn() } as any;
59
- render(
60
- <SchemaRendererProvider dataSource={ds}>
61
- <LookupCellRenderer value={LONG} field={{ type: 'lookup' } as any} />
62
- </SchemaRendererProvider>,
63
- );
64
- expectTruncating(screen.getByText(LONG), LONG);
65
- });
66
-
67
- it('UserCellRenderer: display name beside the avatar', () => {
68
- render(<UserCellRenderer value={{ name: LONG }} field={{ type: 'user' } as any} />);
69
- expectTruncating(screen.getByText(LONG), LONG);
70
- });
71
-
72
- it('FileCellRenderer: single file name', () => {
73
- render(<FileCellRenderer value={{ name: LONG }} field={{ type: 'file' } as any} />);
74
- expectTruncating(screen.getByText(LONG), LONG);
75
- });
76
-
77
- it('SelectCellRenderer (dot): bounded container with title, shrinkable label', () => {
78
- render(
79
- <SelectCellRenderer
80
- value="opt1"
81
- field={{
82
- type: 'select',
83
- appearance: 'dot',
84
- options: [{ value: 'opt1', label: LONG }],
85
- } as any}
86
- />,
87
- );
88
- const label = screen.getByText(LONG);
89
- // The label shrinks inside the dot row; the row itself is width-bounded
90
- // and carries the full text on hover.
91
- expect(label).toHaveClass('min-w-0', 'truncate');
92
- const row = label.parentElement!;
93
- expect(row).toHaveClass('max-w-full');
94
- expect(row).toHaveAttribute('title', LONG);
95
- });
96
- });
@@ -1,169 +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 SDUI path delivers field metadata on ONE key — objectui#3233.
11
- *
12
- * `SchemaRenderer` hands every registered component the authored node as
13
- * `schema`. That key is the universal SDUI contract (`element:*`, `page:*`,
14
- * `object-grid`, `ReportRenderer` …) and is NOT retired. What was retired is
15
- * `schema` on the *field-widget* contract: a widget used to receive the node
16
- * under `schema` here and the metadata under `field` from the form renderer,
17
- * which is why ~30 widgets resolved their config as `field || schema`.
18
- *
19
- * The translation now happens once, at the registration seam
20
- * (`withFieldCarrier`), so downstream `field` is the only carrier. That is only
21
- * correct if the node reaches the widget UNCHANGED — "swap the key, don't drop
22
- * the cargo". The first test proves it by OBJECT IDENTITY: a spy sits where the
23
- * registry entry sits, records the object `SchemaRenderer` passed as `schema`,
24
- * and the widget's `field` must be that exact object. (Identity against the
25
- * authored literal would be wrong to assert — `SchemaRenderer` shallow-copies
26
- * the node while evaluating expressions, so the object it delivers is its own.)
27
- *
28
- * The last test pins that the REAL registration path goes through the adapter
29
- * at all: an adapter nobody applied would leave every SDUI-hosted widget
30
- * reading `undefined`, which is exactly the silent failure this convergence is
31
- * accused of risking.
32
- *
33
- * The sibling half of the invariant — the form path through
34
- * `renderFieldComponent` — is pinned in `packages/components/src/renderers/
35
- * form/__tests__/form-field-carrier.test.tsx`.
36
- */
37
-
38
- import React, { Suspense } from 'react';
39
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
40
- import { render, screen, cleanup } from '@testing-library/react';
41
- import { ComponentRegistry } from '@object-ui/core';
42
- import { SchemaRenderer } from '@object-ui/react';
43
-
44
- import { withFieldCarrier } from '../withFieldCarrier';
45
- import { registerField } from '../index';
46
- // Module scope, not a hook: `registerField('boolean')` registers
47
- // `React.lazy(() => import('./widgets/BooleanField'))`, and under a saturated
48
- // transform pipeline a first `import()` can outlive RTL's 1000ms budget. The
49
- // specifier matches `fieldWidgetMap`'s exactly, so ESM's cache makes that
50
- // factory resolve immediately (AGENTS.md 测试纪律 / objectui#3010).
51
- import '../widgets/BooleanField';
52
-
53
- /**
54
- * Capture holder, not bare module variables: `react-hooks/globals` forbids
55
- * REASSIGNING an outer binding from a component body, which is what a probe
56
- * has to do. Mutating a property of a stable object is the repo's usual shape
57
- * for this (see plugin-detail's `RelatedList` probes).
58
- */
59
- const captured: { props: Record<string, any> | null; node: unknown } = {
60
- props: null,
61
- node: null,
62
- };
63
-
64
- function CarrierProbe(props: any) {
65
- captured.props = props;
66
- return <div data-testid="carrier-probe">{props.field?.label ?? 'NO FIELD'}</div>;
67
- }
68
-
69
- const AdaptedProbe = withFieldCarrier(CarrierProbe);
70
-
71
- /**
72
- * Sits exactly where the registry entry sits, so it sees the host's props
73
- * verbatim, then forwards them to the adapter under test. This is what makes
74
- * the identity assertion possible: `captured.node` and the widget's `field`
75
- * are observed within a single render pass.
76
- */
77
- function CarrierSpy(props: any) {
78
- captured.node = props.schema;
79
- return <AdaptedProbe {...props} />;
80
- }
81
-
82
- beforeEach(() => {
83
- captured.props = null;
84
- captured.node = null;
85
- ComponentRegistry.register('carrierprobe', CarrierSpy, {
86
- namespace: 'field',
87
- skipFallback: true,
88
- });
89
- });
90
-
91
- afterEach(() => {
92
- cleanup();
93
- vi.restoreAllMocks();
94
- });
95
-
96
- describe('SDUI path — the authored node arrives as `field` (objectui#3233)', () => {
97
- it('hands the widget the very object it used to read through `schema`', () => {
98
- const node = {
99
- type: 'field:carrierprobe',
100
- name: 'city',
101
- label: 'City',
102
- placeholder: 'Where?',
103
- options: [{ label: 'Beijing', value: 'bj' }],
104
- };
105
-
106
- render(<SchemaRenderer schema={node as any} />);
107
-
108
- expect(screen.getByTestId('carrier-probe')).toHaveTextContent('City');
109
- expect(captured.props).not.toBeNull();
110
- expect(captured.node).toBeTruthy();
111
- // The payload-equivalence proof: same object, by reference. A
112
- // reconstruction would still render "City" while quietly dropping whatever
113
- // the copy did not know to carry.
114
- expect(captured.props!.field).toBe(captured.node);
115
- // …and it really is the whole authored node, not a narrowed projection.
116
- expect(captured.props!.field.name).toBe('city');
117
- expect(captured.props!.field.placeholder).toBe('Where?');
118
- expect(captured.props!.field.options).toEqual([{ label: 'Beijing', value: 'bj' }]);
119
- });
120
-
121
- it('consumes `schema` rather than forwarding it to the widget', () => {
122
- // Key ABSENCE, not `undefined`. Two reasons it must not be forwarded: a
123
- // widget could resolve `field || schema` again (the second contract
124
- // survives in practice while the type claims it is gone), and widgets
125
- // spread their leftover props onto DOM nodes, where an object-valued
126
- // `schema` attribute is a React warning.
127
- render(
128
- <SchemaRenderer schema={{ type: 'field:carrierprobe', name: 'city', label: 'City' } as any} />,
129
- );
130
-
131
- expect(captured.props).not.toBeNull();
132
- expect('schema' in captured.props!).toBe(false);
133
- });
134
-
135
- it('lets an explicit `field` win when a host supplies both', () => {
136
- // The form renderer passes `field` (resolved metadata) and never `schema`.
137
- // If some host ever passed both, the resolved metadata is the better
138
- // answer — the raw node is the fallback, not the override.
139
- const node = { type: 'field:carrierprobe', name: 'from-node', label: 'From node' };
140
- const metadata = { name: 'from-field', label: 'From field' };
141
-
142
- render(<AdaptedProbe schema={node} field={metadata} />);
143
-
144
- expect(captured.props!.field).toBe(metadata);
145
- });
146
-
147
- it('applies the adapter on the REAL registration path (`registerField`)', async () => {
148
- // The seam is only worth anything if `registerField` goes through it.
149
- // `BooleanField` reads `widget` / `name` off its metadata carrier and
150
- // nowhere else: `widget: 'checkbox'` selects the Checkbox over the Switch,
151
- // and `name` becomes the control's id. Both are node keys, so seeing them
152
- // honoured here proves the node reached `field` end to end — through the
153
- // real registry entry, the real SchemaRenderer, and the real widget.
154
- registerField('boolean');
155
-
156
- render(
157
- <Suspense fallback={<div>loading</div>}>
158
- <SchemaRenderer
159
- schema={
160
- { type: 'field:boolean', name: 'is_active', label: 'Active', widget: 'checkbox' } as any
161
- }
162
- />
163
- </Suspense>,
164
- );
165
-
166
- const control = await screen.findByRole('checkbox');
167
- expect(control).toHaveAttribute('id', 'is_active');
168
- });
169
- });
@@ -1,107 +0,0 @@
1
- /**
2
- * [framework#2926 ⑧] LookupCellRenderer must honor the target object's
3
- * configured display field. ObjectGrid forwards `display_field` on the
4
- * column meta (RELATIONAL_META_KEYS) exactly like `reference`, but the read
5
- * cell used to ignore it and always ran the hardcoded heuristics — `name`
6
- * first — so a target object whose displayNameField is a localized/label
7
- * field still rendered the raw API `name`.
8
- */
9
- import { describe, it, expect, vi } from 'vitest';
10
- import { render, screen, waitFor } from '@testing-library/react';
11
- import '@testing-library/jest-dom';
12
- import React from 'react';
13
-
14
- import { LookupCellRenderer } from '../index';
15
- import { SchemaRendererProvider } from '@object-ui/react';
16
-
17
- const ID_A = '01ARZ3NDEKTSV4RRFFQ69G5FAA';
18
- const ID_B = '01ARZ3NDEKTSV4RRFFQ69G5FAB';
19
-
20
- function makeDataSource() {
21
- const findOne = vi.fn(async (object: string, id: string) => {
22
- if (object === 'showcase_category') {
23
- // Record carries BOTH the raw API name and a localized label field.
24
- if (id === ID_A) return { id, name: 'cat_hardware', label_zh: '硬件' };
25
- if (id === ID_B) return { id, name: 'cat_software', label_zh: '软件' };
26
- }
27
- return null;
28
- });
29
- return { findOne, find: vi.fn() } as any;
30
- }
31
-
32
- describe('LookupCellRenderer — display_field resolution', () => {
33
- it('prefers the configured display_field over the heuristic `name`', async () => {
34
- const ds = makeDataSource();
35
- render(
36
- <SchemaRendererProvider dataSource={ds}>
37
- <LookupCellRenderer
38
- value={ID_A}
39
- field={{ type: 'lookup', reference: 'showcase_category', display_field: 'label_zh' } as any}
40
- />
41
- </SchemaRendererProvider>,
42
- );
43
- await waitFor(() => {
44
- expect(screen.getByText('硬件')).toBeInTheDocument();
45
- });
46
- expect(screen.queryByText('cat_hardware')).not.toBeInTheDocument();
47
- });
48
-
49
- it('keeps the heuristic (`name` first) when no display_field is configured', async () => {
50
- const ds = makeDataSource();
51
- render(
52
- <SchemaRendererProvider dataSource={ds}>
53
- <LookupCellRenderer
54
- value={ID_A}
55
- field={{ type: 'lookup', reference: 'showcase_category' } as any}
56
- />
57
- </SchemaRendererProvider>,
58
- );
59
- await waitFor(() => {
60
- expect(screen.getByText('cat_hardware')).toBeInTheDocument();
61
- });
62
- });
63
-
64
- it('uses display_field on server-expanded nested objects (no fetch path)', () => {
65
- const ds = makeDataSource();
66
- render(
67
- <SchemaRendererProvider dataSource={ds}>
68
- <LookupCellRenderer
69
- value={{ id: ID_A, name: 'cat_hardware', label_zh: '硬件' } as any}
70
- field={{ type: 'lookup', reference: 'showcase_category', display_field: 'label_zh' } as any}
71
- />
72
- </SchemaRendererProvider>,
73
- );
74
- expect(screen.getByText('硬件')).toBeInTheDocument();
75
- expect(ds.findOne).not.toHaveBeenCalled();
76
- });
77
-
78
- it('does not serve a cached heuristic name to a display_field column (cache key isolation)', async () => {
79
- const ds = makeDataSource();
80
- // First: a column WITHOUT display_field resolves and caches the heuristic name.
81
- const first = render(
82
- <SchemaRendererProvider dataSource={ds}>
83
- <LookupCellRenderer
84
- value={ID_B}
85
- field={{ type: 'lookup', reference: 'showcase_category' } as any}
86
- />
87
- </SchemaRendererProvider>,
88
- );
89
- await waitFor(() => {
90
- expect(screen.getByText('cat_software')).toBeInTheDocument();
91
- });
92
- first.unmount();
93
- // Then: a column WITH display_field for the same record must show the
94
- // configured field, not the previously cached heuristic name.
95
- render(
96
- <SchemaRendererProvider dataSource={ds}>
97
- <LookupCellRenderer
98
- value={ID_B}
99
- field={{ type: 'lookup', reference: 'showcase_category', display_field: 'label_zh' } as any}
100
- />
101
- </SchemaRendererProvider>,
102
- );
103
- await waitFor(() => {
104
- expect(screen.getByText('软件')).toBeInTheDocument();
105
- });
106
- });
107
- });
@@ -1,119 +0,0 @@
1
- /**
2
- * Regression for objectstack-ai/objectui#2357: the lookup chip
3
- * (LookupCellRenderer — record header highlight + detail sections) resolved
4
- * display names via the local key heuristic (`pickRecordDisplayName`), whose
5
- * `_number`/`_code` suffix scan surfaced an autonumber (`0001`) over the
6
- * record's real name whenever the referenced object declared its title in
7
- * `nameField`/`titleFormat`. The picker (LookupField) already resolved via the
8
- * unified ADR-0079 resolver, so the two surfaces disagreed.
9
- *
10
- * The chip must now resolve through the referenced object's schema
11
- * (`dataSource.getObjectSchema`) with the precedence
12
- * `displayField → nameField/titleFormat → type-aware derivation`, keeping the
13
- * key heuristic only as the no-schema fallback.
14
- */
15
- import { describe, it, expect, vi } from 'vitest';
16
- import { render, screen, waitFor } from '@testing-library/react';
17
- import '@testing-library/jest-dom';
18
- import React from 'react';
19
-
20
- import { LookupCellRenderer } from '../index';
21
- import { SchemaRendererProvider } from '@object-ui/react';
22
-
23
- // The expanded record: the autonumber (`req_number`) precedes the real name
24
- // (`req_name`) in key order, so the OLD heuristic-only path rendered "0001".
25
- const EXPANDED = { id: 'rec1', req_number: '0001', req_name: 'Annual Maintenance' };
26
-
27
- function makeDataSource(objectName: string, schema: any, record?: any) {
28
- return {
29
- find: vi.fn(),
30
- findOne: vi.fn(async (object: string, id: string) =>
31
- object === objectName && record && id === record.id ? record : null,
32
- ),
33
- getObjectSchema: vi.fn(async (object: string) =>
34
- object === objectName ? schema : null,
35
- ),
36
- } as any;
37
- }
38
-
39
- describe('LookupCellRenderer — schema-aware display name (issue #2357)', () => {
40
- it('expanded object: nameField beats the autonumber suffix heuristic', async () => {
41
- const ds = makeDataSource('mtc_request_a', { nameField: 'req_name' });
42
- render(
43
- <SchemaRendererProvider dataSource={ds}>
44
- <LookupCellRenderer
45
- value={EXPANDED}
46
- field={{ type: 'lookup', reference_to: 'mtc_request_a' } as any}
47
- />
48
- </SchemaRendererProvider>,
49
- );
50
- await waitFor(() => {
51
- expect(screen.getByText('Annual Maintenance')).toBeInTheDocument();
52
- });
53
- expect(screen.queryByText('0001')).not.toBeInTheDocument();
54
- });
55
-
56
- it('expanded object: titleFormat template resolves when no nameField', async () => {
57
- const ds = makeDataSource('mtc_request_b', { titleFormat: '{req_name} - {req_number}' });
58
- render(
59
- <SchemaRendererProvider dataSource={ds}>
60
- <LookupCellRenderer
61
- value={{ ...EXPANDED }}
62
- field={{ type: 'lookup', reference_to: 'mtc_request_b' } as any}
63
- />
64
- </SchemaRendererProvider>,
65
- );
66
- await waitFor(() => {
67
- expect(screen.getByText('Annual Maintenance - 0001')).toBeInTheDocument();
68
- });
69
- });
70
-
71
- it('explicit displayField wins over the schema nameField', async () => {
72
- const ds = makeDataSource('mtc_request_c', { nameField: 'req_name' });
73
- render(
74
- <SchemaRendererProvider dataSource={ds}>
75
- <LookupCellRenderer
76
- value={{ ...EXPANDED }}
77
- field={{ type: 'lookup', reference_to: 'mtc_request_c', display_field: 'req_number' } as any}
78
- />
79
- </SchemaRendererProvider>,
80
- );
81
- // Author explicitly asked for the number — honour it (also the documented
82
- // workaround in the issue).
83
- expect(await screen.findByText('0001')).toBeInTheDocument();
84
- });
85
-
86
- it('fetch-on-demand primitive id resolves via the schema nameField too', async () => {
87
- // Opaque ULID-ish id so the old path would have resolved the fetched
88
- // record through the heuristic and shown its autonumber.
89
- const id = '01ARZ3NDEKTSV4RRFFQ69G5FBB';
90
- const record = { id, req_number: '0007', req_name: 'Pump Overhaul' };
91
- const ds = makeDataSource('mtc_request_d', { nameField: 'req_name' }, record);
92
- render(
93
- <SchemaRendererProvider dataSource={ds}>
94
- <LookupCellRenderer
95
- value={id}
96
- field={{ type: 'lookup', reference_to: 'mtc_request_d' } as any}
97
- />
98
- </SchemaRendererProvider>,
99
- );
100
- await waitFor(() => {
101
- expect(screen.getByText('Pump Overhaul')).toBeInTheDocument();
102
- });
103
- expect(screen.queryByText('0007')).not.toBeInTheDocument();
104
- expect(ds.getObjectSchema).toHaveBeenCalledWith('mtc_request_d');
105
- });
106
-
107
- it('falls back to the key heuristic when the data source has no schema API', () => {
108
- const ds = { find: vi.fn(), findOne: vi.fn() } as any; // no getObjectSchema
109
- render(
110
- <SchemaRendererProvider dataSource={ds}>
111
- <LookupCellRenderer
112
- value={{ id: 'rec2', framework_name: 'SOX' }}
113
- field={{ type: 'lookup', reference_to: 'mtc_request_e' } as any}
114
- />
115
- </SchemaRendererProvider>,
116
- );
117
- expect(screen.getByText('SOX')).toBeInTheDocument();
118
- });
119
- });
@@ -1,57 +0,0 @@
1
- /**
2
- * Regression: LookupCellRenderer must resolve a bare foreign-key id to a
3
- * display name when the field metadata carries the ObjectStack `reference`
4
- * key (as produced by `Field.lookup('...')` in @objectstack/spec) — not only
5
- * the objectui `reference_to` alias.
6
- *
7
- * Real-world symptom (framework app-showcase): inline-edit a lookup cell, pick
8
- * a record, click another row → the cell showed a muted "—" forever because
9
- * the just-picked opaque id could not be resolved. Every other reader in the
10
- * codebase (LookupField, UserField, DetailSection, RelatedList, …) already
11
- * accepts `reference_to || reference`; this read cell used to read only
12
- * `reference_to`.
13
- */
14
- import { describe, it, expect, vi } from 'vitest';
15
- import { render, screen, waitFor } from '@testing-library/react';
16
- import '@testing-library/jest-dom';
17
- import React from 'react';
18
-
19
- import { LookupCellRenderer } from '../index';
20
- import { SchemaRendererProvider } from '@object-ui/react';
21
-
22
- // A ULID-style id: `isLikelyOpaqueId` returns true for it, so the OLD code
23
- // (which failed to resolve) would fall back to the muted "—" placeholder.
24
- const OPAQUE_ID = '01ARZ3NDEKTSV4RRFFQ69G5FAV';
25
-
26
- function makeDataSource() {
27
- const findOne = vi.fn(async (object: string, id: string) => {
28
- if (object === 'showcase_account' && id === OPAQUE_ID) {
29
- return { id, name: 'Globex' };
30
- }
31
- return null;
32
- });
33
- return { findOne, find: vi.fn() } as any;
34
- }
35
-
36
- describe('LookupCellRenderer — reference key resolution', () => {
37
- it('resolves an opaque id to a name via the `reference` key (not just reference_to)', async () => {
38
- const ds = makeDataSource();
39
- render(
40
- <SchemaRendererProvider dataSource={ds}>
41
- <LookupCellRenderer
42
- value={OPAQUE_ID}
43
- // ObjectStack convention: the lookup target lives under `reference`.
44
- field={{ type: 'lookup', reference: 'showcase_account' } as any}
45
- />
46
- </SchemaRendererProvider>,
47
- );
48
-
49
- // The related record's display name must appear…
50
- await waitFor(() => {
51
- expect(screen.getByText('Globex')).toBeInTheDocument();
52
- });
53
- // …and the muted placeholder must NOT be what the user sees.
54
- expect(screen.queryByText('—')).not.toBeInTheDocument();
55
- expect(ds.findOne).toHaveBeenCalledWith('showcase_account', OPAQUE_ID);
56
- });
57
- });
@@ -1,91 +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
- * Registry collision guard.
11
- *
12
- * `@object-ui/components` registers display widgets (`text`, `image`,
13
- * `avatar`, `html`, `grid`) under their bare names. `@object-ui/fields`
14
- * registers form-input widgets that — by name — collide with those.
15
- *
16
- * The fix: field registrations for these 5 types pass `skipFallback: true`,
17
- * so the bare lookup keeps returning the display widget while the field
18
- * input is still reachable via the `field:<type>` namespace.
19
- *
20
- * Regression contract:
21
- * 1. `FIELD_TYPES_SKIP_FALLBACK` must contain every colliding name.
22
- * 2. After `registerAllFields()`, bare lookups must NOT resolve to a
23
- * field renderer for any of those names.
24
- * 3. The `field:<type>` namespaced lookup must still work, so form
25
- * schemas using `{ type: 'text' }` still find an input renderer
26
- * when explicitly requested.
27
- */
28
-
29
- import { describe, it, expect, beforeAll } from 'vitest';
30
- import { ComponentRegistry } from '@object-ui/core';
31
-
32
- // Importing the fields entry runs `registerAllFields()` at module load.
33
- // Importing the components entry runs display-widget registration the
34
- // same way. Both must be loaded before we inspect the registry.
35
- import '@object-ui/components';
36
- import { registerAllFields } from '../index';
37
-
38
- const COLLIDING_TYPES = ['text', 'html', 'image', 'avatar', 'grid'] as const;
39
-
40
- describe('registry collision (fields ↔ components)', () => {
41
- beforeAll(() => {
42
- // Defensive: in case the auto-register at import time hasn't run yet
43
- // under the test runner's module isolation.
44
- registerAllFields();
45
- });
46
-
47
- it.each(COLLIDING_TYPES)(
48
- 'bare "%s" lookup is NOT clobbered by the field renderer',
49
- (type) => {
50
- // The bare lookup must resolve, and it must resolve to a display widget
51
- // (or a FallbackComponent / null) — never to the lazy field renderer.
52
- // The field renderer is a React.lazy() result; the display widget is a
53
- // plain function component. We don't pin to a specific identity, but we
54
- // DO pin that whatever the bare lookup returns, the namespaced
55
- // `field:<type>` lookup returns something different.
56
- const bare = ComponentRegistry.get(type);
57
- const namespaced = ComponentRegistry.get(`field:${type}`);
58
-
59
- // Namespaced field renderer must exist for every colliding type —
60
- // that's the whole point of keeping the field accessible.
61
- expect(
62
- namespaced,
63
- `field:${type} must be registered so forms can still use it`,
64
- ).toBeTruthy();
65
-
66
- // The bare and namespaced renderers must NOT be identical. If they
67
- // are, it means the field renderer wrote to the bare fallback and
68
- // would render an empty <input> instead of the display widget.
69
- expect(
70
- bare,
71
- `bare "${type}" must not be the same component as field:${type}`,
72
- ).not.toBe(namespaced);
73
- },
74
- );
75
-
76
- it('non-colliding field types DO populate the bare fallback', () => {
77
- // Sanity check: a field type with no display-widget collision (e.g.
78
- // `email`, `password`, `number`) must still register both at the
79
- // namespaced AND bare keys, otherwise we'd silently break forms.
80
- for (const type of ['email', 'password', 'number', 'date', 'select']) {
81
- expect(
82
- ComponentRegistry.get(`field:${type}`),
83
- `field:${type} must be registered`,
84
- ).toBeTruthy();
85
- expect(
86
- ComponentRegistry.get(type),
87
- `bare "${type}" must also be reachable (no collision)`,
88
- ).toBeTruthy();
89
- }
90
- });
91
- });
@@ -1,20 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { resolveFieldCurrency } from '../index';
3
-
4
- describe('resolveFieldCurrency', () => {
5
- it('prefers the field explicit currency over everything', () => {
6
- expect(resolveFieldCurrency({ currency: 'JPY', currencyConfig: { defaultCurrency: 'EUR' } }, 'USD')).toBe('JPY');
7
- });
8
- it('falls back to currencyConfig.defaultCurrency, then legacy defaultCurrency', () => {
9
- expect(resolveFieldCurrency({ currencyConfig: { defaultCurrency: 'EUR' } }, 'USD')).toBe('EUR');
10
- expect(resolveFieldCurrency({ defaultCurrency: 'GBP' } as any, 'USD')).toBe('GBP');
11
- });
12
- it('falls back to the tenant default when the field omits its own', () => {
13
- expect(resolveFieldCurrency({}, 'CNY')).toBe('CNY');
14
- expect(resolveFieldCurrency(null, 'CNY')).toBe('CNY');
15
- });
16
- it('returns undefined when nothing is known (→ renderer shows a plain number)', () => {
17
- expect(resolveFieldCurrency({})).toBeUndefined();
18
- expect(resolveFieldCurrency(undefined, undefined)).toBeUndefined();
19
- });
20
- });