-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathFieldEditWidget.test.ts
More file actions
90 lines (82 loc) · 3.97 KB
/
Copy pathFieldEditWidget.test.ts
File metadata and controls
90 lines (82 loc) · 3.97 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
/**
* 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.
*/
import { describe, it, expect } from 'vitest';
import {
FORM_FIELD_TYPES,
INLINE_EXCLUDED_FIELD_TYPES,
hasFieldEditWidget,
mapFieldTypeToFormType,
} from './index';
/**
* A field type the form can render — either a direct widget-map key, or a spec
* spelling that resolves onto one through the alias table (`secret` →
* `field:password`, `autonumber` → `field:auto_number`, …).
*
* `FORM_FIELD_TYPES` alone is `Object.keys(fieldWidgetMap)`, which does NOT
* include the alias-only spellings — see the note at index.tsx's
* `resolveFormWidgetType`. Using it as the definition of "a real type" made the
* staleness check below reject `secret`, a genuine spec field type the form
* renders, purely because it arrives via an alias.
*/
function isRenderableFormType(t: string): boolean {
// `text` is the alias table's fallback, so a non-`field:text` result means an
// explicit entry exists. `text` itself is a direct widget key, so it is
// already covered by the first clause and no real type is missed here.
return FORM_FIELD_TYPES.includes(t) || mapFieldTypeToFormType(t) !== 'field:text';
}
/**
* Drift-guard: the inline cell editor reuses the form's field widgets, but the
* two lists were hand-maintained separately and drifted — `lookup` (and other
* relational types) had a perfectly good form widget yet fell back to a plain
* text box inline, because nobody wired it up. This pins the contract: every
* type the FORM can render must have an explicit inline decision — an editor or
* a documented exclusion — so a new form widget can never again silently become
* an editable text box (or a missing one) in the grid.
*/
describe('inline editor ↔ form widget parity', () => {
it('every form field type either has an inline editor or is explicitly excluded', () => {
const undecided = FORM_FIELD_TYPES.filter(
(t) => !hasFieldEditWidget(t) && !INLINE_EXCLUDED_FIELD_TYPES.has(t),
);
// If this fails: a form widget type has no inline decision. Either add it to
// EDIT_WIDGETS (inline-editable) or to INLINE_EXCLUDED_FIELD_TYPES (with a
// reason) in FieldEditWidget.tsx.
expect(undecided).toEqual([]);
});
it('the exclusion set lists only real form types (no stale entries)', () => {
const stale = [...INLINE_EXCLUDED_FIELD_TYPES].filter((t) => !isRenderableFormType(t));
expect(stale).toEqual([]);
});
it('credential types are never inline-editable', () => {
// The grid's fallback for a type with no inline editor is a PLAIN TEXT input.
// Both of these are masked on read, so that input would show the mask as the
// value and write it straight back; `secret` also round-trips through an
// encrypted store (ADR-0100), so the cell holds an opaque ref, not the value.
for (const t of ['password', 'secret']) {
expect(hasFieldEditWidget(t), `${t} must not have an inline editor`).toBe(false);
expect(INLINE_EXCLUDED_FIELD_TYPES.has(t), `${t} must be explicitly excluded`).toBe(true);
}
});
it('relational fields use the standard picker inline (regression: lookup was a text box)', () => {
for (const t of ['lookup', 'master_detail', 'user', 'owner']) {
expect(hasFieldEditWidget(t)).toBe(true);
}
});
it('computed / binary form types are NOT inline-editable (excluded)', () => {
for (const t of ['formula', 'summary', 'auto_number', 'file', 'image']) {
expect(hasFieldEditWidget(t)).toBe(false);
expect(INLINE_EXCLUDED_FIELD_TYPES.has(t)).toBe(true);
}
});
it('structured-value types edit inline with their form widget (color/address/location/code/…)', () => {
for (const t of ['color', 'address', 'location', 'geolocation', 'code', 'qrcode']) {
expect(hasFieldEditWidget(t)).toBe(true);
expect(INLINE_EXCLUDED_FIELD_TYPES.has(t)).toBe(false);
}
});
});