Files
clean-architecture-frontend…/tests/component/application-query-fixture.tsx
T

108 lines
3.1 KiB
TypeScript

// @vitest-environment jsdom
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react";
import type { MutationIntentFactory } from "../../src/application/ports/mutation-intent-factory.ts";
import { QueryInvalidationProvider } from "../../src/presentation/adapters/query/query-invalidation-provider.tsx";
import { MutationIntentProvider } from "../../src/presentation/adapters/query/mutation-intent-provider.tsx";
import {
defineQueryInvalidationTopic,
type QueryInvalidationCoordinator,
} from "../../src/contracts/query-invalidation.ts";
import { createRuntimeIdentityRegistry } from "../../src/contracts/query-keys.ts";
import type { QueryResultMeasure } from "../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../src/contracts/server-state-scope.ts";
export const RESOURCE_INVALIDATION_TOPIC =
defineQueryInvalidationTopic("resource");
export function scopeSnapshot(
generation = 1,
fingerprint = "scope-fingerprint-0001",
): CacheScopeSnapshot & { fence(): void } {
let current = true;
const lifetime = new AbortController();
const identities = createRuntimeIdentityRegistry({
tokenFactory: () => "scope-identity-token-0001",
});
return {
generation,
fingerprint,
identities,
signal: lifetime.signal,
isCurrent: () => current,
fence() {
current = false;
lifetime.abort();
},
};
}
export function measureOne(): QueryResultMeasure {
return { itemCount: 1, estimatedBytes: 8 };
}
export function queryClient() {
return new QueryClient({
defaultOptions: {
queries: { retry: false, staleTime: 0, gcTime: Infinity },
mutations: { retry: false },
},
});
}
export function deterministicMutationIntentFactory(): MutationIntentFactory {
let sequence = 0;
return Object.freeze({
create(input) {
sequence += 1;
return Object.freeze({
intentId: `intent-${sequence}`,
operationId: input.operationId,
canonicalInputIdentity: input.canonicalInputIdentity,
...(input.requiresIdempotencyKey
? { idempotencyKey: `key-${sequence}` }
: {}),
createdAtMonotonicMs: sequence,
});
},
});
}
export function wrapper(
client: QueryClient,
mutationIntentFactory = deterministicMutationIntentFactory(),
) {
const coordinator: QueryInvalidationCoordinator = {
async invalidate(topics) {
for (const topic of topics) {
await client.invalidateQueries({
queryKey: [topic],
exact: false,
refetchType: "active",
});
}
},
beginMutation() {
return { release: async () => {} };
},
async resetLocal() {
await client.cancelQueries();
client.clear();
},
dispose() {},
};
return function QueryWrapper({ children }: { children: ReactNode }) {
return (
<MutationIntentProvider factory={mutationIntentFactory}>
<QueryClientProvider client={client}>
<QueryInvalidationProvider coordinator={coordinator}>
{children}
</QueryInvalidationProvider>
</QueryClientProvider>
</MutationIntentProvider>
);
};
}