Mount the @data harness inside Refine, and stop the bundle leaking globals

The harness builds its bundles with a classic script tag, so an unwrapped
bundle publishes every top-level declaration as a global. Bundling Refine
brought in @tanstack/query-core, whose exported MutationObserver class then
shadowed the DOM one for Playwright's injected script, and every
waitForFunction died on defaultMutationOptions. --format=iife keeps the
bundle's declarations to itself.

The harness also mounted the data context outside <Refine> and called the
previous bootstrapWallet signature; it now mirrors the app's provider order
and seeds through the context, so tests and app write events by one path.
This commit is contained in:
Sylvain Duchesne
2026-09-21 12:13:12 +02:00
parent 33d54c6e3e
commit d237753916
2 changed files with 32 additions and 8 deletions
+13 -2
View File
@@ -325,13 +325,24 @@ async function ensureAuth(): Promise<void> {
BeforeAll({ timeout: 10 * 60 * 1000 }, async function () {
console.log('Starting Festipod BDD tests...');
// `--format=iife` IS LOad-BEARING, not tidiness. Both bundles are loaded as
// CLASSIC scripts (`<script src="/harness.js">` for the NG harness,
// `addScriptTag({path})` for the mock one), so every top-level declaration in
// an unwrapped bundle becomes a GLOBAL binding. `@tanstack/query-core` — pulled
// in by Refine — exports a class named `MutationObserver`, which then shadows
// the DOM's for anything that runs afterwards in that page. Playwright's own
// injected script does `new MutationObserver(...)`, so it got TanStack's and
// every `waitForFunction` died with "defaultMutationOptions is not a function".
// The IIFE wrapper keeps the bundle's declarations to itself; `window.__testData`
// is still published explicitly, so the bridge is unaffected.
// Build the mock harness (always needed for non-@data or fallback)
execSync(`bun build ${HARNESS_ENTRY} --outfile ${HARNESS_OUT} --bundle`, { stdio: 'pipe' });
execSync(`bun build ${HARNESS_ENTRY} --outfile ${HARNESS_OUT} --bundle --format=iife`, { stdio: 'pipe' });
console.log(`[Harness] Built mock (${(fs.statSync(HARNESS_OUT).size / 1024).toFixed(0)} KB)`);
// Try to build the real broker harness
try {
execSync(`bun build ${HARNESS_NG_ENTRY} --outfile ${HARNESS_NG_OUT} --bundle`, { stdio: 'pipe' });
execSync(`bun build ${HARNESS_NG_ENTRY} --outfile ${HARNESS_NG_OUT} --bundle --format=iife`, { stdio: 'pipe' });
console.log(`[Harness] Built NG (${(fs.statSync(HARNESS_NG_OUT).size / 1024).toFixed(0)} KB)`);
// Ensure wallet exists in persistent profile (opens browser if needed)
+19 -6
View File
@@ -10,6 +10,7 @@
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { NextGraphProvider, useNextGraph } from '../context/NextGraphContext';
import { RefineProvider } from '../refine/RefineProvider';
import { FestipodDataProvider, useFestipodData } from '../context/FestipodDataContext';
import { useShape } from '@ng-org/orm/react';
import { ng } from '@ng-org/web';
@@ -21,7 +22,6 @@ import {
} from '../shapes/orm/festipodShapes.shapeTypes';
import type { FpEvent, FpUserProfile, FpParticipation } from '../shapes/orm/festipodShapes.typings';
import { seedEvents, seedUsers, seedParticipations } from '../data/seedData';
import { bootstrapWallet } from '../utils/ngBootstrap';
import { ensureGraphNuri } from '../utils/ngGraph';
// ============================================================================
@@ -31,9 +31,13 @@ import { ensureGraphNuri } from '../utils/ngGraph';
function DataHarnessNG() {
return (
<NextGraphProvider>
<FestipodDataProvider>
<HarnessRouter />
</FestipodDataProvider>
{/* Same order as the real app (app/App.tsx): the data context reads events
through Refine's hooks, so it cannot mount outside <Refine>. */}
<RefineProvider>
<FestipodDataProvider>
<HarnessRouter />
</FestipodDataProvider>
</RefineProvider>
</NextGraphProvider>
);
}
@@ -148,9 +152,18 @@ function ConnectedHarness() {
}
},
/** Load the app's default seed data into the wallet */
/**
* Load the app's default seed data into the wallet.
*
* Delegates to the data context instead of calling `bootstrapWallet`
* directly: seeding events now goes through Refine's `create` (one
* document per event), and the callback that does it lives in the
* context. Calling the bootstrap from here with raw ORM sets would seed
* by a DIFFERENT path than the app's — which is the divergence this
* harness exists to avoid.
*/
loadTestData() {
return bootstrapWallet(events as any, users as any, participations as any);
return appData.loadTestData();
},
// --- Stopgap multi-store validation (see brief_2026-06-15_shared-wallet-shim) ---