952 lines
25 KiB
TypeScript
952 lines
25 KiB
TypeScript
import assert from "node:assert";
|
|
|
|
import { afterEach, beforeEach, describe, it } from "@std/testing/bdd";
|
|
import { expect } from "expect";
|
|
import z from "zod";
|
|
|
|
(globalThis as any).assert = assert;
|
|
|
|
import { Collection } from "../src/collection.ts";
|
|
import { MemoryStorage } from "../src/databases/memory/storage.ts";
|
|
|
|
const schema = {
|
|
id: z.string(),
|
|
name: z.string().optional(),
|
|
emails: z.array(z.email()),
|
|
friends: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
type: z.union([z.literal("family"), z.literal("close")]),
|
|
}),
|
|
),
|
|
age: z.number(),
|
|
};
|
|
|
|
type UserSchema = typeof schema;
|
|
|
|
describe("Collection", { sanitizeOps: false, sanitizeResources: false }, () => {
|
|
let collection: Collection<{
|
|
name: string;
|
|
storage: MemoryStorage;
|
|
schema: UserSchema;
|
|
indexes: [{ field: "id"; kind: "primary" }];
|
|
}>;
|
|
|
|
beforeEach(() => {
|
|
collection = new Collection({
|
|
name: "test",
|
|
storage: new MemoryStorage("test", [
|
|
{
|
|
field: "id",
|
|
kind: "primary",
|
|
},
|
|
]),
|
|
schema: {
|
|
id: z.string(),
|
|
name: z.string().optional(),
|
|
fullName: z.string().optional(),
|
|
emails: z.array(z.email()),
|
|
friends: z.array(
|
|
z.object({
|
|
id: z.string(),
|
|
type: z.union([z.literal("family"), z.literal("close")]),
|
|
}),
|
|
),
|
|
age: z.number(),
|
|
},
|
|
indexes: [
|
|
{
|
|
field: "id",
|
|
kind: "primary",
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await collection.flush();
|
|
});
|
|
|
|
describe("Constructor and Properties", () => {
|
|
it("should initialize with correct name", () => {
|
|
expect(collection.name).toBe("test");
|
|
});
|
|
|
|
it("should have correct schema", () => {
|
|
expect(collection.schema).toBeDefined();
|
|
expect(collection.schema.id).toBeDefined();
|
|
});
|
|
|
|
it("should have correct storage", () => {
|
|
expect(collection.storage).toBeDefined();
|
|
});
|
|
|
|
it("should identify primary key correctly", () => {
|
|
expect(collection.primaryKey).toBe("id");
|
|
});
|
|
|
|
it("should throw error when primary key is missing", () => {
|
|
expect(() => {
|
|
new Collection({
|
|
name: "invalid",
|
|
storage: new MemoryStorage("invalid", []),
|
|
schema: { id: z.string() },
|
|
indexes: [],
|
|
});
|
|
}).toThrow("missing required primary key assignment");
|
|
});
|
|
});
|
|
|
|
describe("Utilities", () => {
|
|
describe("getPrimaryKeyValue", () => {
|
|
it("should return primary key value from document", () => {
|
|
const doc = { id: "123", name: "Test" };
|
|
expect(collection.getPrimaryKeyValue(doc)).toBe("123");
|
|
});
|
|
|
|
it("should throw error when primary key is missing", () => {
|
|
const doc = { name: "Test" };
|
|
expect(() => collection.getPrimaryKeyValue(doc)).toThrow("Missing primary key");
|
|
});
|
|
|
|
it("should throw error when primary key is not a string", () => {
|
|
const doc = { id: 123, name: "Test" };
|
|
expect(() => collection.getPrimaryKeyValue(doc)).toThrow("Missing primary key");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Insert Operations", () => {
|
|
it("should insert a single document", async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
]);
|
|
|
|
const doc = await collection.findOne({ id: "1" });
|
|
expect(doc).toBeDefined();
|
|
expect(doc?.name).toBe("Alice");
|
|
});
|
|
|
|
it("should insert multiple documents", async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [],
|
|
age: 30,
|
|
},
|
|
]);
|
|
|
|
const count = await collection.count();
|
|
expect(count).toBe(2);
|
|
});
|
|
|
|
it("should validate documents against schema on insert", async () => {
|
|
await expect(
|
|
collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Invalid",
|
|
emails: ["not-an-email"],
|
|
friends: [],
|
|
age: 25,
|
|
} as any,
|
|
]),
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("Query Operations", () => {
|
|
beforeEach(async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [{ id: "2", type: "close" }],
|
|
age: 25,
|
|
},
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [{ id: "1", type: "family" }],
|
|
age: 30,
|
|
},
|
|
{
|
|
id: "3",
|
|
name: "Charlie",
|
|
emails: ["charlie@test.com"],
|
|
friends: [],
|
|
age: 35,
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("findOne", () => {
|
|
it("should find document by id", async () => {
|
|
const doc = await collection.findOne({ id: "1" });
|
|
expect(doc).toBeDefined();
|
|
expect(doc?.name).toBe("Alice");
|
|
});
|
|
|
|
it("should return undefined when no match found", async () => {
|
|
const doc = await collection.findOne({ id: "999" });
|
|
expect(doc).toBeUndefined();
|
|
});
|
|
|
|
it("should find document by field value", async () => {
|
|
const doc = await collection.findOne({ name: "Bob" });
|
|
expect(doc).toBeDefined();
|
|
expect(doc?.id).toBe("2");
|
|
});
|
|
|
|
it("should support comparison operators", async () => {
|
|
const doc = await collection.findOne({ age: { $gte: 30 } });
|
|
expect(doc).toBeDefined();
|
|
expect(doc?.age).toBeGreaterThanOrEqual(30);
|
|
});
|
|
|
|
it("should support empty condition", async () => {
|
|
const doc = await collection.findOne();
|
|
expect(doc).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("findMany", () => {
|
|
it("should find all documents", async () => {
|
|
const docs = await collection.findMany();
|
|
expect(docs).toHaveLength(3);
|
|
});
|
|
|
|
it("should find documents by condition", async () => {
|
|
const docs = await collection.findMany({ age: { $gte: 30 } });
|
|
expect(docs).toHaveLength(2);
|
|
expect(docs.every((d) => d.age >= 30)).toBe(true);
|
|
});
|
|
|
|
it("should support limit option", async () => {
|
|
const docs = await collection.findMany({}, { limit: 2 });
|
|
expect(docs).toHaveLength(2);
|
|
});
|
|
|
|
it("should support skip option", async () => {
|
|
const docs = await collection.findMany({}, { skip: 1 });
|
|
expect(docs).toHaveLength(2);
|
|
});
|
|
|
|
it("should support sort option", async () => {
|
|
const docs = await collection.findMany({}, { sort: { age: -1 } });
|
|
expect(docs[0].age).toBe(35);
|
|
expect(docs[2].age).toBe(25);
|
|
});
|
|
|
|
it("should support complex queries with $and", async () => {
|
|
const docs = await collection.findMany({
|
|
$and: [{ age: { $gte: 25 } }, { age: { $lte: 30 } }],
|
|
});
|
|
expect(docs).toHaveLength(2);
|
|
});
|
|
|
|
it("should support complex queries with $or", async () => {
|
|
const docs = await collection.findMany({
|
|
$or: [{ name: "Alice" }, { name: "Charlie" }],
|
|
});
|
|
expect(docs).toHaveLength(2);
|
|
});
|
|
|
|
it("should support array queries", async () => {
|
|
const docs = await collection.findMany({
|
|
emails: { $in: ["alice@test.com"] },
|
|
});
|
|
expect(docs).toHaveLength(1);
|
|
expect(docs[0].name).toBe("Alice");
|
|
});
|
|
|
|
it("should support nested object queries", async () => {
|
|
const docs = await collection.findMany({
|
|
"friends.type": "close",
|
|
});
|
|
expect(docs).toHaveLength(1);
|
|
expect(docs[0].name).toBe("Alice");
|
|
});
|
|
});
|
|
|
|
describe("count", () => {
|
|
it("should count all documents", async () => {
|
|
const count = await collection.count();
|
|
expect(count).toBe(3);
|
|
});
|
|
|
|
it("should count documents matching condition", async () => {
|
|
const count = await collection.count({ age: { $gte: 30 } });
|
|
expect(count).toBe(2);
|
|
});
|
|
|
|
it("should return 0 when no matches", async () => {
|
|
const count = await collection.count({ age: { $gte: 100 } });
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Update Operations", () => {
|
|
beforeEach(async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "100",
|
|
name: "John Doe",
|
|
emails: ["john.doe@fixture.none"],
|
|
friends: [{ id: "201", type: "close" }],
|
|
age: 22,
|
|
},
|
|
{
|
|
id: "200",
|
|
name: "Jane Doe",
|
|
emails: ["jane.doe@fixture.none"],
|
|
friends: [],
|
|
age: 28,
|
|
},
|
|
]);
|
|
});
|
|
|
|
describe("$set operator", () => {
|
|
it("should set top level fields", async () => {
|
|
const result = await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$set: {
|
|
age: 32,
|
|
emails: ["john.doe@test.none"],
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result).toEqual({ matchedCount: 1, modifiedCount: 1 });
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(32);
|
|
expect(doc?.emails).toEqual(["john.doe@test.none"]);
|
|
});
|
|
|
|
it("should set nested fields", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$set: {
|
|
"friends.0.type": "family",
|
|
},
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.friends[0].type).toBe("family");
|
|
});
|
|
|
|
it("should update multiple documents", async () => {
|
|
const result = await collection.update(
|
|
{ age: { $gte: 20 } },
|
|
{
|
|
$set: { age: 50 },
|
|
},
|
|
);
|
|
|
|
expect(result.matchedCount).toBe(2);
|
|
expect(result.modifiedCount).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("$unset operator", () => {
|
|
it("should remove fields", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$unset: { name: "" },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.name).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("$inc operator", () => {
|
|
it("should increment numeric fields", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$inc: { age: 5 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(27);
|
|
});
|
|
|
|
it("should decrement with negative values", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$inc: { age: -2 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(20);
|
|
});
|
|
});
|
|
|
|
describe("$mul operator", () => {
|
|
it("should multiply numeric fields", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$mul: { age: 2 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(44);
|
|
});
|
|
});
|
|
|
|
describe("$min and $max operators", () => {
|
|
it("should update only if new value is smaller ($min)", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$min: { age: 20 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(20);
|
|
});
|
|
|
|
it("should not update if current value is smaller ($min)", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$min: { age: 30 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(22);
|
|
});
|
|
|
|
it("should update only if new value is larger ($max)", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$max: { age: 30 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.age).toBe(30);
|
|
});
|
|
});
|
|
|
|
// TODO: TypeError: Cannot destructure property 'node' of 'params[key]' as it is undefined.
|
|
// describe("$rename operator", () => {
|
|
// it("should rename fields", async () => {
|
|
// await collection.update(
|
|
// { id: "100" },
|
|
// {
|
|
// $rename: { name: "fullName" },
|
|
// },
|
|
// );
|
|
|
|
// const doc = await collection.findOne({ id: "100" });
|
|
// expect(doc?.fullName).toBe("John Doe");
|
|
// expect(doc?.name).toBeUndefined();
|
|
// });
|
|
// });
|
|
|
|
describe("Array update operators", () => {
|
|
describe("$push", () => {
|
|
it("should push item to array", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$push: { emails: "new@test.com" },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails).toContain("new@test.com");
|
|
expect(doc?.emails).toHaveLength(2);
|
|
});
|
|
|
|
it("should push object to array", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$push: { friends: { id: "300", type: "family" } },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.friends).toHaveLength(2);
|
|
expect(doc?.friends[1].id).toBe("300");
|
|
});
|
|
});
|
|
|
|
describe("$pull", () => {
|
|
it("should pull item from array", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$pull: { emails: "john.doe@fixture.none" },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails).toHaveLength(0);
|
|
});
|
|
|
|
it("should pull object from array by condition", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$pull: { friends: { id: "201" } },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.friends).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("$addToSet", () => {
|
|
it("should add unique item to array", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$addToSet: { emails: "new@test.com" },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails).toContain("new@test.com");
|
|
});
|
|
|
|
it("should not add duplicate item to array", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$addToSet: { emails: "john.doe@fixture.none" },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("$pop", () => {
|
|
it("should remove last element with 1", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$push: { emails: "extra@test.com" },
|
|
},
|
|
);
|
|
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$pop: { emails: 1 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails[0]).toBe("john.doe@fixture.none");
|
|
});
|
|
|
|
it("should remove first element with -1", async () => {
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$push: { emails: "extra@test.com" },
|
|
},
|
|
);
|
|
|
|
await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$pop: { emails: -1 },
|
|
},
|
|
);
|
|
|
|
const doc = await collection.findOne({ id: "100" });
|
|
expect(doc?.emails[0]).toBe("extra@test.com");
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should return matched and modified counts", async () => {
|
|
const result = await collection.update(
|
|
{ age: { $gte: 25 } },
|
|
{
|
|
$set: { age: 40 },
|
|
},
|
|
);
|
|
|
|
expect(result.matchedCount).toBeGreaterThan(0);
|
|
expect(result.modifiedCount).toBe(result.matchedCount);
|
|
});
|
|
|
|
it("should return 0 modified when no changes made", async () => {
|
|
const result = await collection.update(
|
|
{ id: "100" },
|
|
{
|
|
$set: { age: 22 },
|
|
},
|
|
);
|
|
|
|
expect(result.matchedCount).toBe(1);
|
|
expect(result.modifiedCount).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Remove Operations", () => {
|
|
beforeEach(async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [],
|
|
age: 30,
|
|
},
|
|
{
|
|
id: "3",
|
|
name: "Charlie",
|
|
emails: ["charlie@test.com"],
|
|
friends: [],
|
|
age: 35,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("should remove single document", async () => {
|
|
const removed = await collection.remove({ id: "1" });
|
|
expect(removed).toBe(1);
|
|
|
|
const doc = await collection.findOne({ id: "1" });
|
|
expect(doc).toBeUndefined();
|
|
});
|
|
|
|
it("should remove multiple documents", async () => {
|
|
const removed = await collection.remove({ age: { $gte: 30 } });
|
|
expect(removed).toBe(2);
|
|
|
|
const count = await collection.count();
|
|
expect(count).toBe(1);
|
|
});
|
|
|
|
it("should return 0 when no documents match", async () => {
|
|
const removed = await collection.remove({ id: "999" });
|
|
expect(removed).toBe(0);
|
|
});
|
|
|
|
it("should remove all documents with empty condition", async () => {
|
|
const removed = await collection.remove({});
|
|
expect(removed).toBe(3);
|
|
|
|
const count = await collection.count();
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Subscribe Operations", () => {
|
|
beforeEach(async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("should subscribe to single document changes", async () => {
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for subscription"));
|
|
}, 1000);
|
|
|
|
subscription = collection.subscribe({ id: "1" }, { limit: 1 }, (doc) => {
|
|
if (doc && doc.age === 30) {
|
|
clearTimeout(timeout);
|
|
expect(doc.name).toBe("Alice");
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
setTimeout(() => {
|
|
collection.update({ id: "1" }, { $set: { age: 30 } });
|
|
}, 10);
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
|
|
it("should subscribe to multiple document changes", async () => {
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for subscription"));
|
|
}, 1000);
|
|
|
|
subscription = collection.subscribe({}, {}, (docs) => {
|
|
if (docs.length > 1) {
|
|
clearTimeout(timeout);
|
|
expect(docs.length).toBeGreaterThan(1);
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
setTimeout(() => {
|
|
collection.insert([
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [],
|
|
age: 30,
|
|
},
|
|
]);
|
|
}, 10);
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
|
|
it("should unsubscribe successfully", async () => {
|
|
let callCount = 0;
|
|
const subscription = collection.subscribe({}, {}, () => {
|
|
callCount++;
|
|
});
|
|
|
|
subscription.unsubscribe();
|
|
|
|
await collection.insert([
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [],
|
|
age: 30,
|
|
},
|
|
]);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
expect(callCount).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("Event Handlers", () => {
|
|
it("should trigger onChange on insert", async () => {
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for onChange"));
|
|
}, 1000);
|
|
|
|
subscription = collection.onChange((event) => {
|
|
clearTimeout(timeout);
|
|
expect(event.type).toBe("insert");
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
});
|
|
|
|
collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
]);
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
|
|
it("should trigger onChange on update", async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
]);
|
|
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for onChange"));
|
|
}, 1000);
|
|
|
|
subscription = collection.onChange((event) => {
|
|
if (event.type === "update") {
|
|
clearTimeout(timeout);
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
collection.update({ id: "1" }, { $set: { age: 30 } });
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
|
|
it("should trigger onChange on remove", async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
]);
|
|
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for onChange"));
|
|
}, 1000);
|
|
|
|
subscription = collection.onChange((event) => {
|
|
if (event.type === "remove") {
|
|
clearTimeout(timeout);
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
collection.remove({ id: "1" });
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
|
|
it("should trigger onFlush when flush is called", async () => {
|
|
let subscription: any;
|
|
|
|
try {
|
|
const promise = new Promise<void>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
subscription?.unsubscribe();
|
|
reject(new Error("Timeout waiting for onFlush"));
|
|
}, 1000);
|
|
|
|
subscription = collection.onFlush(() => {
|
|
clearTimeout(timeout);
|
|
subscription.unsubscribe();
|
|
resolve();
|
|
});
|
|
|
|
collection.flush();
|
|
});
|
|
|
|
await promise;
|
|
} finally {
|
|
subscription?.unsubscribe();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Flush Operation", () => {
|
|
it("should clear all documents", async () => {
|
|
await collection.insert([
|
|
{
|
|
id: "1",
|
|
name: "Alice",
|
|
emails: ["alice@test.com"],
|
|
friends: [],
|
|
age: 25,
|
|
},
|
|
{
|
|
id: "2",
|
|
name: "Bob",
|
|
emails: ["bob@test.com"],
|
|
friends: [],
|
|
age: 30,
|
|
},
|
|
]);
|
|
|
|
await collection.flush();
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
|
|
const count = await collection.count();
|
|
expect(count).toBe(0);
|
|
});
|
|
});
|
|
});
|