diff --git a/.changeset/network-escape-batch3.md b/.changeset/network-escape-batch3.md new file mode 100644 index 000000000..f30fed98f --- /dev/null +++ b/.changeset/network-escape-batch3.md @@ -0,0 +1,4 @@ +--- +--- + +Test-only change: the four `plugin-detail` network-escape files in batch 3 of objectui#7307 now serve their probes from a recording double instead of a real socket, and their lines leave `KNOWN_ESCAPES` and `PINNED_LEDGER` together. One of the four also reached `/api/task/42` through `DetailView`'s `api` branch, so its router serves that route too. No published behaviour changes — no product source is touched. diff --git a/packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx b/packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx index 73a2c4a25..853d071f6 100644 --- a/packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx +++ b/packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx @@ -52,7 +52,7 @@ */ import * as React from 'react'; -import { describe, it, expect, afterEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, within } from '@testing-library/react'; import { RecordContextProvider, SchemaRenderer } from '@object-ui/react'; // Module-scope side-effect imports: the registry must hold the page/record @@ -152,7 +152,99 @@ function renderDefaultPage(): HTMLElement { return block as HTMLElement; } -afterEach(cleanup); +/* ───────────────────────────────────────────────────────────────────────────── + * objectui#7307 — this file's `/api/v1/security/explain` escape, served here. + * + * Nothing below asks for a security verdict, yet every run opened a REAL TCP + * connection to `http://localhost:3000`. Traced with a stack probe on the + * network-escape guard's attribution point (measured, not inferred): + * + * SchemaRenderer -> the synthesized `record:details` node + * -> RecordDetailsRenderer packages/plugin-detail/src/renderers/record-details.tsx:302 + * -> DetailView packages/plugin-detail/src/DetailView.tsx:290, :296 + * -> useRecordEditable packages/plugin-detail/src/useRecordEditable.ts:76 + * -> `const doFetch = apiFetch ?? fetch` [the escape] + * POST /api/v1/security/explain (twice per render: edit, then delete) + * + * `useRecordEditable` reads the host's AUTHENTICATED `apiFetch` off + * `SchemaRendererContext` and, with no host supplying one, degrades to the + * GLOBAL `fetch` by design — a standalone embed must keep rendering rather than + * crash. Under happy-dom that global is a real HTTP client and the document URL + * defaults to `http://localhost:3000`, so the relative path resolved to a live + * request. The read is best-effort (a network or parse failure leaves the + * record editable — fail open), which is why the cases below stayed green while + * the request always failed. + * + * Answered from a RECORDING double — the shape objectui#5225 settled on, carried + * by `packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx` and by + * this burn-down's earlier batches (see + * `packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx`). + * Deliberately NOT a blanket network stub: it records every URL it is handed and + * `afterEach` fails on any URL outside the set it serves, so an escape to + * somewhere else reds here instead of vanishing into that `catch`. + * + * What it answers, and why that changes no assertion here: the permissive + * verdict, in the two response shapes the two explain hooks read — + * `{ record: { visible } }` for a single `recordId`, and + * `{ records: [{ recordId, visible }] }` for a batched `recordIds`. Only the + * FIRST is reached from this file (every call measured above comes from + * `useRecordEditable`); the batched branch is kept so this router stays + * byte-identical to its siblings rather than forking per file. + * `useRecordEditable` initialises `allowed` to `true` and its failure path + * leaves it there, so `true` and the absent verdict the failing request + * produced are the same value at every read site — nothing below reads the + * verdict at all. + * ─────────────────────────────────────────────────────────────────────────── */ + +const EXPLAIN_ROUTE = '/api/v1/security/explain'; + +/** Every URL this file's renders handed the global `fetch`, in request order. */ +let explainCalls: string[] = []; + +/** Serve `POST /api/v1/security/explain` permissively; record everything. */ +function installExplainDouble() { + explainCalls = []; + vi.stubGlobal( + 'fetch', + vi.fn(async (input: unknown, init?: unknown) => { + const url = String( + input && typeof input === 'object' && 'url' in input ? (input as { url: unknown }).url : input, + ); + explainCalls.push(url); + if (url !== EXPLAIN_ROUTE) return { ok: false, status: 404, json: async () => ({}) }; + let body: { recordId?: unknown; recordIds?: unknown } = {}; + try { + body = JSON.parse(String((init as { body?: unknown } | undefined)?.body ?? '{}')); + } catch { + /* a non-JSON body is not a request this route can answer */ + } + const recordIds = Array.isArray(body.recordIds) ? body.recordIds : null; + return { + ok: true, + status: 200, + json: async () => + recordIds + ? { records: recordIds.map((recordId) => ({ recordId, visible: true })) } + : { record: { visible: true } }, + }; + }), + ); +} + +beforeEach(installExplainDouble); + +afterEach(() => { + // The double is a router, not a sink: an escape to any OTHER endpoint fails + // here instead of vanishing into the hook's best-effort `catch`. + expect(explainCalls.filter((url) => url !== EXPLAIN_ROUTE)).toEqual([]); + // Unmount BEFORE restoring the real `fetch`. Vitest runs `afterEach` hooks in + // reverse registration order, so this file's teardown runs before the root + // setup's RTL cleanup: unstubbing first would leave the tree mounted with the + // real global back in place, and a verdict effect settling in that window + // escapes again (objectui#7439). + cleanup(); + vi.unstubAllGlobals(); +}); describe('default `fieldGroups` detail page — synthesized section headings reach the DOM (#6241)', () => { it('renders the declared heading text of every group inside the details block', () => { diff --git a/packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx b/packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx index 7f7951ddb..54f75c123 100644 --- a/packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx +++ b/packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx @@ -34,7 +34,7 @@ * the more specific signal. */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, waitFor, cleanup } from '@testing-library/react'; import React from 'react'; import fs from 'node:fs'; @@ -96,7 +96,121 @@ function makeAdapter() { const DETAIL_SNIPPETS = guideSchemas('detail-view'); const SNIPPET = DETAIL_SNIPPETS[0]; -beforeEach(() => cleanup()); +/* ───────────────────────────────────────────────────────────────────────────── + * objectui#7307 — this file's TWO network escapes, both served here. + * + * Nothing below asks for a security verdict or for a REST record, yet every run + * opened real TCP connections to `http://localhost:3000`. Traced with a stack + * probe on the network-escape guard's attribution point (measured, not + * inferred) — this file is the only one in its batch that reaches two routes: + * + * SchemaRenderer -> the guide's `detail-view` snippet + * -> DetailView packages/plugin-detail/src/DetailView.tsx:290, :296 + * -> useRecordEditable packages/plugin-detail/src/useRecordEditable.ts:76 + * -> `const doFetch = apiFetch ?? fetch` [escape 1, 12 calls] + * POST /api/v1/security/explain (twice per render: edit, then delete) + * + * the `api`-sourced case at the bottom of this file + * -> DetailView packages/plugin-detail/src/DetailView.tsx:616 + * -> `fetch(`${schema.api}/${schema.resourceId}`)` [escape 2, 1 call] + * GET /api/task/42 + * + * `useRecordEditable` reads the host's AUTHENTICATED `apiFetch` off + * `SchemaRendererContext` and, with no host supplying one, degrades to the + * GLOBAL `fetch` by design — a standalone embed must keep rendering rather than + * crash. `DetailView`'s `api` branch has no such seam at all: it calls the + * global directly. Under happy-dom that global is a real HTTP client and the + * document URL defaults to `http://localhost:3000`, so both relative paths + * resolved to live requests. Both reads are best-effort (each failure is + * caught), which is why the cases below stayed green while the requests always + * failed. + * + * Answered from a RECORDING double — the shape objectui#5225 settled on, carried + * by `packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx` and by + * this burn-down's earlier batches (see + * `packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx`). + * Deliberately NOT a blanket network stub: it records every URL it is handed and + * `afterEach` fails on any URL outside the set it serves, so an escape to + * somewhere else reds here instead of vanishing into one of those `catch`es. + * + * What it answers, and why that changes no assertion here: + * + * - `/api/v1/security/explain` — the permissive verdict, in the two response + * shapes the two explain hooks read: `{ record: { visible } }` for a single + * `recordId` and `{ records: [{ recordId, visible }] }` for a batched + * `recordIds`. Only the first is reached from this file; the batched branch + * is kept so this router stays byte-identical to its siblings in this batch. + * `useRecordEditable` initialises `allowed` to `true` and its failure path + * leaves it there, so `true` and the absent verdict the failing request + * produced are the same value at every read site. + * - `/api/task/42` — the RECORD, which is the shape its reader consumes: + * `DetailView` does `res.json()` then `setData(result?.data || result)`. + * The one case that reaches it asserts only that the "No data source + * resolved" panel stays absent, and that panel is a WIRING gate + * (`ElementDataSourceGate`) decided before any response arrives — it is + * absent here because the block declares `api`, not because the request + * failed. Serving the record therefore exercises the success path this + * route always had, without moving any assertion. + * ─────────────────────────────────────────────────────────────────────────── */ + +const EXPLAIN_ROUTE = '/api/v1/security/explain'; + +/** `DetailView`'s `api`-sourced read — `${schema.api}/${schema.resourceId}`. */ +const RECORD_ROUTE = '/api/task/42'; + +const SERVED_ROUTES: readonly string[] = [EXPLAIN_ROUTE, RECORD_ROUTE]; + +/** Every URL this file's renders handed the global `fetch`, in request order. */ +let servedCalls: string[] = []; + +/** Serve both routes; record everything; 404 anything else. */ +function installFetchDouble() { + servedCalls = []; + vi.stubGlobal( + 'fetch', + vi.fn(async (input: unknown, init?: unknown) => { + const url = String( + input && typeof input === 'object' && 'url' in input ? (input as { url: unknown }).url : input, + ); + servedCalls.push(url); + if (url === RECORD_ROUTE) return { ok: true, status: 200, json: async () => RECORD }; + if (url !== EXPLAIN_ROUTE) return { ok: false, status: 404, json: async () => ({}) }; + let body: { recordId?: unknown; recordIds?: unknown } = {}; + try { + body = JSON.parse(String((init as { body?: unknown } | undefined)?.body ?? '{}')); + } catch { + /* a non-JSON body is not a request this route can answer */ + } + const recordIds = Array.isArray(body.recordIds) ? body.recordIds : null; + return { + ok: true, + status: 200, + json: async () => + recordIds + ? { records: recordIds.map((recordId) => ({ recordId, visible: true })) } + : { record: { visible: true } }, + }; + }), + ); +} + +beforeEach(() => { + cleanup(); + installFetchDouble(); +}); + +afterEach(() => { + // The double is a router, not a sink: an escape to any OTHER endpoint fails + // here instead of vanishing into one of the readers' best-effort `catch`es. + expect(servedCalls.filter((url) => !SERVED_ROUTES.includes(url))).toEqual([]); + // Unmount BEFORE restoring the real `fetch`. Vitest runs `afterEach` hooks in + // reverse registration order, so this file's teardown runs before the root + // setup's RTL cleanup: unstubbing first would leave the tree mounted with the + // real global back in place, and a verdict effect settling in that window + // escapes again (objectui#7439). + cleanup(); + vi.unstubAllGlobals(); +}); describe('guide/building-crud-app.md — the `detail-view` snippet actually renders', () => { it('publishes exactly one detail snippet', () => { diff --git a/packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx b/packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx index da24afa63..ffd1f58be 100644 --- a/packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx +++ b/packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx @@ -35,8 +35,8 @@ * published-surface assertions in `recordDetailsInputs.spec-parity.test.ts`. */ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/react'; import * as React from 'react'; import { RecordContextProvider } from '@object-ui/react'; import { RecordDetailsRenderer } from '../renderers/record-details'; @@ -73,6 +73,99 @@ const renderDetails = (schema: Record) => , ); +/* ───────────────────────────────────────────────────────────────────────────── + * objectui#7307 — this file's `/api/v1/security/explain` escape, served here. + * + * Nothing below asks for a security verdict, yet every run opened a REAL TCP + * connection to `http://localhost:3000`. Traced with a stack probe on the + * network-escape guard's attribution point (measured, not inferred): + * + * RecordDetailsRenderer packages/plugin-detail/src/renderers/record-details.tsx:302 + * -> DetailView packages/plugin-detail/src/DetailView.tsx:290, :296 + * -> useRecordEditable packages/plugin-detail/src/useRecordEditable.ts:76 + * -> `const doFetch = apiFetch ?? fetch` [the escape] + * POST /api/v1/security/explain (twice per render: edit, then delete) + * + * `useRecordEditable` reads the host's AUTHENTICATED `apiFetch` off + * `SchemaRendererContext` and, with no host supplying one, degrades to the + * GLOBAL `fetch` by design — a standalone embed must keep rendering rather than + * crash. Under happy-dom that global is a real HTTP client and the document URL + * defaults to `http://localhost:3000`, so the relative path resolved to a live + * request. The read is best-effort (a network or parse failure leaves the + * record editable — fail open), which is why the cases below stayed green while + * the request always failed. + * + * Answered from a RECORDING double — the shape objectui#5225 settled on, carried + * by `packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx` and by + * this burn-down's earlier batches (see + * `packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx`). + * Deliberately NOT a blanket network stub: it records every URL it is handed and + * `afterEach` fails on any URL outside the set it serves, so an escape to + * somewhere else reds here instead of vanishing into that `catch`. + * + * What it answers, and why that changes no assertion here: the permissive + * verdict, in the two response shapes the two explain hooks read — + * `{ record: { visible } }` for a single `recordId`, and + * `{ records: [{ recordId, visible }] }` for a batched `recordIds`. Only the + * FIRST is reached from this file (every call measured above comes from + * `useRecordEditable`); the batched branch is kept so this router stays + * byte-identical to its siblings rather than forking per file. + * `useRecordEditable` initialises `allowed` to `true` and its failure path + * leaves it there, so `true` and the absent verdict the failing request + * produced are the same value at every read site — nothing below reads the + * verdict at all. + * ─────────────────────────────────────────────────────────────────────────── */ + +const EXPLAIN_ROUTE = '/api/v1/security/explain'; + +/** Every URL this file's renders handed the global `fetch`, in request order. */ +let explainCalls: string[] = []; + +/** Serve `POST /api/v1/security/explain` permissively; record everything. */ +function installExplainDouble() { + explainCalls = []; + vi.stubGlobal( + 'fetch', + vi.fn(async (input: unknown, init?: unknown) => { + const url = String( + input && typeof input === 'object' && 'url' in input ? (input as { url: unknown }).url : input, + ); + explainCalls.push(url); + if (url !== EXPLAIN_ROUTE) return { ok: false, status: 404, json: async () => ({}) }; + let body: { recordId?: unknown; recordIds?: unknown } = {}; + try { + body = JSON.parse(String((init as { body?: unknown } | undefined)?.body ?? '{}')); + } catch { + /* a non-JSON body is not a request this route can answer */ + } + const recordIds = Array.isArray(body.recordIds) ? body.recordIds : null; + return { + ok: true, + status: 200, + json: async () => + recordIds + ? { records: recordIds.map((recordId) => ({ recordId, visible: true })) } + : { record: { visible: true } }, + }; + }), + ); +} + +beforeEach(installExplainDouble); + +afterEach(() => { + // The double is a router, not a sink: an escape to any OTHER endpoint fails + // here instead of vanishing into the hook's best-effort `catch`. + expect(explainCalls.filter((url) => url !== EXPLAIN_ROUTE)).toEqual([]); + // Unmount BEFORE restoring the real `fetch`. Vitest runs `afterEach` hooks in + // reverse registration order, so this file's teardown runs before the root + // setup's RTL cleanup: unstubbing first would leave the tree mounted with the + // real global back in place, and a verdict effect settling in that window + // escapes again (objectui#7439). + cleanup(); + vi.unstubAllGlobals(); +}); + describe('record:details — `sections` presence decides the body (#3818)', () => { it('renders the authored groups when `sections` is present (the old `custom`)', () => { renderDetails({ diff --git a/packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx b/packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx index 01119f524..7c86c1781 100644 --- a/packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx +++ b/packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx @@ -46,8 +46,8 @@ * same DOM nodes a translated app fills with translated labels. */ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/react'; import * as React from 'react'; import { RecordContextProvider } from '@object-ui/react'; import { RecordDetailsRenderer } from '../record-details'; @@ -85,6 +85,99 @@ const renderDetails = (schema: Record, data: Record screen.queryAllByTitle('No value'); +/* ───────────────────────────────────────────────────────────────────────────── + * objectui#7307 — this file's `/api/v1/security/explain` escape, served here. + * + * Nothing below asks for a security verdict, yet every run opened a REAL TCP + * connection to `http://localhost:3000`. Traced with a stack probe on the + * network-escape guard's attribution point (measured, not inferred): + * + * RecordDetailsRenderer packages/plugin-detail/src/renderers/record-details.tsx:302 + * -> DetailView packages/plugin-detail/src/DetailView.tsx:290, :296 + * -> useRecordEditable packages/plugin-detail/src/useRecordEditable.ts:76 + * -> `const doFetch = apiFetch ?? fetch` [the escape] + * POST /api/v1/security/explain (twice per render: edit, then delete) + * + * `useRecordEditable` reads the host's AUTHENTICATED `apiFetch` off + * `SchemaRendererContext` and, with no host supplying one, degrades to the + * GLOBAL `fetch` by design — a standalone embed must keep rendering rather than + * crash. Under happy-dom that global is a real HTTP client and the document URL + * defaults to `http://localhost:3000`, so the relative path resolved to a live + * request. The read is best-effort (a network or parse failure leaves the + * record editable — fail open), which is why the cases below stayed green while + * the request always failed. + * + * Answered from a RECORDING double — the shape objectui#5225 settled on, carried + * by `packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx` and by + * this burn-down's earlier batches (see + * `packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx`). + * Deliberately NOT a blanket network stub: it records every URL it is handed and + * `afterEach` fails on any URL outside the set it serves, so an escape to + * somewhere else reds here instead of vanishing into that `catch`. + * + * What it answers, and why that changes no assertion here: the permissive + * verdict, in the two response shapes the two explain hooks read — + * `{ record: { visible } }` for a single `recordId`, and + * `{ records: [{ recordId, visible }] }` for a batched `recordIds`. Only the + * FIRST is reached from this file (every call measured above comes from + * `useRecordEditable`); the batched branch is kept so this router stays + * byte-identical to its siblings rather than forking per file. + * `useRecordEditable` initialises `allowed` to `true` and its failure path + * leaves it there, so `true` and the absent verdict the failing request + * produced are the same value at every read site — nothing below reads the + * verdict at all. + * ─────────────────────────────────────────────────────────────────────────── */ + +const EXPLAIN_ROUTE = '/api/v1/security/explain'; + +/** Every URL this file's renders handed the global `fetch`, in request order. */ +let explainCalls: string[] = []; + +/** Serve `POST /api/v1/security/explain` permissively; record everything. */ +function installExplainDouble() { + explainCalls = []; + vi.stubGlobal( + 'fetch', + vi.fn(async (input: unknown, init?: unknown) => { + const url = String( + input && typeof input === 'object' && 'url' in input ? (input as { url: unknown }).url : input, + ); + explainCalls.push(url); + if (url !== EXPLAIN_ROUTE) return { ok: false, status: 404, json: async () => ({}) }; + let body: { recordId?: unknown; recordIds?: unknown } = {}; + try { + body = JSON.parse(String((init as { body?: unknown } | undefined)?.body ?? '{}')); + } catch { + /* a non-JSON body is not a request this route can answer */ + } + const recordIds = Array.isArray(body.recordIds) ? body.recordIds : null; + return { + ok: true, + status: 200, + json: async () => + recordIds + ? { records: recordIds.map((recordId) => ({ recordId, visible: true })) } + : { record: { visible: true } }, + }; + }), + ); +} + +beforeEach(installExplainDouble); + +afterEach(() => { + // The double is a router, not a sink: an escape to any OTHER endpoint fails + // here instead of vanishing into the hook's best-effort `catch`. + expect(explainCalls.filter((url) => url !== EXPLAIN_ROUTE)).toEqual([]); + // Unmount BEFORE restoring the real `fetch`. Vitest runs `afterEach` hooks in + // reverse registration order, so this file's teardown runs before the root + // setup's RTL cleanup: unstubbing first would leave the tree mounted with the + // real global back in place, and a verdict effect settling in that window + // escapes again (objectui#7439). + cleanup(); + vi.unstubAllGlobals(); +}); + describe('record:details — the UNAUTHORED empty-section default is DetailSection\'s heuristic (#7064)', () => { it('an ALL-empty section renders its skeleton: heading, every field label, an empty placeholder each', () => { renderDetails({ diff --git a/scripts/__tests__/network-escape-ledger.test.ts b/scripts/__tests__/network-escape-ledger.test.ts index 623adf3f8..8b7a81149 100644 --- a/scripts/__tests__/network-escape-ledger.test.ts +++ b/scripts/__tests__/network-escape-ledger.test.ts @@ -47,10 +47,6 @@ const PINNED_LEDGER: readonly string[] = [ 'packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx', 'packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx', 'packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx', - 'packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx', - 'packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx', - 'packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx', - 'packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx', ]; describe('network-escape ledger (objectui#6640) is shrink-only', () => { diff --git a/vitest.setup.network-escape-guard.ts b/vitest.setup.network-escape-guard.ts index 4a500c910..347ed78bb 100644 --- a/vitest.setup.network-escape-guard.ts +++ b/vitest.setup.network-escape-guard.ts @@ -129,14 +129,6 @@ export const KNOWN_ESCAPES: ReadonlySet = new Set([ 'packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx', // /api/v1/ai/conversations 'packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx', - // /api/v1/security/explain - 'packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx', - // /api/task/42, /api/v1/security/explain - 'packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx', - // /api/v1/security/explain - 'packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx', - // /api/v1/security/explain - 'packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx', ]); type Escape = { file: string; test: string; url: string };