c5de1cea35
`Set.prototype.add` rend THIS pour permettre le chainage - EXECUTE, pas deduit, sur le `Set` nu et sur le vrai `DeepSignalSet`, qui rendent tous deux la collection. Les trois mutations etaient transmises liees a la cible sur le motif ecrit juste au-dessus : "elles prennent un objet et rendent void ou un booleen, donc elles ne laissent rien fuir". Faux pour `add`, et `add` est le PREMIER membre que l-exemple de documentation du SDK emploie. Toutes les entrees etaient filtrees et la sortie rendait la collection entiere. Les deux autres rendaient bien ce que le motif disait, et AGISSAIENT au-dela de la vue : `delete` retirait un objet dont `has` refuse meme de confirmer l-existence - les deux se contredisaient - et `clear` vidait la collection que toutes les identites partagent. Une seule regle : une mutation atteint la collection dessous, mais ne rend ni n-atteint ce que la vue cache. `add` rend la VUE, `delete` n-agit que sur ce que la vue montre, `clear` vide la vue. L-ecriture atteint toujours le document. Le motif commun aux cinq fuites de cette famille est une JUSTIFICATION ECRITE SUR UNE VALEUR DE RETOUR JAMAIS EXECUTEE. Chacune etait raisonnable, aucune n-avait ete jouee. Aussi : la forme ordonnee rend des index la ou un Set rend des objets (`keys`/`entries`/`forEach`), et le faux partage reproduit desormais le `isLoading` LIVRE - inverse de sa propre declaration. L-inversion est amont ; un faux plus correct que la cible recree l-aveuglement que cette tache enleve.
122 lines
5.6 KiB
TypeScript
122 lines
5.6 KiB
TypeScript
/**
|
|
* use-shape-fake - a stand-in for the SDK's React hook, `@ng-org/orm`'s `useShape`.
|
|
*
|
|
* Not a `*.test.ts`, so `bun test` does not pick it up: it is the injection the suites
|
|
* share, and it is the seam the package is built on - an application hands `configure` the
|
|
* real hook, and a suite hands it this. Sibling of `orm-fake.ts`, which does the same for
|
|
* the shape query one floor down.
|
|
*
|
|
* -- Why it returns the ENVELOPE, and why that is the whole point -----------
|
|
* Every fake in this repository used to return a bare Set-like, because that is what the
|
|
* hook returned in `alpha.18`. `@ng-org/orm@0.1.2-alpha.21` returns a `UseShapeResult`
|
|
* AROUND the collection, and while no fake said so, a capability filter that handed three
|
|
* of that envelope's members back RAW could not fail a single test. So the shape below is
|
|
* read off the artifact that settles it - the installed package's
|
|
* `dist/frontendAdapters/react/useShape.d.ts`, and the `dist/.../useShape.js` beside it,
|
|
* which agree - and not off what this package wished the hook returned:
|
|
*
|
|
* { data, promise, subscription, isLoading, nextPage, previousPage }
|
|
*
|
|
* Each of the six is reproduced with the cardinality the declaration gives it:
|
|
* - `data` is a `Set` for an unordered config and an ARRAY once `orderBy` is asked for
|
|
* (upstream `SubscriptionData<T, CONF>`), and `undefined` while nothing has arrived;
|
|
* - `promise` resolves to that same collection, and is `undefined` when `conf` is;
|
|
* - `subscription` is the handle, carrying the items three ways of its own -
|
|
* `signalObject`, `readyPromise` and the raw arrays a change listener is handed;
|
|
* - `isLoading` is a boolean, the one member that carries nothing - reproduced with the
|
|
* shipped EXPRESSION, `ormSubscription.isReady`, which is the INVERSE of what the
|
|
* declaration beside it describes ("`true` when no data is available yet"). The
|
|
* inversion is upstream's, and a fake that quietly corrected it would be measuring a
|
|
* hook nobody ships - which is the exact blindness this file exists to remove;
|
|
* - the two page commands exist only under a paginated config, as `WithMaybePagination`
|
|
* `Omit`s them otherwise and the shipped hook leaves them `undefined`.
|
|
*
|
|
* What it does NOT invent: no member the declaration does not carry. A fake richer than
|
|
* the target proves things about a surface nobody ships.
|
|
*/
|
|
|
|
import type { BaseType } from "@ng-org/shex-orm";
|
|
import type { ShapeResult, UseShapeLike } from "../src/model/types";
|
|
|
|
/** The subscription handle the envelope carries - the members `RdfOrmSubscription` exposes
|
|
* that reach the items, which is what makes handing it on a leak. */
|
|
export interface FakeSubscription {
|
|
readonly signalObject: unknown;
|
|
readonly readyPromise: Promise<unknown>;
|
|
readonly isReady: boolean;
|
|
addChangeListener(l: (c: { adds: unknown[]; updates: unknown[]; removes: unknown[] }) => void): void;
|
|
close(): void;
|
|
}
|
|
|
|
export interface FakeShapeHook {
|
|
/** What goes into `configure({ useShape })`. */
|
|
hook: UseShapeLike;
|
|
/** The rows the fake engine holds. A suite mutates this array in place. */
|
|
items: BaseType[];
|
|
/** The RAW collection the last call handed back - what a leak reaches, and what a test
|
|
* compares the filtered view against. */
|
|
raw(): unknown;
|
|
/** Pages asked for, in order, so a test can see a forwarded command arrive. */
|
|
pages: string[];
|
|
}
|
|
|
|
/**
|
|
* A fake `useShape` over an in-memory row list.
|
|
*
|
|
* `loaded` (the default) answers as a hook whose payload has arrived. Turn it off for the
|
|
* state the declaration describes as "still loading": `promise` present but `data` not
|
|
* there yet. Note which way `isLoading` then reads - see the note above; it follows the
|
|
* shipped expression, not the shipped prose.
|
|
*/
|
|
export function makeUseShapeHook(
|
|
options: { loaded?: boolean; paginated?: boolean } = {},
|
|
): FakeShapeHook {
|
|
const items: BaseType[] = [];
|
|
const pages: string[] = [];
|
|
const loaded = options.loaded !== false;
|
|
const paginated = options.paginated === true;
|
|
let lastRaw: unknown = undefined;
|
|
|
|
const hook: UseShapeLike = (_shapeType, conf): ShapeResult => {
|
|
// Upstream: "If the conf is explicitly set to `undefined`, the returned data is
|
|
// `undefined` and `isLoading` is false" - and `promise`/`subscription` are typed
|
|
// `CONF extends undefined ? undefined : ...`, so all three go together.
|
|
if (conf === undefined) {
|
|
lastRaw = undefined;
|
|
return {
|
|
data: undefined,
|
|
promise: undefined,
|
|
subscription: undefined,
|
|
isLoading: false,
|
|
nextPage: undefined,
|
|
previousPage: undefined,
|
|
};
|
|
}
|
|
// `SubscriptionData<T, CONF>`: a set with no `orderBy`, an ordered array with one.
|
|
const ordered = typeof conf === "object" && conf.orderBy !== undefined;
|
|
const collection: unknown = ordered ? [...items] : new Set(items);
|
|
lastRaw = collection;
|
|
const subscription: FakeSubscription = {
|
|
signalObject: collection,
|
|
readyPromise: Promise.resolve(collection),
|
|
isReady: loaded,
|
|
addChangeListener(l): void {
|
|
l({ adds: [...items], updates: [], removes: [] });
|
|
},
|
|
close(): void {},
|
|
};
|
|
return {
|
|
data: loaded ? collection : undefined,
|
|
promise: Promise.resolve(collection),
|
|
subscription,
|
|
// Upstream, verbatim: `!ormSubscription ? false : ormSubscription.isReady`. There is
|
|
// a subscription here, so this is `isReady` - and `isReady` is `loaded`.
|
|
isLoading: loaded,
|
|
nextPage: paginated ? (): void => void pages.push("next") : undefined,
|
|
previousPage: paginated ? (): void => void pages.push("previous") : undefined,
|
|
};
|
|
};
|
|
|
|
return { hook, items, raw: () => lastRaw, pages };
|
|
}
|