Skip to content

Commit f950f7a

Browse files
committed
feat: use custom toIncludeSameMembers matcher
1 parent 52ccf24 commit f950f7a

File tree

8 files changed

+145
-5
lines changed

8 files changed

+145
-5
lines changed

tests/integration/tools/mongodb/create/createCollection.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describeWithMongoDB("createCollection tool", (integration) => {
5555
expect(content).toEqual(`Collection "collection2" created in database "${integration.randomDbName()}".`);
5656
collections = await mongoClient.db(integration.randomDbName()).listCollections().toArray();
5757
expect(collections).toHaveLength(2);
58-
expect(collections.map((c) => c.name)).toEqual(expect.arrayContaining(["collection1", "collection2"]));
58+
expect(collections.map((c) => c.name)).toIncludeSameMembers(["collection1", "collection2"]);
5959
});
6060

6161
it("does nothing if collection already exists", async () => {

tests/integration/tools/mongodb/metadata/listCollections.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ describeWithMongoDB("listCollections tool", (integration) => {
5656
});
5757
const items2 = getResponseElements(response2.content);
5858
expect(items2).toHaveLength(2);
59-
expect(items2.map((item) => item.text)).toEqual(
60-
expect.arrayContaining(['Name: "collection-1"', 'Name: "collection-2"'])
61-
);
59+
expect(items2.map((item) => item.text)).toIncludeSameMembers([
60+
'Name: "collection-1"',
61+
'Name: "collection-2"',
62+
]);
6263
});
6364
});
6465

tests/integration/tools/mongodb/metadata/listDatabases.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describeWithMongoDB("listDatabases tool", (integration) => {
3535

3636
const response = await integration.mcpClient().callTool({ name: "list-databases", arguments: {} });
3737
const dbNames = getDbNames(response.content);
38-
expect(dbNames).toEqual(expect.arrayContaining([...defaultDatabases, "foo", "baz"]));
38+
expect(dbNames).toIncludeSameMembers([...defaultDatabases, "foo", "baz"]);
3939
});
4040
});
4141

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
describe("toIncludeSameMembers matcher", () => {
4+
it("should pass when arrays contain the same elements in different order", () => {
5+
const array1 = [1, 2, 3];
6+
const array2 = [3, 1, 2];
7+
8+
expect(array1).toIncludeSameMembers(array2);
9+
});
10+
11+
it("should pass when arrays contain the same elements in same order", () => {
12+
const array1 = [1, 2, 3];
13+
const array2 = [1, 2, 3];
14+
15+
expect(array1).toIncludeSameMembers(array2);
16+
});
17+
18+
it("should fail when arrays have different lengths", () => {
19+
const array1 = [1, 2, 3];
20+
const array2 = [1, 2];
21+
22+
expect(() => expect(array1).toIncludeSameMembers(array2)).toThrow();
23+
});
24+
25+
it("should fail when arrays contain different elements", () => {
26+
const array1 = [1, 2, 3];
27+
const array2 = [4, 5, 6];
28+
29+
expect(() => expect(array1).toIncludeSameMembers(array2)).toThrow();
30+
});
31+
32+
it("should work with string arrays", () => {
33+
const array1 = ["apple", "banana", "cherry"];
34+
const array2 = ["cherry", "apple", "banana"];
35+
36+
expect(array1).toIncludeSameMembers(array2);
37+
});
38+
39+
it("should work with object arrays", () => {
40+
const array1 = [{ name: "Alice" }, { name: "Bob" }];
41+
const array2 = [{ name: "Bob" }, { name: "Alice" }];
42+
43+
expect(array1).toIncludeSameMembers(array2);
44+
});
45+
46+
it("should work with mixed type arrays", () => {
47+
const array1 = [1, "hello", { key: "value" }];
48+
const array2 = [{ key: "value" }, 1, "hello"];
49+
50+
expect(array1).toIncludeSameMembers(array2);
51+
});
52+
53+
it("should work with empty arrays", () => {
54+
const array1: unknown[] = [];
55+
const array2: unknown[] = [];
56+
57+
expect(array1).toIncludeSameMembers(array2);
58+
});
59+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { expect } from "vitest";
2+
3+
export function toIncludeSameMembers<T>(actual: T[], expected: T[]): { pass: boolean; message: () => string } {
4+
if (!Array.isArray(actual)) {
5+
return {
6+
pass: false,
7+
message: () => `Expected ${String(actual)} to be an array`,
8+
};
9+
}
10+
11+
if (!Array.isArray(expected)) {
12+
return {
13+
pass: false,
14+
message: () => `Expected ${String(expected)} to be an array`,
15+
};
16+
}
17+
18+
if (actual.length !== expected.length) {
19+
return {
20+
pass: false,
21+
message: () =>
22+
`Expected arrays to have the same length.\nExpected: ${expected.length}\nReceived: ${actual.length}`,
23+
};
24+
}
25+
26+
// Create copies to avoid modifying original arrays
27+
const actualCopy = [...actual];
28+
const expectedCopy = [...expected];
29+
30+
// Sort both arrays for comparison
31+
// Handle primitive values and objects differently
32+
const sortComparator = (a: T, b: T): number => {
33+
const aStr = JSON.stringify(a);
34+
const bStr = JSON.stringify(b);
35+
return aStr.localeCompare(bStr);
36+
};
37+
38+
actualCopy.sort(sortComparator);
39+
expectedCopy.sort(sortComparator);
40+
41+
// Check if sorted arrays are equal
42+
const areEqual = actualCopy.every((item, index) => {
43+
try {
44+
expect(item).toEqual(expectedCopy[index]);
45+
return true;
46+
} catch {
47+
return false;
48+
}
49+
});
50+
51+
return {
52+
pass: areEqual,
53+
message: () => {
54+
if (areEqual) {
55+
return `Expected arrays not to include the same members`;
56+
} else {
57+
return `Expected arrays to include the same members.\nExpected: ${JSON.stringify(expected)}\nReceived: ${JSON.stringify(actual)}`;
58+
}
59+
},
60+
};
61+
}

tests/setup.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { expect } from "vitest";
2+
import { toIncludeSameMembers } from "./matchers/toIncludeSameMembers.js";
3+
4+
// Extend vitest's expect with custom matchers
5+
expect.extend({
6+
toIncludeSameMembers,
7+
});

tests/vitest.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import "vitest";
2+
3+
declare module "vitest" {
4+
interface Assertion<T = unknown> {
5+
toIncludeSameMembers<U>(expected: U[]): T;
6+
}
7+
8+
interface AsymmetricMatchersContaining {
9+
toIncludeSameMembers<T>(expected: T[]): unknown;
10+
}
11+
}

vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default defineConfig({
66
testTimeout: 3600000,
77
hookTimeout: 3600000,
88
include: ["**/*.test.ts"],
9+
setupFiles: ["./tests/setup.ts"],
910
coverage: {
1011
exclude: ["node_modules", "tests", "dist"],
1112
reporter: ["lcov"],

0 commit comments

Comments
 (0)