Skip to content

Commit 24641d6

Browse files
test(plugin-calendar): partial-mock @object-ui/react so both vitest configs agree (#3219) (#3241)
`registration.test.tsx` whole-module-mocked `@object-ui/react` with just `useSchemaContext` + `SchemaRendererContext`. That made the file's result depend on how the module graph resolved, so the repo's two vitest configs disagreed about it: - root `vitest.config.mts` (CI): the file is in `heavyDomTests`, and `vitest.setup.dom.tsx` eagerly imports `@object-ui/components`, which evaluates `related-count-store.ts` (imports `subscribeDataChanges` from `@object-ui/react`) against the REAL module before the mock applies — green by accident. - `packages/plugin-calendar/vitest.config.ts` (`pnpm --filter … test`, `turbo run test`): no such setup, so `@object-ui/components` is first evaluated inside the mocked graph. Vitest 4 hard-errors on a missing export, so the suite failed to load at all. Switch to an `importOriginal` partial mock: the mock is now a superset of the real module under either resolution, so a transitive consumer can never trip over an export this test never intended to replace. Add a regression guard asserting the mock exposes every real export. It compares export SETS rather than naming `subscribeDataChanges`, so adding a new `@object-ui/react` export cannot re-arm the same trap. Verified to fail on the root path — the path where the original defect was invisible. Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa Co-authored-by: Claude <noreply@anthropic.com>
1 parent ca58e45 commit 24641d6

1 file changed

Lines changed: 57 additions & 9 deletions

File tree

packages/plugin-calendar/src/registration.test.tsx

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,43 @@
11
import { describe, it, expect, vi } from 'vitest';
22
import { render, screen } from '@testing-library/react';
33
import React from 'react';
4+
import * as ObjectUIReact from '@object-ui/react';
45
import { ObjectCalendarRenderer } from './index';
56

6-
// Mock dependencies
7-
vi.mock('@object-ui/react', async () => {
8-
const React = await import('react');
9-
return {
10-
useSchemaContext: vi.fn(() => ({ dataSource: { type: 'mock-datasource' } })),
11-
SchemaRendererContext: React.createContext(null),
12-
};
13-
});
7+
// Partial mock — override ONLY what this test controls, keep every other real
8+
// export (objectui#3219).
9+
//
10+
// A whole-module `vi.mock('@object-ui/react', () => ({ ... }))` used to list
11+
// just `useSchemaContext` + `SchemaRendererContext`. That made the file's
12+
// result depend on HOW the module graph resolved, so the two vitest configs in
13+
// this repo disagreed about it:
14+
//
15+
// - root `vitest.config.mts` (what CI runs): this file is in `heavyDomTests`,
16+
// and `vitest.setup.dom.tsx` eagerly imports `@object-ui/components`. That
17+
// evaluates `components/src/hooks/related-count-store.ts` — which imports
18+
// `subscribeDataChanges` from `@object-ui/react` — against the REAL module
19+
// before the mock applies. Green, by accident.
20+
// - `packages/plugin-calendar/vitest.config.ts` (what `pnpm --filter … test`
21+
// and `turbo run test` run): no such setup, so `@object-ui/components` is
22+
// first evaluated inside the mocked graph. Vitest 4 hard-errors on a
23+
// missing export instead of silently yielding `undefined`, so the suite
24+
// failed to load at all: `No "subscribeDataChanges" export is defined on
25+
// the "@object-ui/react" mock`.
26+
//
27+
// Spreading `importOriginal()` removes the sensitivity: the mock is a superset
28+
// of the real module under either resolution, so a transitive consumer can
29+
// never trip over an export this test never intended to replace. Adding a new
30+
// `@object-ui/react` export can no longer break this file.
31+
vi.mock(import('@object-ui/react'), async (importOriginal) => ({
32+
...(await importOriginal()),
33+
// Only the pieces this test drives:
34+
useSchemaContext: vi.fn(() => ({ dataSource: { type: 'mock-datasource' } })),
35+
}));
1436

15-
// Mock the implementation
37+
// Mock the implementation. Deliberate whole-module replacement of a LOCAL
38+
// module: stubbing `ObjectCalendar` is the isolation boundary this test is
39+
// about, and `./ObjectCalendar`'s only runtime export is the component itself
40+
// (`ObjectCalendarProps` is type-only and erased at runtime).
1641
vi.mock('./ObjectCalendar', () => ({
1742
ObjectCalendar: ({ dataSource, data, loading }: any) => (
1843
<div data-testid="calendar-mock">
@@ -42,4 +67,27 @@ describe('Plugin Calendar Registration', () => {
4267
expect(el).toHaveTextContent('Data: [{"id":1,"name":"Event A"}]');
4368
expect(el).toHaveTextContent('Loading: false');
4469
});
70+
71+
// Regression guard for objectui#3219 — keep this test.
72+
//
73+
// It pins the exact invariant whose absence made the root config and the
74+
// package config disagree about this file: the `@object-ui/react` mock must
75+
// expose every export the real module has. When that holds, no transitive
76+
// importer of `@object-ui/react` can hit a missing export, so the file
77+
// behaves identically no matter which config resolved the module (source via
78+
// the root alias, or `dist` via the package config) and no matter whether a
79+
// setup file happened to pre-load the consumer first.
80+
//
81+
// Deliberately compares export SETS rather than naming `subscribeDataChanges`:
82+
// naming the one export that broke would just re-arm the same trap for the
83+
// next export somebody adds.
84+
it('mocks @object-ui/react as a superset of the real module (pins both run paths to the same result)', async () => {
85+
const actual = await vi.importActual<typeof ObjectUIReact>('@object-ui/react');
86+
87+
const missing = Object.keys(actual)
88+
.filter((name) => !(name in ObjectUIReact))
89+
.sort();
90+
91+
expect(missing).toEqual([]);
92+
});
4593
});

0 commit comments

Comments
 (0)