mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
Merge pull request #1337 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
This commit is contained in:
@@ -61,6 +61,7 @@ export const FREIGHT_PERMS = {
|
||||
wagonCancellationVoid: "edr_freight_app:bookings:wagon_cancellation_void",
|
||||
wagonCancellationRebook:
|
||||
"edr_freight_app:bookings:wagon_cancellation_rebook",
|
||||
approveConsolidation: "edr_freight_app:bookings:approve_consolidation",
|
||||
},
|
||||
contracts: {
|
||||
view: "edr_freight_app:contracts:view",
|
||||
|
||||
58
apps/edr-freight-web/backoffice/src/lib/queryClient.test.ts
Normal file
58
apps/edr-freight-web/backoffice/src/lib/queryClient.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { queryClient } from "./queryClient";
|
||||
|
||||
/**
|
||||
* The MutationCache seeds `meta.updates` entries into the cache and then skips
|
||||
* exactly those keys when running `meta.invalidates`. Getting that skip wrong
|
||||
* silently reintroduces the refetch it exists to avoid, so it is worth pinning.
|
||||
*/
|
||||
const runOnSuccess = (meta: Record<string, unknown>, data: unknown, variables: unknown) => {
|
||||
const handler = (queryClient.getMutationCache() as unknown as {
|
||||
config: {
|
||||
onSuccess?: (
|
||||
data: unknown,
|
||||
variables: unknown,
|
||||
context: unknown,
|
||||
mutation: { meta?: Record<string, unknown> },
|
||||
) => void;
|
||||
};
|
||||
}).config.onSuccess;
|
||||
handler?.(data, variables, undefined, { meta });
|
||||
};
|
||||
|
||||
describe("MutationCache updates/invalidates", () => {
|
||||
it("writes the mutation response into the seeded key and leaves it fresh", () => {
|
||||
const seededKey = ["train-builder", "composition", "t1"] as const;
|
||||
const siblingKey = ["train-builder", "list", {}] as const;
|
||||
|
||||
queryClient.setQueryData(seededKey, { code: "STALE" });
|
||||
queryClient.setQueryData(siblingKey, { items: [] });
|
||||
|
||||
const response = { code: "FRESH" };
|
||||
runOnSuccess(
|
||||
{
|
||||
updates: (_v: unknown, d: unknown) => [[seededKey, d]],
|
||||
invalidates: () => [["train-builder"]],
|
||||
},
|
||||
response,
|
||||
{ id: "t1" },
|
||||
);
|
||||
|
||||
// Seeded key holds the response, and was NOT invalidated back to stale.
|
||||
expect(queryClient.getQueryData(seededKey)).toStrictEqual(response);
|
||||
expect(queryClient.getQueryState(seededKey)?.isInvalidated).toBe(false);
|
||||
|
||||
// Its siblings under the same root still get invalidated.
|
||||
expect(queryClient.getQueryState(siblingKey)?.isInvalidated).toBe(true);
|
||||
});
|
||||
|
||||
it("invalidates everything when a mutation declares no updates", () => {
|
||||
const key = ["train-builder", "composition", "t2"] as const;
|
||||
queryClient.setQueryData(key, { code: "X" });
|
||||
|
||||
runOnSuccess({ invalidates: () => [["train-builder"]] }, undefined, undefined);
|
||||
|
||||
expect(queryClient.getQueryState(key)?.isInvalidated).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MutationCache, QueryClient } from "@tanstack/react-query";
|
||||
|
||||
import type { InvalidatesMeta } from "@/utils/endpoint";
|
||||
import type { InvalidatesMeta, UpdatesMeta } from "@/utils/endpoint";
|
||||
|
||||
/**
|
||||
* Single app-wide React Query client (do not nest additional providers).
|
||||
@@ -14,13 +14,37 @@ import type { InvalidatesMeta } from "@/utils/endpoint";
|
||||
export const queryClient = new QueryClient({
|
||||
mutationCache: new MutationCache({
|
||||
onSuccess: (data, variables, _context, mutation) => {
|
||||
// Seed first: endpoints that return the entity they just changed write it
|
||||
// straight into its cache key, so the screen updates from the response
|
||||
// instead of round-tripping for data it already holds.
|
||||
const updates = mutation.meta?.updates as UpdatesMeta | undefined;
|
||||
const seeded: readonly unknown[][] = [];
|
||||
if (typeof updates === "function") {
|
||||
for (const [queryKey, value] of updates(variables, data)) {
|
||||
queryClient.setQueryData(queryKey, value);
|
||||
seeded.push(queryKey as unknown[]);
|
||||
}
|
||||
}
|
||||
|
||||
const invalidates = mutation.meta?.invalidates as
|
||||
| InvalidatesMeta
|
||||
| undefined;
|
||||
if (typeof invalidates !== "function") return;
|
||||
|
||||
for (const queryKey of invalidates(variables, data)) {
|
||||
void queryClient.invalidateQueries({ queryKey });
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey,
|
||||
// A seeded key already holds the authoritative value from this very
|
||||
// response — invalidating it would refetch it right back.
|
||||
predicate: seeded.length
|
||||
? (query) =>
|
||||
!seeded.some(
|
||||
(key) =>
|
||||
key.length === query.queryKey.length &&
|
||||
key.every((part, i) => Object.is(part, query.queryKey[i])),
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
},
|
||||
// Mutation failures are surfaced globally by the axios interceptor in
|
||||
|
||||
Reference in New Issue
Block a user