test(data): validate ng-eventually read filter on the real ORM set

Adds a @data scenario (workshop/read-filter) that enables the lib's read filter on the
real reactive ORM set (via a FilterProbe + setupReadFilter harness helper, granting each
participation to its own user) and asserts useShape returns only the target user's
participations. Validates the trickiest piece — filtering a live DeepSignalSet — against the
broker. @data 9/9. Doc: read filter marked implemented & validated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-06-29 10:28:56 +02:00
parent e270cc6063
commit aec338441c
4 changed files with 119 additions and 3 deletions
+40 -1
View File
@@ -12,6 +12,7 @@ import { createRoot } from 'react-dom/client';
import { NextGraphProvider, useNextGraph } from '../context/NextGraphContext';
import { FestipodDataProvider, useFestipodData } from '../context/FestipodDataContext';
import { useShape } from '@ng-eventually/client';
import { setGrantOf, setCurrentUser } from '@ng-eventually/client/polyfill';
import type { DeepSignalSet } from '@ng-eventually/client';
import {
FpEventShapeType,
@@ -67,6 +68,8 @@ function ConnectedHarness() {
const participations = useShape(FpParticipationShapeType, privateNuri) as DeepSignalSet<FpParticipation>;
const [bridgeReady, setBridgeReady] = useState(false);
// Read-filter validation: when set, <FilterProbe> mounts a filtered useShape.
const [filterUser, setFilterUser] = useState<string | null>(null);
useEffect(() => {
// Small delay for useShape to populate
@@ -146,6 +149,19 @@ function ConnectedHarness() {
loadTestData() {
return bootstrapWallet(events as any, users as any, participations as any);
},
/**
* Enable the lib's READ FILTER on the real ORM set: each participation is
* granted to its own `user`, and the current user is `user`. <FilterProbe>
* then exposes window.__readFilter with the filtered participations.
*/
setupReadFilter(user: string) {
setGrantOf((item: any) =>
item && item.user ? { read: [item.user], write: [item.user] } : undefined,
);
setCurrentUser(user);
setFilterUser(user);
},
};
console.log('[HarnessNG] Ready — events:', events.size, 'users:', users.size,
@@ -157,7 +173,30 @@ function ConnectedHarness() {
return () => clearTimeout(timer);
}, [events, users, participations, ngCtx, appData]);
return <div id="harness-status">{bridgeReady ? 'READY' : 'LOADING_SHAPES'}</div>;
return (
<>
<div id="harness-status">{bridgeReady ? 'READY' : 'LOADING_SHAPES'}</div>
{filterUser && privateNuri && <FilterProbe privateNuri={privateNuri} />}
</>
);
}
// ============================================================================
// FilterProbe — subscribes participations AFTER the read filter is enabled, so
// useShape returns a filtered view. Exposes window.__readFilter for the @data
// scenario validating the read filter on the real ORM set.
// ============================================================================
function FilterProbe({ privateNuri }: { privateNuri: string }) {
const set = useShape(FpParticipationShapeType, privateNuri) as DeepSignalSet<FpParticipation>;
useEffect(() => {
(window as any).__readFilter = {
ready: true,
count: set.size,
users: [...set].map(p => p.user),
};
}, [set]);
return null;
}
// ============================================================================