diff --git a/.changeset/vi-mock-inherit-slice8-permissions.md b/.changeset/vi-mock-inherit-slice8-permissions.md new file mode 100644 index 0000000000..bccdff0367 --- /dev/null +++ b/.changeset/vi-mock-inherit-slice8-permissions.md @@ -0,0 +1,7 @@ +--- +--- + +Test-only change: 24 `vi.mock('@object-ui/permissions', ...)` factories now inherit +the real module's export surface instead of freezing a hand-listed one, and the +specifier joins `COVERED_SPECIFIERS` in `scripts/check-vi-mock-inherit.mjs`. No +published behaviour changes. diff --git a/packages/app-shell/src/layout/__tests__/AppSidebar.derivedAreaVisibility.test.tsx b/packages/app-shell/src/layout/__tests__/AppSidebar.derivedAreaVisibility.test.tsx index 5b7b3e402c..6e145e4c3d 100644 --- a/packages/app-shell/src/layout/__tests__/AppSidebar.derivedAreaVisibility.test.tsx +++ b/packages/app-shell/src/layout/__tests__/AppSidebar.derivedAreaVisibility.test.tsx @@ -55,9 +55,13 @@ let permissionsState: { can: (objectName: string, action: string) => boolean; hasCapabilities: (caps: string[]) => boolean; }; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => permissionsState, -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => permissionsState, + }; +}); let metadataState: { apps: unknown[]; objects: unknown[] }; vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/layout/__tests__/UnifiedSidebar.derivedAreaVisibility.test.tsx b/packages/app-shell/src/layout/__tests__/UnifiedSidebar.derivedAreaVisibility.test.tsx index 1a07a38bd2..af91b0461c 100644 --- a/packages/app-shell/src/layout/__tests__/UnifiedSidebar.derivedAreaVisibility.test.tsx +++ b/packages/app-shell/src/layout/__tests__/UnifiedSidebar.derivedAreaVisibility.test.tsx @@ -54,9 +54,13 @@ let permissionsState: { can: (objectName: string, action: string) => boolean; hasCapabilities: (caps: string[]) => boolean; }; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => permissionsState, -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => permissionsState, + }; +}); let metadataState: { apps: unknown[]; objects: unknown[] }; vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/layout/__tests__/appSidebarSettingsTargets.test.tsx b/packages/app-shell/src/layout/__tests__/appSidebarSettingsTargets.test.tsx index 9cf1adf4e1..469143474b 100644 --- a/packages/app-shell/src/layout/__tests__/appSidebarSettingsTargets.test.tsx +++ b/packages/app-shell/src/layout/__tests__/appSidebarSettingsTargets.test.tsx @@ -108,9 +108,13 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ getUserInitials: () => 'U', })); -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), + }; +}); /** The zero-app deployment both senders exist for. */ vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/layout/__tests__/systemNavDatasourcesHop.test.tsx b/packages/app-shell/src/layout/__tests__/systemNavDatasourcesHop.test.tsx index e46f3ff094..bc64ed5f4d 100644 --- a/packages/app-shell/src/layout/__tests__/systemNavDatasourcesHop.test.tsx +++ b/packages/app-shell/src/layout/__tests__/systemNavDatasourcesHop.test.tsx @@ -81,9 +81,13 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ getUserInitials: () => 'U', })); -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), + }; +}); /** The zero-app deployment `systemFallbackNavigation` exists for. */ vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/layout/__tests__/systemNavObjectsHop.test.tsx b/packages/app-shell/src/layout/__tests__/systemNavObjectsHop.test.tsx index 8d986a6e91..5f1b3f5bdc 100644 --- a/packages/app-shell/src/layout/__tests__/systemNavObjectsHop.test.tsx +++ b/packages/app-shell/src/layout/__tests__/systemNavObjectsHop.test.tsx @@ -97,9 +97,13 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ getUserInitials: () => 'U', })); -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), + }; +}); /** The zero-app deployment `systemFallbackNavigation` exists for. */ vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/layout/__tests__/systemNavSettingsTarget.test.tsx b/packages/app-shell/src/layout/__tests__/systemNavSettingsTarget.test.tsx index b27cf6b551..33fb1e062e 100644 --- a/packages/app-shell/src/layout/__tests__/systemNavSettingsTarget.test.tsx +++ b/packages/app-shell/src/layout/__tests__/systemNavSettingsTarget.test.tsx @@ -112,9 +112,13 @@ vi.mock('@object-ui/auth', async (importOriginal) => ({ getUserInitials: () => 'U', })); -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ can: () => true, hasCapabilities: () => true }), + }; +}); /** The zero-app deployment this whole screen exists for. */ vi.mock('../../providers/MetadataProvider', () => ({ diff --git a/packages/app-shell/src/views/ObjectDataPage.createAffordances.test.tsx b/packages/app-shell/src/views/ObjectDataPage.createAffordances.test.tsx index a9fd234eb9..2281f7dd04 100644 --- a/packages/app-shell/src/views/ObjectDataPage.createAffordances.test.tsx +++ b/packages/app-shell/src/views/ObjectDataPage.createAffordances.test.tsx @@ -62,21 +62,25 @@ let principal: Record = {}; /** Controllable server-resolved effective API operations (#3391); `undefined` = unrestricted. */ let apiOperations: string[] | undefined; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => apiOperations, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: (_object: string, action: string) => principal[action] ?? true, - cannot: (_object: string, action: string) => !(principal[action] ?? true), - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => apiOperations, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: (_object: string, action: string) => principal[action] ?? true, + cannot: (_object: string, action: string) => !(principal[action] ?? true), + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.chartConfigForward-7891.test.tsx b/packages/app-shell/src/views/ObjectView.chartConfigForward-7891.test.tsx index 3fd7f18fa0..93448bf7ad 100644 --- a/packages/app-shell/src/views/ObjectView.chartConfigForward-7891.test.tsx +++ b/packages/app-shell/src/views/ObjectView.chartConfigForward-7891.test.tsx @@ -71,21 +71,25 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.chartRelay-7823.test.tsx b/packages/app-shell/src/views/ObjectView.chartRelay-7823.test.tsx index 741302ed58..dbe1f8eb85 100644 --- a/packages/app-shell/src/views/ObjectView.chartRelay-7823.test.tsx +++ b/packages/app-shell/src/views/ObjectView.chartRelay-7823.test.tsx @@ -90,21 +90,25 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.createPredicates.test.tsx b/packages/app-shell/src/views/ObjectView.createPredicates.test.tsx index 4e1b431d57..1a70880203 100644 --- a/packages/app-shell/src/views/ObjectView.createPredicates.test.tsx +++ b/packages/app-shell/src/views/ObjectView.createPredicates.test.tsx @@ -54,21 +54,25 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom'; /** Controllable principal verdict, keyed by action (`can()` is permissive by default). */ let principal: Record = {}; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: (_object: string, action: string) => principal[action] ?? true, - cannot: (_object: string, action: string) => !(principal[action] ?? true), - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: (_object: string, action: string) => principal[action] ?? true, + cannot: (_object: string, action: string) => !(principal[action] ?? true), + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.importPredicates.test.tsx b/packages/app-shell/src/views/ObjectView.importPredicates.test.tsx index cbcd0add68..46c1c0e34a 100644 --- a/packages/app-shell/src/views/ObjectView.importPredicates.test.tsx +++ b/packages/app-shell/src/views/ObjectView.importPredicates.test.tsx @@ -45,21 +45,25 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom'; /** Controllable principal verdict, keyed by action (`can()` is permissive by default). */ let principal: Record = {}; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: (_object: string, action: string) => principal[action] ?? true, - cannot: (_object: string, action: string) => !(principal[action] ?? true), - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: (_object: string, action: string) => principal[action] ?? true, + cannot: (_object: string, action: string) => !(principal[action] ?? true), + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.objectBoundActions-7234.test.tsx b/packages/app-shell/src/views/ObjectView.objectBoundActions-7234.test.tsx index 49161ded47..e836bb1087 100644 --- a/packages/app-shell/src/views/ObjectView.objectBoundActions-7234.test.tsx +++ b/packages/app-shell/src/views/ObjectView.objectBoundActions-7234.test.tsx @@ -76,22 +76,26 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom'; */ let heldCapabilities: string[] | undefined; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - systemPermissions: heldCapabilities, - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: true, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + systemPermissions: heldCapabilities, + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: true, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.rowColorRelay-7218.test.tsx b/packages/app-shell/src/views/ObjectView.rowColorRelay-7218.test.tsx index 90dcef5a65..1dc8587b52 100644 --- a/packages/app-shell/src/views/ObjectView.rowColorRelay-7218.test.tsx +++ b/packages/app-shell/src/views/ObjectView.rowColorRelay-7218.test.tsx @@ -86,21 +86,25 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx b/packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx index 48a9a12417..9e990c94fa 100644 --- a/packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx +++ b/packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsx @@ -71,21 +71,25 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/ObjectView.viewDescriptionRelay-7199.test.tsx b/packages/app-shell/src/views/ObjectView.viewDescriptionRelay-7199.test.tsx index f173082b46..22b6bde068 100644 --- a/packages/app-shell/src/views/ObjectView.viewDescriptionRelay-7199.test.tsx +++ b/packages/app-shell/src/views/ObjectView.viewDescriptionRelay-7199.test.tsx @@ -67,21 +67,25 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, cleanup, waitFor } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - check: () => ({ allowed: true }), - checkField: () => true, - getFieldPermissions: () => [], - getRowFilter: () => undefined, - getObjectApiOperations: () => undefined, - roles: [], - isLoaded: false, - hasCapabilities: () => true, - can: () => true, - cannot: () => false, - }), - useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + check: () => ({ allowed: true }), + checkField: () => true, + getFieldPermissions: () => [], + getRowFilter: () => undefined, + getObjectApiOperations: () => undefined, + roles: [], + isLoaded: false, + hasCapabilities: () => true, + can: () => true, + cannot: () => false, + }), + useFieldPermissions: () => ({ canRead: () => true, canWrite: () => true, permissions: [] }), + }; +}); vi.mock('@object-ui/auth', async (importOriginal) => ({ ...(await importOriginal>()), diff --git a/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.createPredicates.test.tsx b/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.createPredicates.test.tsx index 72ef7fdc0c..9d23dbfa5f 100644 --- a/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.createPredicates.test.tsx +++ b/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.createPredicates.test.tsx @@ -32,12 +32,16 @@ import React from 'react'; // default to allowed, matching `can()`'s permissive fallback. let principal: Record = {}; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - getObjectApiOperations: () => undefined, - can: (_name: string, action: string) => principal[action] ?? true, - }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + getObjectApiOperations: () => undefined, + can: (_name: string, action: string) => principal[action] ?? true, + }), + }; +}); import { RelatedRecordActionsBridge } from '../RelatedRecordActionsBridge'; import { useRelatedRecordActions } from '@object-ui/react'; diff --git a/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.effectiveOps.test.tsx b/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.effectiveOps.test.tsx index 53c9f387b3..6ad93cd52a 100644 --- a/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.effectiveOps.test.tsx +++ b/packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.effectiveOps.test.tsx @@ -32,12 +32,16 @@ let effectiveOps: Record = {}; // entries default to allowed, matching `can()`'s permissive fallback. let principal: Record = {}; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - getObjectApiOperations: (name: string) => effectiveOps[name], - can: (_name: string, action: string) => principal[action] ?? true, - }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + getObjectApiOperations: (name: string) => effectiveOps[name], + can: (_name: string, action: string) => principal[action] ?? true, + }), + }; +}); import { RelatedRecordActionsBridge } from '../RelatedRecordActionsBridge'; import { useRelatedRecordActions } from '@object-ui/react'; diff --git a/packages/plugin-detail/src/renderers/__tests__/record-details.effectiveOps.test.tsx b/packages/plugin-detail/src/renderers/__tests__/record-details.effectiveOps.test.tsx index 4d0df0a450..1c9034eff4 100644 --- a/packages/plugin-detail/src/renderers/__tests__/record-details.effectiveOps.test.tsx +++ b/packages/plugin-detail/src/renderers/__tests__/record-details.effectiveOps.test.tsx @@ -49,13 +49,17 @@ vi.mock('@object-ui/react', async (importOriginal) => ({ }), })); -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - can: () => true, - getObjectApiOperations: () => stub.effectiveOps, - }), - useFieldPermissions: () => ({ readableFields: (names: string[]) => names }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + can: () => true, + getObjectApiOperations: () => stub.effectiveOps, + }), + useFieldPermissions: () => ({ readableFields: (names: string[]) => names }), + }; +}); // Capture the schema the renderer synthesizes — `inlineEdit` is the gate under // test. Stubbing DetailView also keeps the suite off the heavy field/registry diff --git a/packages/plugin-detail/src/renderers/__tests__/record-details.test.tsx b/packages/plugin-detail/src/renderers/__tests__/record-details.test.tsx index 7f9a8fb720..22240a3e0d 100644 --- a/packages/plugin-detail/src/renderers/__tests__/record-details.test.tsx +++ b/packages/plugin-detail/src/renderers/__tests__/record-details.test.tsx @@ -47,9 +47,11 @@ vi.mock('@object-ui/react', async (importOriginal) => { }; }); -vi.mock('@object-ui/permissions', async () => { +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); const React = await import('react'); return { + ...actual, usePermissions: () => (React.useRef(0), { can: () => stub.can }), useFieldPermissions: (_objectName: string) => (React.useRef(0), { readableFields: (names: string[]) => names, diff --git a/packages/plugin-form/src/ObjectForm.effectiveOps.test.tsx b/packages/plugin-form/src/ObjectForm.effectiveOps.test.tsx index fd06aa501b..f038a11a2e 100644 --- a/packages/plugin-form/src/ObjectForm.effectiveOps.test.tsx +++ b/packages/plugin-form/src/ObjectForm.effectiveOps.test.tsx @@ -50,7 +50,13 @@ const { permsStub, state } = vi.hoisted(() => { }); // `usePermissions` is the only binding this module graph takes from the package. -vi.mock('@object-ui/permissions', () => ({ usePermissions: () => permsStub })); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => permsStub, + }; +}); import { ObjectForm } from './ObjectForm'; import { registerAllFields } from '@object-ui/fields'; diff --git a/packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx b/packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx index 50d583449f..827316231c 100644 --- a/packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx +++ b/packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx @@ -44,14 +44,18 @@ import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/re import '@testing-library/jest-dom'; import React from 'react'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - isLoaded: false, - checkField: () => true, - getObjectApiOperations: () => undefined, - can: () => true, - }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + isLoaded: false, + checkField: () => true, + getObjectApiOperations: () => undefined, + can: () => true, + }), + }; +}); import { ObjectGrid } from '../ObjectGrid'; import { registerAllFields } from '@object-ui/fields'; diff --git a/packages/plugin-grid/src/__tests__/bulkFanoutHostParams.test.tsx b/packages/plugin-grid/src/__tests__/bulkFanoutHostParams.test.tsx index b2f63b89c4..0f4dc8ef96 100644 --- a/packages/plugin-grid/src/__tests__/bulkFanoutHostParams.test.tsx +++ b/packages/plugin-grid/src/__tests__/bulkFanoutHostParams.test.tsx @@ -44,14 +44,18 @@ import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-li import '@testing-library/jest-dom'; import React from 'react'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ - isLoaded: false, - checkField: () => true, - getObjectApiOperations: () => undefined, - can: () => true, - }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ + isLoaded: false, + checkField: () => true, + getObjectApiOperations: () => undefined, + can: () => true, + }), + }; +}); import { ObjectGrid } from '../ObjectGrid'; import { registerAllFields } from '@object-ui/fields'; diff --git a/packages/plugin-grid/src/__tests__/legacyRowActionDispatch.test.tsx b/packages/plugin-grid/src/__tests__/legacyRowActionDispatch.test.tsx index 1e89553ea9..17fdef3ee3 100644 --- a/packages/plugin-grid/src/__tests__/legacyRowActionDispatch.test.tsx +++ b/packages/plugin-grid/src/__tests__/legacyRowActionDispatch.test.tsx @@ -29,9 +29,13 @@ import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import React from 'react'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ isLoaded: false, checkField: () => true, getObjectApiOperations: () => undefined, can: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ isLoaded: false, checkField: () => true, getObjectApiOperations: () => undefined, can: () => true }), + }; +}); import { ObjectGrid } from '../ObjectGrid'; import { registerAllFields } from '@object-ui/fields'; diff --git a/packages/plugin-grid/src/__tests__/objectBulkActionDispatch.test.tsx b/packages/plugin-grid/src/__tests__/objectBulkActionDispatch.test.tsx index fff272f408..6e1ee327b0 100644 --- a/packages/plugin-grid/src/__tests__/objectBulkActionDispatch.test.tsx +++ b/packages/plugin-grid/src/__tests__/objectBulkActionDispatch.test.tsx @@ -32,9 +32,13 @@ import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/re import '@testing-library/jest-dom'; import React from 'react'; -vi.mock('@object-ui/permissions', () => ({ - usePermissions: () => ({ isLoaded: false, checkField: () => true, getObjectApiOperations: () => undefined, can: () => true }), -})); +vi.mock('@object-ui/permissions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + usePermissions: () => ({ isLoaded: false, checkField: () => true, getObjectApiOperations: () => undefined, can: () => true }), + }; +}); import { ObjectGrid } from '../ObjectGrid'; import { registerAllFields } from '@object-ui/fields'; diff --git a/scripts/check-vi-mock-inherit.mjs b/scripts/check-vi-mock-inherit.mjs index b3a65d01c0..6829fbaff0 100644 --- a/scripts/check-vi-mock-inherit.mjs +++ b/scripts/check-vi-mock-inherit.mjs @@ -82,7 +82,7 @@ * - **Workspace specifiers not in `COVERED_SPECIFIERS`.** See below. * * `COVERED_SPECIFIERS` holds the workspace packages whose frozen sites have - * actually been SWEPT to zero. Today that is twelve, and each joined by sweep + * actually been SWEPT to zero. Today that is thirteen, and each joined by sweep * rather than by judgement. Running this file's classifier over all 1,499 * `vi.mock` call sites in the tree at `9ce20233f`: * @@ -406,11 +406,66 @@ * `@object-ui/components`. Inheriting a barrel is cheap once its own * dependencies are already loaded in that file. * - * The remaining 98 stay on objectui#6892: `@object-ui/permissions` (24), - * `@object-ui/app-shell` (23, ALL frozen, still only after objectui#6580 -- - * which is now CLOSED, so that reading is a git-history read rather than an - * open card), `@object-ui/plugin-detail` (13) and `@object-ui/plugin-chatbot` - * (11). + * `@object-ui/permissions` joined as objectui#6892's EIGHTH slice, re-derived + * on `571b4870d` by the same `scan()` method, the constant below again never + * widened-and-reverted: + * + * @object-ui/permissions 48 judged, 24 inheriting, 24 frozen -> 0 + * + * with the population moving 98 -> 74 frozen over 659 judged and no site moving + * the other way -- every other one of the 21 rows byte-identical between the + * two runs. The 24 frozen sites sit in four owning packages (17 under + * `packages/app-shell`, 4 under `packages/plugin-grid`, 2 under + * `packages/plugin-detail`, 1 under `packages/plugin-form`) and in two + * syntactic shapes: 23 zero-parameter object-literal arrows and one that was + * ALREADY `async` and still frozen -- it awaited `react`, not the module under + * mock, which is the shape a name-matching gate waves through. + * + * STEP 0 was taken again rather than inherited, and this barrel is the SMALLEST + * yet measured on this worklist and the first to come back genuinely INERT + * since `@object-ui/collaboration`: 27 modules and 422 module-scope statements + * reached from `packages/permissions/src/index.ts` -- the package plus + * `@object-ui/types`, and nothing else in the workspace. It is a 43-line + * re-export-only barrel, and a verdict on one of those is NOT free: what it + * re-exports is what runs. Of the 422 statements exactly 18 execute anything, + * and every one is allocation: ten `createDiscardProofCache()` calls (a + * `new WeakMap` plus a returned closure -- read at the definition, not + * assumed), one `createContext(null)` with its `displayName` assignment, three + * `new Set` of literals, one `Object.freeze` of a literal, one `new WeakMap`, + * and one `Symbol.for('objectui.inflightGet')`. That last one is the only + * effect that leaves the module, and it is the global SYMBOL REGISTRY rather + * than a global property: interning is idempotent, stores no value, and the + * empirical run confirms it puts nothing on `globalThis` (the probe's own + * `getOwnPropertySymbols(globalThis)` does not contain it). ZERO + * `ComponentRegistry.register` calls -- the first swept barrel with none -- + * ZERO timers, globals, storage, `fetch`, connections, side-effect-only + * imports and CSS. Two column-anchored greps over all 27 modules agree with the + * walk EXACTLY, with no disagreement to record this time. Empirically: + * importing the real barrel under happy-dom in the light `dom` project exports + * 7 names, costs ~0.47s and emits ZERO `console.warn` and ZERO `console.error`. + * + * ⭐ The free confirmation is back, and it is the strongest this worklist has + * had: 24 of the 48 sites -- exactly half, spread over TWELVE packages -- + * already inherited the real barrel on `main` and passed, so the real module + * was known to load in the environment before anything was converted. All 48 + * sites are `.test.tsx` and NONE is in `heavyDomTests`, so all 48 run in the + * single light `dom` project and one environment is the whole answer. + * + * ⭐ Slice 6's collection-death class did NOT fire, and the neighbour reading + * says why in advance rather than after the fact. The 24 files carry 8 frozen + * `sonner` factories and 7 frozen `@object-ui/plugin-list` ones -- the exact + * shape that killed 15 files in slice 6 -- but this barrel's graph reaches + * NEITHER, because it reaches nothing beyond `@object-ui/types`, `react` and + * `@objectstack/spec`. A frozen neighbour is dangerous only when the newly-real + * module's graph reaches it; walk the graph, then read the neighbours against + * it. + * + * The remaining 74 stay on objectui#6892: `@object-ui/app-shell` (23, ALL + * frozen, still only after objectui#6580 -- which is now CLOSED, so that + * reading is a git-history read rather than an open card), + * `@object-ui/plugin-detail` (13), `@object-ui/plugin-chatbot` (11), + * `@object-ui/plugin-designer` (10), `@object-ui/plugin-list` (9) and + * `@object-ui/fields` (8). * * **The precondition for widening is a sweep, not a judgement.** Convert a * specifier's frozen factories to the inheriting form, confirm this gate reads @@ -507,6 +562,7 @@ export const COVERED_SPECIFIERS = Object.freeze([ '@object-ui/plugin-form', '@object-ui/components', '@object-ui/plugin-grid', + '@object-ui/permissions', ]); /** Files the walk reads at all. */