Skip to content
Merged
18 changes: 18 additions & 0 deletions src/api/__tests__/fc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const fcMockUseQueryResponseArg = <T>(arbData: fc.Arbitrary<T>) =>
export const fcMockUseSubscriptionResponseArg = <T>(arbData: fc.Arbitrary<T>) =>
fc.array(fcMockUseQueryResponseArg(arbData));

export const fcScheduleQueueItem = fc.record({
createdAt: fc.date().map((d) => d.toISOString()),
id: fc.integer(),
name: fc.string(),
script: fc.string(),
});

if (import.meta.vitest) {
it("fcUndefinedOr()", () => {
fc.assert(
Expand Down Expand Up @@ -50,4 +57,15 @@ if (import.meta.vitest) {
}),
);
});

it("fcScheduleQueueItem()", () => {
fc.assert(
fc.property(fcScheduleQueueItem, (v) => {
expect(v.createdAt).toBeDefined();
expect(v.id).toBeDefined();
expect(v.name).toBeDefined();
expect(v.script).toBeDefined();
}),
);
});
}
2 changes: 2 additions & 0 deletions src/api/__tests__/mock-use-query-response.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ describe("mockUseQueryResponse()", () => {
expect(response).toBe(returned);

// Assert initially undefined.
expect(response.fetching.value).toBe(true);
expect(response.error.value).toBeUndefined();
expect(response.data.value).toBeUndefined();

// Await until the values are assigned.
const state = await response;
expect(response.fetching.value).toBe(false);

// Confirm the object returned by await contains the same objects.
expect(state.data).toBe(response.data);
Expand Down
5 changes: 4 additions & 1 deletion src/api/__tests__/mock-use-query-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { OnReady } from "@/utils/on-ready";
type MockUseQueryResponse<T> = OnReady<{
data: Ref<T | undefined>;
error: Ref<Error | undefined>;
fetching: Ref<boolean>;
}>;

interface MockUseQueryResponseArg<T> {
Expand All @@ -19,12 +20,14 @@ export function mockUseQueryResponse<T>(
): MockUseQueryResponse<T> {
const data = ref<T | undefined>(undefined);
const error = ref<Error | undefined>(undefined);
const fetching = ref<boolean>(true);

const ready = (async () => {
await Promise.resolve();
data.value = arg.data;
error.value = arg.error;
fetching.value = false;
})();

return onReady({ data, error }, ready) as MockUseQueryResponse<T>;
return onReady({ data, error, fetching }, ready) as MockUseQueryResponse<T>;
}
18 changes: 15 additions & 3 deletions src/api/__tests__/run-property-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { mockUseSubscriptionResponse } from "./mock-use-subscription-response";
interface _UseSubscribeXXXReturn<R> {
data: ComputedRef<R | undefined>;
error: ComputedRef<Error | undefined>;
loading: ComputedRef<boolean>;
}

type UseSubscribeXXXReturn<R> = _UseSubscribeXXXReturn<R> &
Expand Down Expand Up @@ -39,11 +40,22 @@ export async function runPropertyTest<QueryData, SubData, R>(
const { response: subRes, issue } = mockUseSubscriptionResponse<SubData>(subArg);
vi.mocked(useSubscription<SubData>).mockReturnValue(subRes as SubRes);

const { data, error } = await useSubscribeXXX();
const { data, error, loading, then } = useSubscribeXXX();

// Assert loading and undefined values.
expect(loading.value).toBe(true);
expect(error.value).toBeUndefined();
expect(data.value).toBeUndefined();

// Wait until loading ends.
await then();
expect(loading.value).toBe(false);

// Assert initial values are from query.
expect(error.value).toBe(queryArg.error);
expect(data.value).toBe(queryArg.error ? undefined : mapQuery(queryArg.data));
expect(data.value).toStrictEqual(
queryArg.error ? undefined : mapQuery(queryArg.data),
);

// Assert the subsequent values are issued from subscription backed up by query.
for (const issued of issue) {
Expand All @@ -52,7 +64,7 @@ export async function runPropertyTest<QueryData, SubData, R>(
? undefined
: (mapSub(issued.data) ?? mapQuery(queryArg.data));
expect(error.value).toBe(expectedError);
expect(data.value).toBe(expectedData);
expect(data.value).toStrictEqual(expectedData);
}
}),
);
Expand Down
131 changes: 131 additions & 0 deletions src/api/__tests__/use-subscriptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,27 @@ import type {
CtrlStateSSubscription,
CtrlRunNoQuery,
CtrlRunNoSSubscription,
CtrlTraceIdsQuery,
CtrlTraceIdsSSubscription,
CtrlContinuousEnabledQuery,
CtrlContinuousEnabledSSubscription,
ScheduleAutoModeModeQuery,
QScheduleAutoModeStateQuery,
ScheduleAutoModeStateSSubscription,
ScheduleAutoModeModeSSubscription,
ScheduleQueueItemsQuery,
ScheduleQueueItemsSSubscription,
} from "@/graphql/codegen/generated";

import { useSubscribeContinuousEnabled } from "../use-continuous-enabled-subscription";
import { useSubscribeRunNo } from "../use-run-no-subscription";
import { useSubscribeScheduleAutoModeMode } from "../use-schedule-auto-mode-mode-subscription";
import { useSubscribeScheduleAutoModeState } from "../use-schedule-auto-mode-state-subscription";
import { useSubscribeScheduleQueueItems } from "../use-schedule-queue-items-subscription";
import { useSubscribeState } from "../use-state-subscription";
import { useSubscribeTraceIds } from "../use-trace_ids-subscription";

import { fcScheduleQueueItem } from "./fc";
import { runPropertyTest } from "./run-property-test";

// Mock functions used in runPropertyTest()
Expand Down Expand Up @@ -50,3 +66,118 @@ it("useSubscribeRunNo", async () => {

await runPropertyTest(useSubscribeRunNo, mapQuery, mapSub, fcQueryData, fcSubData);
});

it("useSubscribeTraceIds", async () => {
type QueryData = CtrlTraceIdsQuery;
type SubData = CtrlTraceIdsSSubscription;

const mapQuery = (d: QueryData | undefined) => d?.ctrl.traceIds;
const mapSub = (d: SubData | undefined) => d?.ctrlTraceIds;

const fcTraceIds = fc.array(fc.integer());
const fcQueryData: fc.Arbitrary<QueryData> = fc.record({
ctrl: fc.record({ traceIds: fcTraceIds }),
});
const fcSubData: fc.Arbitrary<SubData> = fc.record({
ctrlTraceIds: fcTraceIds,
});

await runPropertyTest(useSubscribeTraceIds, mapQuery, mapSub, fcQueryData, fcSubData);
});

it("useSubscribeContinuousEnabled", async () => {
type QueryData = CtrlContinuousEnabledQuery;
type SubData = CtrlContinuousEnabledSSubscription;

const mapQuery = (d: QueryData | undefined) => d?.ctrl.continuousEnabled;
const mapSub = (d: SubData | undefined) => d?.ctrlContinuousEnabled;

const fcContinuousEnabled = fc.boolean();
const fcQueryData: fc.Arbitrary<QueryData> = fc.record({
ctrl: fc.record({ continuousEnabled: fcContinuousEnabled }),
});
const fcSubData: fc.Arbitrary<SubData> = fc.record({
ctrlContinuousEnabled: fcContinuousEnabled,
});

await runPropertyTest(
useSubscribeContinuousEnabled,
mapQuery,
mapSub,
fcQueryData,
fcSubData,
);
});

it("useSubscribeScheduleAutoModeMode", async () => {
type QueryData = ScheduleAutoModeModeQuery;
type SubData = ScheduleAutoModeModeSSubscription;

const mapQuery = (d: QueryData | undefined) => d?.schedule.autoMode.mode;
const mapSub = (d: SubData | undefined) => d?.scheduleAutoModeMode;

const fcScheduleAutoMode = fc.string();
const fcQueryData: fc.Arbitrary<QueryData> = fc.record({
schedule: fc.record({ autoMode: fc.record({ mode: fcScheduleAutoMode }) }),
});
const fcSubData: fc.Arbitrary<SubData> = fc.record({
scheduleAutoModeMode: fcScheduleAutoMode,
});

await runPropertyTest(
useSubscribeScheduleAutoModeMode,
mapQuery,
mapSub,
fcQueryData,
fcSubData,
);
});

it("useSubscribeScheduleAutoModeState", async () => {
type QueryData = QScheduleAutoModeStateQuery;
type SubData = ScheduleAutoModeStateSSubscription;

const mapQuery = (d: QueryData | undefined) => d?.schedule.autoMode.state;
const mapSub = (d: SubData | undefined) => d?.scheduleAutoModeState;

const fcScheduleAutoModeState = fc.string();
const fcQueryData: fc.Arbitrary<QueryData> = fc.record({
schedule: fc.record({ autoMode: fc.record({ state: fcScheduleAutoModeState }) }),
});
const fcSubData: fc.Arbitrary<SubData> = fc.record({
scheduleAutoModeState: fcScheduleAutoModeState,
});

await runPropertyTest(
useSubscribeScheduleAutoModeState,
mapQuery,
mapSub,
fcQueryData,
fcSubData,
);
});

it("useSubscribeScheduleQueueItems", async () => {
type QueryData = ScheduleQueueItemsQuery;
type SubData = ScheduleQueueItemsSSubscription;

const mapQuery = (d: QueryData | undefined) => d?.schedule.queue.items;
const mapSub = (d: SubData | undefined) => d?.scheduleQueueItems;

const fcQueryData: fc.Arbitrary<QueryData> = fc.record({
schedule: fc.record({
queue: fc.record({ items: fc.array(fcScheduleQueueItem) }),
}),
});
const fcSubData: fc.Arbitrary<SubData> = fc.record({
scheduleQueueItems: fc.array(fcScheduleQueueItem),
});

await runPropertyTest(
useSubscribeScheduleQueueItems,
mapQuery,
mapSub,
fcQueryData,
fcSubData,
);
});
36 changes: 11 additions & 25 deletions src/api/use-continuous-enabled-subscription.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,25 @@
import type { ComputedRef } from "vue";
import { computed } from "vue";

import {
useCtrlContinuousEnabledQuery,
useCtrlContinuousEnabledSSubscription,
} from "@/graphql/codegen/generated";
import { onReady } from "@/utils/on-ready";
import type { OnReady } from "@/utils/on-ready";

interface _ContinuousEnabledSubscription {
continuousEnabled: ComputedRef<boolean | undefined>;
error: ComputedRef<Error | undefined>;
subscription: ReturnType<typeof useCtrlContinuousEnabledSSubscription>;
query: ReturnType<typeof useCtrlContinuousEnabledQuery>;
}

type ContinuousEnabledSubscription = OnReady<_ContinuousEnabledSubscription>;
import { useQueryBackedSubscription } from "./use-query-backed-subscription";

export function useSubscribeContinuousEnabled(): ContinuousEnabledSubscription {
export function useSubscribeContinuousEnabled() {
const query = useCtrlContinuousEnabledQuery({
requestPolicy: "network-only",
variables: {},
});
const subscription = useCtrlContinuousEnabledSSubscription({ variables: {} });

const error = computed(() => subscription.error?.value || query.error?.value);
const mapQueryData = (d: typeof query.data) => d.value?.ctrl.continuousEnabled;
const mapSubscriptionData = (d: typeof subscription.data) =>
d.value?.ctrlContinuousEnabled;

const continuousEnabled = computed(() =>
error.value
? undefined
: subscription.data?.value?.ctrlContinuousEnabled ||
query.data?.value?.ctrl.continuousEnabled,
);

const ret = { continuousEnabled, error, subscription, query };

return onReady(ret, query);
return useQueryBackedSubscription({
query,
subscription,
mapQueryData,
mapSubscriptionData,
});
}
7 changes: 6 additions & 1 deletion src/api/use-query-backed-subscription.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { computed } from "vue";
import type { ComputedRef, Ref } from "vue";
import { UseQueryResponse, UseSubscriptionResponse } from "@urql/vue";

Expand All @@ -16,16 +17,20 @@ interface UseQueryBackedSubscriptionOptions<T, Q, S> {
type UseQueryBackedSubscriptionReturn<T> = OnReady<{
data: ComputedRef<T | undefined>;
error: ComputedRef<Error | undefined>;
loading: ComputedRef<boolean>;
}>;

export function useQueryBackedSubscription<T, Q, S>(
options: UseQueryBackedSubscriptionOptions<T, Q, S>,
): UseQueryBackedSubscriptionReturn<T> {
const ret = useMappedWithFallback({
const mapped = useMappedWithFallback({
response1: options.subscription,
response2: options.query,
map1: options.mapSubscriptionData,
map2: options.mapQueryData,
});
const loading = computed(() => options.query.fetching?.value);

const ret = { ...mapped, loading };
return onReady(ret, options.query);
}
11 changes: 1 addition & 10 deletions src/api/use-run-no-subscription.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import type { ComputedRef } from "vue";

import {
useCtrlRunNoQuery,
useCtrlRunNoSSubscription,
} from "@/graphql/codegen/generated";

import { useQueryBackedSubscription } from "./use-query-backed-subscription";

interface _RunNoSubscription {
data: ComputedRef<number | undefined>;
error: ComputedRef<Error | undefined>;
}

type RunNoSubscription = _RunNoSubscription & PromiseLike<_RunNoSubscription>;

export function useSubscribeRunNo(): RunNoSubscription {
export function useSubscribeRunNo() {
const query = useCtrlRunNoQuery({ requestPolicy: "network-only", variables: {} });
const subscription = useCtrlRunNoSSubscription({ variables: {} });

Expand Down
Loading
Loading