Skip to content

Commit 43dcded

Browse files
authored
Merge pull request #117 from simonsobs/dev
Update tests, clean code
2 parents 4d5f5e8 + 0ceb857 commit 43dcded

File tree

5 files changed

+239
-55
lines changed

5 files changed

+239
-55
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { useQuery } from "@urql/vue";
3+
import type { UseQueryResponse } from "@urql/vue";
4+
import gql from "graphql-tag";
5+
import fc from "fast-check";
6+
7+
import { mockUseQueryResponse } from "./mock-use-query-response";
8+
9+
vi.mock("@urql/vue", () => ({
10+
useQuery: vi.fn(),
11+
}));
12+
13+
const query = gql`
14+
query CtrlState {
15+
ctrl {
16+
state
17+
}
18+
}
19+
`;
20+
21+
type Data = { ctrl: { state: string } };
22+
23+
const fcState = fc.string({ minLength: 1 });
24+
const fcData: fc.Arbitrary<Data | undefined> = fc.oneof(
25+
fc.constant(undefined),
26+
fc.record({ ctrl: fc.record({ state: fcState }) }),
27+
);
28+
29+
const fcErrorInstance = fc.string().map((msg) => new Error(msg));
30+
const fcError = fc.oneof(fc.constant(undefined), fcErrorInstance);
31+
32+
const fcArg = fc.record({ data: fcData, error: fcError });
33+
34+
describe("mockUseQueryResponse()", () => {
35+
beforeEach(() => {
36+
vi.clearAllMocks();
37+
});
38+
39+
afterEach(() => {
40+
vi.resetAllMocks();
41+
});
42+
43+
it("Property test", async () => {
44+
fc.assert(
45+
fc.asyncProperty(fcArg, async (arg) => {
46+
const response = mockUseQueryResponse<Data>(arg);
47+
48+
type Response = UseQueryResponse<Data>;
49+
vi.mocked(useQuery).mockReturnValue(response as Response);
50+
51+
// Assert the mock response is returned.
52+
const returned = useQuery<Data>({ query });
53+
expect(response).toBe(returned);
54+
55+
// Assert initially undefined.
56+
expect(response.error.value).toBeUndefined();
57+
expect(response.data.value).toBeUndefined();
58+
59+
// Await until the values are assigned.
60+
const state = await response;
61+
62+
// Confirm the object returned by await contains the same objects.
63+
expect(state.data).toBe(response.data);
64+
expect(state.error).toBe(response.error);
65+
66+
// Assert the mocked values are assigned.
67+
expect(response.error.value).toBe(arg.error);
68+
expect(response.data.value).toStrictEqual(arg.data);
69+
}),
70+
);
71+
});
72+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ref } from "vue";
2+
import type { Ref } from "vue";
3+
4+
import { onReady } from "@/utils/on-ready";
5+
import type { OnReady } from "@/utils/on-ready";
6+
7+
type MockUseQueryResponse<T> = OnReady<{
8+
data: Ref<T | undefined>;
9+
error: Ref<Error | undefined>;
10+
}>;
11+
12+
interface MockUseQueryResponseArg<T> {
13+
data: T | undefined;
14+
error: Error | undefined;
15+
}
16+
17+
export function mockUseQueryResponse<T>(
18+
arg: MockUseQueryResponseArg<T>,
19+
): MockUseQueryResponse<T> {
20+
const data = ref<T | undefined>(undefined);
21+
const error = ref<Error | undefined>(undefined);
22+
23+
const ready = (async () => {
24+
await Promise.resolve();
25+
data.value = arg.data;
26+
error.value = arg.error;
27+
})();
28+
29+
return onReady({ data, error }, ready) as MockUseQueryResponse<T>;
30+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { useSubscription } from "@urql/vue";
3+
import type { UseSubscriptionResponse } from "@urql/vue";
4+
import gql from "graphql-tag";
5+
import fc from "fast-check";
6+
7+
import { mockUseSubscriptionResponse } from "./mock-use-subscription-response";
8+
9+
vi.mock("@urql/vue", () => ({
10+
useSubscription: vi.fn(),
11+
}));
12+
13+
export const query = gql`
14+
subscription CtrlStateS {
15+
ctrlState
16+
}
17+
`;
18+
19+
export type Data = { ctrlState: string };
20+
21+
const fcState = fc.string({ minLength: 1 });
22+
const fcData = fc.oneof(fc.constant(undefined), fc.record({ ctrlState: fcState }));
23+
24+
const fcErrorInstance = fc.string().map((msg) => new Error(msg));
25+
const fcError = fc.oneof(fc.constant(undefined), fcErrorInstance);
26+
27+
const fcArg = fc.record({ data: fcData, error: fcError });
28+
29+
describe("mockUseSubscriptionResponse()", () => {
30+
beforeEach(() => {
31+
vi.clearAllMocks();
32+
});
33+
34+
afterEach(() => {
35+
vi.resetAllMocks();
36+
});
37+
38+
it("Property test", () => {
39+
fc.assert(
40+
fc.property(fc.array(fcArg), (arg) => {
41+
const { response, issue } = mockUseSubscriptionResponse<Data>(arg);
42+
43+
type Response = UseSubscriptionResponse<Data>;
44+
vi.mocked(useSubscription).mockReturnValue(response as Response);
45+
46+
// Assert the mock response is returned.
47+
const returned = useSubscription<Data>({ query });
48+
expect(returned).toBe(response);
49+
50+
// Assert the mocked values are issued to the subscription.
51+
for (const issued of issue) {
52+
expect(response.error.value).toBe(issued.error);
53+
expect(response.data.value).toStrictEqual(issued.data);
54+
}
55+
}),
56+
);
57+
});
58+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ref } from "vue";
2+
import type { Ref } from "vue";
3+
4+
interface MockUseSubscriptionResponseArgElement<T> {
5+
data: T | undefined;
6+
error: Error | undefined;
7+
}
8+
9+
type MockUseSubscriptionResponseArg<T> = Iterable<
10+
MockUseSubscriptionResponseArgElement<T>
11+
>;
12+
13+
interface MockUseSubscriptionResponse<T> {
14+
response: {
15+
data: Ref<T | undefined>;
16+
error: Ref<Error | undefined>;
17+
};
18+
issue: MockUseSubscriptionResponseArg<T>;
19+
}
20+
21+
export function mockUseSubscriptionResponse<T>(
22+
arg: MockUseSubscriptionResponseArg<T>,
23+
): MockUseSubscriptionResponse<T> {
24+
const data = ref<T | undefined>(undefined);
25+
const error = ref<Error | undefined>(undefined);
26+
27+
function* _issue() {
28+
for (const res of arg) {
29+
data.value = res.data;
30+
error.value = res.error;
31+
yield res;
32+
}
33+
}
34+
const issue = _issue();
35+
const response = { data, error };
36+
37+
return { response, issue } as MockUseSubscriptionResponse<T>;
38+
}
Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,37 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2-
import { ref } from "vue";
32
import fc from "fast-check";
43

4+
import type {
5+
CtrlStateQuery,
6+
CtrlStateSSubscription,
7+
} from "@/graphql/codegen/generated";
58
import {
69
useCtrlStateQuery,
710
useCtrlStateSSubscription,
811
} from "@/graphql/codegen/generated";
9-
import type { CtrlStateSSubscription } from "@/graphql/codegen/generated";
10-
import { onReady } from "@/utils/on-ready";
1112

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

15+
import { mockUseQueryResponse } from "./mock-use-query-response";
16+
import { mockUseSubscriptionResponse } from "./mock-use-subscription-response";
17+
1418
vi.mock("@/graphql/codegen/generated", () => ({
1519
useCtrlStateQuery: vi.fn(),
1620
useCtrlStateSSubscription: vi.fn(),
1721
}));
1822

19-
const fcState = fc.oneof(fc.constant(undefined), fc.string({ minLength: 1 }));
23+
const fcState = fc.string({ minLength: 1 });
24+
const fcQueryData = fc.oneof(
25+
fc.constant(undefined),
26+
fc.record({ ctrl: fc.record({ state: fcState }) }),
27+
);
28+
const fcSubData = fc.oneof(fc.constant(undefined), fc.record({ ctrlState: fcState }));
2029

2130
const fcErrorInstance = fc.string().map((msg) => new Error(msg));
2231
const fcError = fc.oneof(fc.constant(undefined), fcErrorInstance);
2332

24-
type Query = ReturnType<typeof useCtrlStateQuery>;
25-
type Sub = ReturnType<typeof useCtrlStateSSubscription>;
26-
27-
function createMockQuery(
28-
state_value: string | undefined,
29-
error_value: Error | undefined,
30-
): Query {
31-
type Data = NonNullable<Query["data"]["value"]>;
32-
const data = ref<Data | undefined>(undefined);
33-
const error = ref<Error | undefined>(undefined);
34-
35-
const ready = (async () => {
36-
await Promise.resolve();
37-
data.value = { ctrl: { state: state_value } } as Data;
38-
error.value = error_value;
39-
})();
40-
41-
return onReady({ data, error }, ready) as Query;
42-
}
43-
44-
function createMockSubscription(
45-
state_value: string | undefined,
46-
error_value: Error | undefined,
47-
): Sub {
48-
const data = ref<CtrlStateSSubscription | undefined>(undefined);
49-
const error = ref<Error | undefined>(undefined);
50-
51-
const ready = (async () => {
52-
await Promise.resolve();
53-
data.value = { ctrlState: state_value } as CtrlStateSSubscription;
54-
error.value = error_value;
55-
})();
56-
57-
return onReady({ data, error }, ready) as unknown as Sub;
58-
}
33+
const fcQueryArg = fc.record({ data: fcQueryData, error: fcError });
34+
const fcSubArg = fc.record({ data: fcSubData, error: fcError });
5935

6036
describe("useSubscribeState()", () => {
6137
beforeEach(() => {
@@ -68,26 +44,36 @@ describe("useSubscribeState()", () => {
6844

6945
it("Property test", async () => {
7046
await fc.assert(
71-
fc.asyncProperty(
72-
fcState,
73-
fcError,
74-
fcState,
75-
fcError,
76-
async (queryState, queryError, subState, subError) => {
77-
const query = createMockQuery(queryState, queryError);
78-
const sub = createMockSubscription(subState, subError);
79-
vi.mocked(useCtrlStateQuery).mockReturnValue(query);
80-
vi.mocked(useCtrlStateSSubscription).mockReturnValue(sub);
81-
const { state, error } = await useSubscribeState();
82-
const expectedError = subError || queryError;
47+
fc.asyncProperty(fcQueryArg, fc.array(fcSubArg), async (queryArg, subArg) => {
48+
// Mock useCtrlStateQuery()
49+
const queryRes = mockUseQueryResponse<CtrlStateQuery>(queryArg);
50+
type Query = ReturnType<typeof useCtrlStateQuery>;
51+
vi.mocked(useCtrlStateQuery).mockReturnValue(queryRes as Query);
52+
53+
// Mock useCtrlStateSSubscription()
54+
const { response: subRes, issue } =
55+
mockUseSubscriptionResponse<CtrlStateSSubscription>(subArg);
56+
type SubRes = ReturnType<typeof useCtrlStateSSubscription>;
57+
vi.mocked(useCtrlStateSSubscription).mockReturnValue(subRes as SubRes);
58+
59+
const { state, error } = await useSubscribeState();
60+
61+
// Assert initial values are from query.
62+
expect(error.value).toBe(queryArg.error);
63+
expect(state.value).toBe(
64+
queryArg.error ? undefined : queryArg.data?.ctrl.state,
65+
);
66+
67+
// Assert the subsequent values are issued from subscription backed up by query.
68+
for (const issued of issue) {
69+
const expectedError = issued.error || queryArg.error;
8370
const expectedState = expectedError
8471
? undefined
85-
: subState || queryState || undefined;
86-
72+
: issued.data?.ctrlState || queryArg.data?.ctrl.state;
8773
expect(error.value).toBe(expectedError);
8874
expect(state.value).toBe(expectedState);
89-
},
90-
),
75+
}
76+
}),
9177
);
9278
});
9379
});

0 commit comments

Comments
 (0)