eb956c880d
L-ASYMETRIE, une fois de plus, et c-est elle le defaut. `delete` et `clear` interrogeaient `keep`, `add` non - ce qui rendait la phrase publiee vraie par coincidence : le `add` non garde FABRIQUAIT l-etat que les deux autres ne traitaient qu-a moitie. Bob plante un objet dans le document d-ALICE, ne le revoit jamais (la vue le refiltre), la liaison bidirectionnelle le commite la-bas, puis `clear()` le laisse derriere. Les trois posent maintenant la meme question ; ce qu-elles repondent differe parce que les membres different : `delete` a `false`, qui est le mot du contrat Set pour "il n-y etait pas" et qui est vrai de la vue ; `add` n-a aucune valeur de ce sens, donc il leve. LA VUE PORTE SES PROPRES ECRITURES. Aucun piege `set`/`deleteProperty`/ `defineProperty` : contre un tableau ordonne, `view[0] = x`, `delete view[0]` et `view.length = 0` passaient - le dernier defaisant `clear` entierement. Ce qui est livre est un `readOnlyArray` qui les refuse, donc la production etait protegee PAR LA CIBLE : la vue s-appuyait sur une garantie qu-elle ne donne pas, sur une forme qu-elle ne controle pas. C-est l-hypothese empruntee qui est derriere chaque defaut de cette famille. LE PROPRIETAIRE EST SERVI. 18 membres declares d-un resultat ordonne levaient pour celui dont ce sont les donnees, plus `includes` et les sept combinateurs d-ensemble : une fuite echangee contre une panne. Tout ce que `@ng-org/alien-deepsignals` declare non mutant est servi sur les objets lisibles, et un test relit le fichier livre pour que la liste restee ici ne derive pas. Aussi : le descripteur d-un ACCESSEUR routait vers la cible ; le faux construit maintenant les VRAIES collections (`deepSignal`, `readOnlyArray`) au lieu d-un Set et d-un tableau nus, ce qui le rendait plus permissif que ce qui est livre apres l-avoir rendu plus correct au round precedent.
133 lines
6.5 KiB
TypeScript
133 lines
6.5 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 `DeepSignalSet` for an unordered config and a `readOnlyArray` over a
|
|
* `DeepSignal` array once `orderBy` is asked for (upstream `SubscriptionData<T, CONF>`),
|
|
* built with upstream's OWN constructors, 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.
|
|
*/
|
|
|
|
// The REAL collection kinds, not stand-ins for them. `data` is what `useDeepSignal` wraps
|
|
// - a `DeepSignalSet` unordered, a `readOnlyArray` over a `DeepSignal` array once an
|
|
// `orderBy` is asked for - and the two behave in ways a plain `Set` and a plain array do
|
|
// not: the set's `add` hands the proxy back, and the ordered one REFUSES a write to an
|
|
// index, to `length`, and even a `get` of `push`. A fake that used the plain kinds was
|
|
// MORE PERMISSIVE than what ships (it let a caller overwrite an item by index), having
|
|
// been more CORRECT than it one round earlier. Neither is a fake's business.
|
|
import { deepSignal, readOnlyArray } from "@ng-org/alien-deepsignals";
|
|
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
|
|
? readOnlyArray(deepSignal([...items]) as never)
|
|
: deepSignal(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 };
|
|
}
|