convex
Version:
Client for the Convex Cloud
177 lines (154 loc) • 5.12 kB
text/typescript
import { Value } from "../values/index.js";
import {
test,
beforeEach,
afterEach,
expect,
vi,
MockedFunction,
} from "vitest";
import FakeWatch from "../test/fake_watch.js";
import { QueriesObserver } from "./queries_observer.js";
import { FunctionReference, anyApi } from "../server/api.js";
let queriesObserver: QueriesObserver;
let createWatch: MockedFunction<
(
query: FunctionReference<"query">,
args: Record<string, Value>,
) => FakeWatch<any>
>;
let listener: MockedFunction<() => void>;
beforeEach(() => {
createWatch = vi.fn(() => new FakeWatch<any>()) as any;
queriesObserver = new QueriesObserver(createWatch);
listener = vi.fn() as any;
queriesObserver.subscribe(listener);
});
afterEach(() => {
queriesObserver.destroy();
});
test("`getLocalResults`", () => {
const queries = {
query: {
query: anyApi.myQuery.default,
args: {},
},
};
queriesObserver.setQueries(queries);
expect(queriesObserver.getLocalResults(queries)).toStrictEqual({
query: undefined,
});
// The listener isn't notified for our own changes.
expect(listener.mock.calls.length).toBe(0);
// If we update the value of the query, the listener is notified and the
// local results include the update.
createWatch.mock.results[0].value.setValue("query value");
expect(listener.mock.calls.length).toBe(1);
createWatch.mockImplementation(() => {
const watch = new FakeWatch<any>();
watch.value = "query value";
return watch;
});
expect(queriesObserver.getLocalResults(queries)).toStrictEqual({
query: "query value",
});
});
test("If the query changes, only stay subscribed to the second query", () => {
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: {},
},
});
queriesObserver.setQueries({
query: {
query: anyApi.myQuery2.default,
args: {},
},
});
// The listener isn't notified for our own changes.
expect(listener.mock.calls.length).toBe(0);
// There were 2 watches created. We should be subscribed to only the second.
expect(createWatch.mock.calls.length).toBe(2);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(0);
expect(createWatch.mock.results[1].value.numCallbacks()).toBe(1);
});
test("If the query args change, only stay subscribed to the second args", () => {
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: { arg: "first arg" },
},
});
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: { arg: "new arg" },
},
});
// The listener isn't notified for our own changes.
expect(listener.mock.calls.length).toBe(0);
// There were 2 watches created. We should be subscribed to only the second.
expect(createWatch.mock.calls.length).toBe(2);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(0);
expect(createWatch.mock.results[1].value.numCallbacks()).toBe(1);
});
test("If the query doesn't change, we only have one subscription", () => {
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: { arg1: "arg1", arg2: 1, arg3: {} },
},
});
expect(createWatch.mock.calls.length).toBe(1);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(1);
// Re-adding the same query doesn't create a new watch.
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: { arg1: "arg1", arg2: 1, arg3: {} },
},
});
expect(createWatch.mock.calls.length).toBe(1);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(1);
});
test("destroy unsubscribes from all queries", async () => {
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: {},
},
});
queriesObserver.destroy();
// No subscriptions to the watch now.
expect(createWatch.mock.calls.length).toBe(1);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(0);
});
test("swapping createWatch recreates subscriptions", async () => {
queriesObserver.setQueries({
query: {
query: anyApi.myQuery.default,
args: {},
},
});
// We should have a listen for the query.
expect(createWatch.mock.calls.length).toBe(1);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(1);
// Pretend that the server sent us a query journal
createWatch.mock.results[0].value.setJournal("query journal");
// Swap out the `createWatch` function.
const createWatch2 = vi.fn(() => new FakeWatch<any>()) as any;
queriesObserver.setCreateWatch(createWatch2);
// No subscriptions left for the original watch.
expect(createWatch.mock.calls.length).toBe(1);
expect(createWatch.mock.results[0].value.numCallbacks()).toBe(0);
// Now there is a sub using the new createWatch function and the
// journal was passed through!
expect(createWatch2.mock.calls.length).toBe(1);
expect(createWatch2.mock.results[0].value.numCallbacks()).toBe(1);
expect(createWatch2.mock.calls[0]).toEqual([
anyApi.myQuery.default,
{},
"query journal",
]);
});