Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 65 additions & 87 deletions src/api/__tests__/use-state-subscription.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ref } from "vue";
import fc from "fast-check";

import {
useCtrlStateQuery,
useCtrlStateSSubscription,
} from "@/graphql/codegen/generated";
import type { CtrlStateSSubscription } from "@/graphql/codegen/generated";
import { onReady } from "@/utils/on-ready";

import { useSubscribeState } from "../use-state-subscription";

Expand All @@ -12,104 +16,78 @@ vi.mock("@/graphql/codegen/generated", () => ({
useCtrlStateSSubscription: vi.fn(),
}));

describe("useSubscribeState", () => {
beforeEach(() => {
vi.clearAllMocks();
});
const fcState = fc.oneof(fc.constant(undefined), fc.string({ minLength: 1 }));

afterEach(() => {
vi.resetAllMocks();
});
const fcErrorInstance = fc.string().map((msg) => new Error(msg));
const fcError = fc.oneof(fc.constant(undefined), fcErrorInstance);

it("fetches initial state successfully from the query", () => {
vi.mocked(useCtrlStateQuery).mockReturnValue({
data: { value: { ctrl: { state: "initialized" } } },
error: { value: undefined },
} as any);
vi.mocked(useCtrlStateSSubscription).mockReturnValue({
data: { value: undefined },
error: { value: undefined },
} as any);

const { state } = useSubscribeState();
expect(state.value).toBe("initialized");
});
type Query = ReturnType<typeof useCtrlStateQuery>;
type Sub = ReturnType<typeof useCtrlStateSSubscription>;

it("updates state from subscription", () => {
const queryMock = vi.mocked(useCtrlStateQuery);
const subscriptionMock = vi.mocked(useCtrlStateSSubscription);
function createMockQuery(
state_value: string | undefined,
error_value: Error | undefined,
): Query {
type Data = NonNullable<Query["data"]["value"]>;
const data = ref<Data | undefined>(undefined);
const error = ref<Error | undefined>(undefined);

queryMock.mockReturnValue({
data: { value: { ctrl: { state: "initial" } } },
error: { value: undefined },
} as any);
const ready = (async () => {
await Promise.resolve();
data.value = { ctrl: { state: state_value } } as Data;
error.value = error_value;
})();

subscriptionMock.mockReturnValue({
data: { value: { ctrlState: "updated" } },
error: { value: undefined },
} as any);
return onReady({ data, error }, ready) as Query;
}

const { state } = useSubscribeState();
expect(state.value).toBe("updated");
});
function createMockSubscription(
state_value: string | undefined,
error_value: Error | undefined,
): Sub {
const data = ref<CtrlStateSSubscription | undefined>(undefined);
const error = ref<Error | undefined>(undefined);

it("handles query error", () => {
vi.mocked(useCtrlStateQuery).mockReturnValue({
data: { value: undefined },
error: { value: new Error("Query error") },
} as any);
vi.mocked(useCtrlStateSSubscription).mockReturnValue({
data: { value: undefined },
error: { value: undefined },
} as any);

const { state, error } = useSubscribeState();
expect(state.value).toBeUndefined();
expect(error.value).toEqual(new Error("Query error"));
});
const ready = (async () => {
await Promise.resolve();
data.value = { ctrlState: state_value } as CtrlStateSSubscription;
error.value = error_value;
})();

it("handles subscription error", () => {
vi.mocked(useCtrlStateQuery).mockReturnValue({
data: { value: { ctrl: { state: "initial" } } },
error: { value: undefined },
} as any);
vi.mocked(useCtrlStateSSubscription).mockReturnValue({
data: { value: undefined },
error: { value: new Error("Subscription error") },
} as any);

const { state, error } = useSubscribeState();
expect(state.value).toBeUndefined();
expect(error.value).toEqual(new Error("Subscription error"));
return onReady({ data, error }, ready) as unknown as Sub;
}

describe("useSubscribeState()", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("can be used as a promise", async () => {
vi.mocked(useCtrlStateQuery).mockReturnValue({
data: { value: { ctrl: { state: "promised" } } },
error: { value: undefined },
then: (resolve: (value: any) => void) =>
resolve({ data: { value: { ctrl: { state: "promised" } } } }),
} as any);
vi.mocked(useCtrlStateSSubscription).mockReturnValue({
data: { value: undefined },
error: { value: undefined },
} as any);

const result = await useSubscribeState();
expect(result.state.value).toBe("promised");
afterEach(() => {
vi.resetAllMocks();
});

it("handles empty responses", () => {
vi.mocked(useCtrlStateQuery).mockReturnValue({
data: { value: null },
error: { value: undefined },
} as any);
vi.mocked(useCtrlStateSSubscription).mockReturnValue({
data: { value: null },
error: { value: undefined },
} as any);

const { state } = useSubscribeState();
expect(state.value).toBeUndefined();
it("Property test", async () => {
await fc.assert(
fc.asyncProperty(
fcState,
fcError,
fcState,
fcError,
async (queryState, queryError, subState, subError) => {
const query = createMockQuery(queryState, queryError);
const sub = createMockSubscription(subState, subError);
vi.mocked(useCtrlStateQuery).mockReturnValue(query);
vi.mocked(useCtrlStateSSubscription).mockReturnValue(sub);
const { state, error } = await useSubscribeState();
const expectedError = subError || queryError;
const expectedState = expectedError
? undefined
: subState || queryState || undefined;

expect(error.value).toBe(expectedError);
expect(state.value).toBe(expectedState);
},
),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@ const AUTO_MODE_STATES = [

const fcAutoModeState = () => fc.constantFrom(...AUTO_MODE_STATES);

describe("scratch", () => {
type Sub = ReturnType<typeof useSubscribeScheduleAutoModeState>;

function createMockSubscription(auto_mode_state: string): Sub {
// Initially `undefined`
const autoModeState = ref(undefined as string | undefined);

// A value set when ready
const ready = (async () => {
await Promise.resolve();
autoModeState.value = auto_mode_state;
})();

// Thenable
return onReady({ autoModeState }, ready) as Sub;
}

describe("usePulling()", () => {
beforeEach(() => {
vi.clearAllMocks();
});
Expand All @@ -33,22 +49,8 @@ describe("scratch", () => {
it("Property test", async () => {
await fc.assert(
fc.asyncProperty(fcAutoModeState(), async (auto_mode_state) => {
// Initially `undefined`
const autoModeState = ref(undefined as string | undefined);

// A value set when ready
const ready = (async () => {
await Promise.resolve();
autoModeState.value = auto_mode_state;
})();

type Sub = ReturnType<typeof useSubscribeScheduleAutoModeState>;

// Thenable
const sub = onReady({ autoModeState }, ready) as unknown as Sub;

const sub = createMockSubscription(auto_mode_state);
vi.mocked(useSubscribeScheduleAutoModeState).mockReturnValue(sub);

const { pulling } = await usePulling();
const expected = auto_mode_state === "auto_pulling";
expect(pulling.value).toBe(expected);
Expand Down
Loading