Skip to content

Commit 2b418a4

Browse files
test(lint): pin the four unprovisionedAnchors threading sites in validate-react-page-props (#8989)
`checkFieldRefs` takes the anchor index as an optional trailing parameter, so a call site that stops passing it keeps compiling and silently downgrades to the pre-#8340 behaviour. Four of the six in-repo threading sites were threaded by convention only: dropping the index at any of them left the whole lint suite green. One behaviour test per uncovered site, each written ablation-first. Refs #8943 Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 36ce254 commit 2b418a4

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

packages/lint/src/validate-react-page-props.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,4 +1242,101 @@ describe('validateReactPageProps — unprovisioned injected anchors (#8340)', ()
12421242
);
12431243
expect(f).toEqual([]);
12441244
});
1245+
1246+
// ───────────────────────────────────────────────────────────────────────
1247+
// [#8943] The four `checkBlockFieldProps` calls that were threaded by
1248+
// CONVENTION ONLY.
1249+
//
1250+
// `checkFieldRefs` takes the anchor index as an OPTIONAL trailing
1251+
// parameter, so a call site that stops passing it keeps compiling and
1252+
// silently downgrades to the pre-#8340 behaviour: existence still
1253+
// answered, provenance never asked. Nothing else can see that happen —
1254+
// the parameter being optional means no type error, and the local
1255+
// `unprovisionedAnchors` binding stays READ by the sibling calls in the
1256+
// same function, so there is no TS6133 either. No CI gate reads call
1257+
// sites at all (#8664).
1258+
//
1259+
// #8943 measured all six threading sites by dropping the argument at each
1260+
// in turn. The two module seams were already pinned — `validatePageFieldBindings`
1261+
// and the `queried` bucket, both covered by the FILTER-position test above.
1262+
// The four below stayed GREEN across the whole packages/lint suite with the
1263+
// index dropped. Each test here was then written ablation-first and observed
1264+
// RED under its own site's ablation, which is precisely why it exists.
1265+
//
1266+
// The branches are disjoint by tag and prop, so each test names exactly one
1267+
// site: `own` refs reach `<ListView columns>`; the subform pair needs
1268+
// `<ObjectForm subforms>`; the COMPONENT_FIELD_SPECS table is reachable
1269+
// only through `<Block type>`, since no react tag's own schemaType
1270+
// (`list-view`, `object-form`, `object-chart`) is a key in it.
1271+
// ───────────────────────────────────────────────────────────────────────
1272+
1273+
/** A LOCAL object — platform storage is real, so it registers no unprovisioned anchor. */
1274+
const localOrder = { name: 'crm_order', fields: [{ name: 'code' }] };
1275+
1276+
it('[#8943] R1 — PINS the `own` display-ref call (the `skipped` bucket of REACT_FIELD_SPECS)', () => {
1277+
// `<ListView columns>` is a DISPLAY binding, not a query: the consequence
1278+
// clause is the blank-column one, not the constant-false one.
1279+
const f = validateReactPageProps(
1280+
extPage(`function Page(){ return <ListView objectName="ext_customer" columns={['owner_id']} />; }`),
1281+
);
1282+
expect(f.filter((x) => x.rule === PAGE_FIELD_UNKNOWN)).toHaveLength(0);
1283+
const warned = f.filter((x) => x.rule === PAGE_FIELD_UNPROVISIONED);
1284+
expect(warned).toHaveLength(1);
1285+
expect(warned[0].severity).toBe('warning');
1286+
expect(warned[0].path).toBe('pages[0].source › columns[0]');
1287+
expect(warned[0].message).toContain('external object (ADR-0015)');
1288+
expect(warned[0].message).toContain('blank, on every record');
1289+
});
1290+
1291+
it('[#8943] R3 — PINS the ObjectForm subform CHILD refs (resolved against `childObject`)', () => {
1292+
// The child batch resolves against the subform's own object, so the
1293+
// external one is the CHILD here and the form's object is local.
1294+
const f = validateReactPageProps(
1295+
extPage(
1296+
`function Page(){ return <ObjectForm objectName="crm_order" subforms={[{ childObject: 'ext_customer', columns: ['owner_id'] }]} />; }`,
1297+
[extCustomer(), localOrder],
1298+
),
1299+
);
1300+
expect(f.filter((x) => x.rule === PAGE_FIELD_UNKNOWN)).toHaveLength(0);
1301+
const warned = f.filter((x) => x.rule === PAGE_FIELD_UNPROVISIONED);
1302+
expect(warned).toHaveLength(1);
1303+
expect(warned[0].severity).toBe('warning');
1304+
expect(warned[0].path).toBe('pages[0].source › subforms[0].columns[0]');
1305+
expect(warned[0].message).toContain('"ext_customer"');
1306+
expect(warned[0].message).toContain('blank, on every record');
1307+
});
1308+
1309+
it('[#8943] R4 — PINS the subform `totalField` rollup (resolved against the FORM object)', () => {
1310+
// The exception inside the exception: `totalField` names the PARENT
1311+
// object's field the child sum rolls up into, so the external object is
1312+
// the form's own and the child is local.
1313+
const f = validateReactPageProps(
1314+
extPage(
1315+
`function Page(){ return <ObjectForm objectName="ext_customer" subforms={[{ childObject: 'crm_order', columns: ['code'], totalField: 'owner_id' }]} />; }`,
1316+
[extCustomer(), localOrder],
1317+
),
1318+
);
1319+
expect(f.filter((x) => x.rule === PAGE_FIELD_UNKNOWN)).toHaveLength(0);
1320+
const warned = f.filter((x) => x.rule === PAGE_FIELD_UNPROVISIONED);
1321+
expect(warned).toHaveLength(1);
1322+
expect(warned[0].severity).toBe('warning');
1323+
expect(warned[0].path).toBe('pages[0].source › subforms[0].totalField');
1324+
expect(warned[0].message).toContain('"ext_customer"');
1325+
});
1326+
1327+
it('[#8943] R5 — PINS the COMPONENT_FIELD_SPECS path reached by `<Block type>`', () => {
1328+
// The shared table `validate-page-field-bindings` walks; on this surface
1329+
// it is reachable only by the type the author spells out, which is what
1330+
// makes the escape hatch checked rather than a hole.
1331+
const f = validateReactPageProps(
1332+
extPage(`function Page(){ return <Block type="element:form" objectName="ext_customer" fields={['owner_id']} />; }`),
1333+
);
1334+
expect(f.filter((x) => x.rule === PAGE_FIELD_UNKNOWN)).toHaveLength(0);
1335+
const warned = f.filter((x) => x.rule === PAGE_FIELD_UNPROVISIONED);
1336+
expect(warned).toHaveLength(1);
1337+
expect(warned[0].severity).toBe('warning');
1338+
expect(warned[0].where).toBe('page "p" › <Block>');
1339+
expect(warned[0].path).toBe('pages[0].source › fields[0]');
1340+
expect(warned[0].message).toContain('external object (ADR-0015)');
1341+
});
12451342
});

0 commit comments

Comments
 (0)