-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathTextAreaField.mobileFullscreen.test.tsx
More file actions
160 lines (138 loc) · 6.33 KB
/
Copy pathTextAreaField.mobileFullscreen.test.tsx
File metadata and controls
160 lines (138 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* `TextAreaField`'s fullscreen affordance has ONE source (objectui#3232).
*
* It used to be read from four places with a `??` chain — a `mobileFullscreen`
* (camelCase) prop, `field.mobile_fullscreen`, a `mobile_fullscreen` prop, and
* `schema.mobile_fullscreen`. Only the field-metadata flag was ever produced:
* `ObjectForm` stamps it on long-text fields from
* `ObjectFormSchema.mobile.fullscreenLongText`. The camelCase prop had no
* producer anywhere in the repo, and the snake_case prop could not arrive
* because `stripRegisteredFieldProps` (components/src/renderers/form/form.tsx)
* removes it from what registered widgets are forwarded.
*
* So this file pins both halves of that convergence:
*
* 1. **The live path still works.** The metadata flag drives the affordance
* end to end — button, dialog, and the committed edit. Deleting the dead
* reads must not have cost the working behaviour, and this is the test
* that fails if a future refactor drops the real read too.
* 2. **The retired prop spellings are gone**, at compile time (they are not
* on the closed `FieldWidgetComponentProps`, objectui#3221) and at runtime
* (a host that passes one anyway gets no affordance). Convergence means a
* misspelled flag is now inert and loud, not silently absorbed.
*
* Note the built-in (unregistered) `textarea` branch in `form.tsx` reads
* `mobile_fullscreen || fullscreen` off the form-field props and renders its
* own `FullscreenTextarea`. That is a separate, live path and is untouched
* here — this file is about the registered `field:textarea` widget only.
*/
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { TextAreaField } from '../TextAreaField';
import type { FieldWidgetComponentProps } from '../types';
import type { FieldMetadata } from '@object-ui/types';
const fieldMeta = (extra: Record<string, unknown> = {}) =>
({
name: 'description',
label: 'Description',
type: 'textarea',
...extra,
}) as unknown as FieldMetadata;
describe('TextAreaField mobile fullscreen — the metadata flag is the only source', () => {
it('renders the expand affordance when the field metadata sets mobile_fullscreen', () => {
render(
<TextAreaField
value="hello"
onChange={() => {}}
field={fieldMeta({ mobile_fullscreen: true })}
/>,
);
expect(screen.getByTestId('textarea-fullscreen-toggle')).toBeInTheDocument();
});
it('opens the fullscreen dialog and commits the edited draft', () => {
const onChange = vi.fn();
render(
<TextAreaField
value="hello"
onChange={onChange}
field={fieldMeta({ mobile_fullscreen: true })}
/>,
);
// Closed until the affordance is used — the dialog is not just mounted.
expect(screen.queryByTestId('textarea-fullscreen-dialog')).not.toBeInTheDocument();
fireEvent.click(screen.getByTestId('textarea-fullscreen-toggle'));
expect(screen.getByTestId('textarea-fullscreen-dialog')).toBeInTheDocument();
const dialogInput = screen.getByTestId('textarea-fullscreen-input');
expect(dialogInput).toHaveValue('hello');
fireEvent.change(dialogInput, { target: { value: 'hello from fullscreen' } });
// The draft is local until "Done" — the host is not notified per keystroke.
expect(onChange).not.toHaveBeenCalled();
fireEvent.click(screen.getByTestId('textarea-fullscreen-save'));
expect(onChange).toHaveBeenCalledWith('hello from fullscreen');
});
it('reads the flag off `schema` when the host supplies no `field`', () => {
// `SchemaRenderer` passes the authored node as `schema`; the widget
// resolves its config as `field || schema`, which is why deleting the
// separate `schema.mobile_fullscreen` read lost nothing. `field` is
// required by the type, so the cast reproduces the runtime shape only.
render(
<TextAreaField
value=""
onChange={() => {}}
field={undefined as unknown as FieldMetadata}
schema={fieldMeta({ mobile_fullscreen: true })}
/>,
);
expect(screen.getByTestId('textarea-fullscreen-toggle')).toBeInTheDocument();
});
it('renders no affordance when the metadata does not opt in', () => {
render(<TextAreaField value="hello" onChange={() => {}} field={fieldMeta()} />);
expect(screen.queryByTestId('textarea-fullscreen-toggle')).not.toBeInTheDocument();
expect(screen.queryByTestId('textarea-fullscreen-dialog')).not.toBeInTheDocument();
});
});
describe('TextAreaField mobile fullscreen — the retired prop spellings', () => {
it('does not declare them on the widget contract', () => {
const props = {} as FieldWidgetComponentProps<string>;
// Both were read by the widget and written by no host. With the closed
// props type (objectui#3221) they are now compile errors, so a future
// author reaches for the metadata flag instead of a silently ignored prop.
// @ts-expect-error `mobileFullscreen` is not part of this contract
void props.mobileFullscreen;
// @ts-expect-error `mobile_fullscreen` is a metadata key, not a widget prop
void props.mobile_fullscreen;
expect(true).toBe(true);
});
it('ignores them at runtime when a host passes them anyway', () => {
// Untyped hosts (plain JS, `as any` spreads) can still get these onto the
// element. They must not resurrect the affordance: the metadata flag is
// the contract. Unknown props land on the DOM spread, so React's unknown
// -attribute warnings are expected noise here and are suppressed.
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
try {
const hostProps = {
mobileFullscreen: true,
mobile_fullscreen: true,
} as unknown as Partial<FieldWidgetComponentProps<string>>;
render(
<TextAreaField
value="hello"
onChange={() => {}}
field={fieldMeta()}
{...hostProps}
/>,
);
expect(screen.queryByTestId('textarea-fullscreen-toggle')).not.toBeInTheDocument();
} finally {
consoleError.mockRestore();
}
});
});