@frontmcp/testing 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/esm/fixtures/index.mjs +2377 -0
  2. package/esm/index.mjs +4768 -0
  3. package/esm/matchers/index.mjs +646 -0
  4. package/esm/package.json +127 -0
  5. package/esm/playwright/index.mjs +19 -0
  6. package/esm/setup.mjs +680 -0
  7. package/fixtures/index.js +2418 -0
  8. package/index.js +4866 -0
  9. package/jest-preset.js +3 -3
  10. package/matchers/index.js +673 -0
  11. package/package.json +52 -24
  12. package/playwright/index.js +46 -0
  13. package/setup.js +651 -0
  14. package/src/assertions/index.js +0 -18
  15. package/src/assertions/index.js.map +0 -1
  16. package/src/assertions/mcp-assertions.js +0 -220
  17. package/src/assertions/mcp-assertions.js.map +0 -1
  18. package/src/auth/auth-headers.js +0 -62
  19. package/src/auth/auth-headers.js.map +0 -1
  20. package/src/auth/index.js +0 -15
  21. package/src/auth/index.js.map +0 -1
  22. package/src/auth/mock-api-server.js +0 -200
  23. package/src/auth/mock-api-server.js.map +0 -1
  24. package/src/auth/mock-oauth-server.js +0 -253
  25. package/src/auth/mock-oauth-server.js.map +0 -1
  26. package/src/auth/token-factory.js +0 -181
  27. package/src/auth/token-factory.js.map +0 -1
  28. package/src/auth/user-fixtures.js +0 -92
  29. package/src/auth/user-fixtures.js.map +0 -1
  30. package/src/client/index.js +0 -12
  31. package/src/client/index.js.map +0 -1
  32. package/src/client/mcp-test-client.builder.js +0 -163
  33. package/src/client/mcp-test-client.builder.js.map +0 -1
  34. package/src/client/mcp-test-client.js +0 -937
  35. package/src/client/mcp-test-client.js.map +0 -1
  36. package/src/client/mcp-test-client.types.js +0 -16
  37. package/src/client/mcp-test-client.types.js.map +0 -1
  38. package/src/errors/index.js +0 -85
  39. package/src/errors/index.js.map +0 -1
  40. package/src/example-tools/index.js +0 -40
  41. package/src/example-tools/index.js.map +0 -1
  42. package/src/example-tools/tool-configs.js +0 -222
  43. package/src/example-tools/tool-configs.js.map +0 -1
  44. package/src/expect.js +0 -31
  45. package/src/expect.js.map +0 -1
  46. package/src/fixtures/fixture-types.js +0 -7
  47. package/src/fixtures/fixture-types.js.map +0 -1
  48. package/src/fixtures/index.js +0 -16
  49. package/src/fixtures/index.js.map +0 -1
  50. package/src/fixtures/test-fixture.js +0 -311
  51. package/src/fixtures/test-fixture.js.map +0 -1
  52. package/src/http-mock/http-mock.js +0 -544
  53. package/src/http-mock/http-mock.js.map +0 -1
  54. package/src/http-mock/http-mock.types.js +0 -10
  55. package/src/http-mock/http-mock.types.js.map +0 -1
  56. package/src/http-mock/index.js +0 -11
  57. package/src/http-mock/index.js.map +0 -1
  58. package/src/index.js +0 -167
  59. package/src/index.js.map +0 -1
  60. package/src/interceptor/index.js +0 -15
  61. package/src/interceptor/index.js.map +0 -1
  62. package/src/interceptor/interceptor-chain.js +0 -207
  63. package/src/interceptor/interceptor-chain.js.map +0 -1
  64. package/src/interceptor/interceptor.types.js +0 -7
  65. package/src/interceptor/interceptor.types.js.map +0 -1
  66. package/src/interceptor/mock-registry.js +0 -189
  67. package/src/interceptor/mock-registry.js.map +0 -1
  68. package/src/matchers/index.js +0 -12
  69. package/src/matchers/index.js.map +0 -1
  70. package/src/matchers/matcher-types.js +0 -10
  71. package/src/matchers/matcher-types.js.map +0 -1
  72. package/src/matchers/mcp-matchers.js +0 -395
  73. package/src/matchers/mcp-matchers.js.map +0 -1
  74. package/src/platform/index.js +0 -47
  75. package/src/platform/index.js.map +0 -1
  76. package/src/platform/platform-client-info.js +0 -155
  77. package/src/platform/platform-client-info.js.map +0 -1
  78. package/src/platform/platform-types.js +0 -110
  79. package/src/platform/platform-types.js.map +0 -1
  80. package/src/playwright/index.js +0 -49
  81. package/src/playwright/index.js.map +0 -1
  82. package/src/server/index.js +0 -10
  83. package/src/server/index.js.map +0 -1
  84. package/src/server/test-server.js +0 -341
  85. package/src/server/test-server.js.map +0 -1
  86. package/src/setup.js +0 -30
  87. package/src/setup.js.map +0 -1
  88. package/src/transport/index.js +0 -10
  89. package/src/transport/index.js.map +0 -1
  90. package/src/transport/streamable-http.transport.js +0 -438
  91. package/src/transport/streamable-http.transport.js.map +0 -1
  92. package/src/transport/transport.interface.js +0 -7
  93. package/src/transport/transport.interface.js.map +0 -1
  94. package/src/ui/index.js +0 -23
  95. package/src/ui/index.js.map +0 -1
  96. package/src/ui/ui-assertions.js +0 -367
  97. package/src/ui/ui-assertions.js.map +0 -1
  98. package/src/ui/ui-matchers.js +0 -493
  99. package/src/ui/ui-matchers.js.map +0 -1
  100. /package/{src/assertions → assertions}/index.d.ts +0 -0
  101. /package/{src/assertions → assertions}/mcp-assertions.d.ts +0 -0
  102. /package/{src/auth → auth}/auth-headers.d.ts +0 -0
  103. /package/{src/auth → auth}/index.d.ts +0 -0
  104. /package/{src/auth → auth}/mock-api-server.d.ts +0 -0
  105. /package/{src/auth → auth}/mock-oauth-server.d.ts +0 -0
  106. /package/{src/auth → auth}/token-factory.d.ts +0 -0
  107. /package/{src/auth → auth}/user-fixtures.d.ts +0 -0
  108. /package/{src/client → client}/index.d.ts +0 -0
  109. /package/{src/client → client}/mcp-test-client.builder.d.ts +0 -0
  110. /package/{src/client → client}/mcp-test-client.d.ts +0 -0
  111. /package/{src/client → client}/mcp-test-client.types.d.ts +0 -0
  112. /package/{src/errors → errors}/index.d.ts +0 -0
  113. /package/{src/example-tools → example-tools}/index.d.ts +0 -0
  114. /package/{src/example-tools → example-tools}/tool-configs.d.ts +0 -0
  115. /package/{src/expect.d.ts → expect.d.ts} +0 -0
  116. /package/{src/fixtures → fixtures}/fixture-types.d.ts +0 -0
  117. /package/{src/fixtures → fixtures}/index.d.ts +0 -0
  118. /package/{src/fixtures → fixtures}/test-fixture.d.ts +0 -0
  119. /package/{src/http-mock → http-mock}/http-mock.d.ts +0 -0
  120. /package/{src/http-mock → http-mock}/http-mock.types.d.ts +0 -0
  121. /package/{src/http-mock → http-mock}/index.d.ts +0 -0
  122. /package/{src/index.d.ts → index.d.ts} +0 -0
  123. /package/{src/interceptor → interceptor}/index.d.ts +0 -0
  124. /package/{src/interceptor → interceptor}/interceptor-chain.d.ts +0 -0
  125. /package/{src/interceptor → interceptor}/interceptor.types.d.ts +0 -0
  126. /package/{src/interceptor → interceptor}/mock-registry.d.ts +0 -0
  127. /package/{src/matchers → matchers}/index.d.ts +0 -0
  128. /package/{src/matchers → matchers}/matcher-types.d.ts +0 -0
  129. /package/{src/matchers → matchers}/mcp-matchers.d.ts +0 -0
  130. /package/{src/platform → platform}/index.d.ts +0 -0
  131. /package/{src/platform → platform}/platform-client-info.d.ts +0 -0
  132. /package/{src/platform → platform}/platform-types.d.ts +0 -0
  133. /package/{src/playwright → playwright}/index.d.ts +0 -0
  134. /package/{src/server → server}/index.d.ts +0 -0
  135. /package/{src/server → server}/test-server.d.ts +0 -0
  136. /package/{src/setup.d.ts → setup.d.ts} +0 -0
  137. /package/{src/transport → transport}/index.d.ts +0 -0
  138. /package/{src/transport → transport}/streamable-http.transport.d.ts +0 -0
  139. /package/{src/transport → transport}/transport.interface.d.ts +0 -0
  140. /package/{src/ui → ui}/index.d.ts +0 -0
  141. /package/{src/ui → ui}/ui-assertions.d.ts +0 -0
  142. /package/{src/ui → ui}/ui-matchers.d.ts +0 -0
@@ -1,189 +0,0 @@
1
- "use strict";
2
- /**
3
- * @file mock-registry.ts
4
- * @description Registry for managing request mocks
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.mockResponse = exports.DefaultMockRegistry = void 0;
8
- /**
9
- * Default implementation of MockRegistry
10
- */
11
- class DefaultMockRegistry {
12
- mocks = [];
13
- add(mock) {
14
- const entry = {
15
- definition: mock,
16
- callCount: 0,
17
- calls: [],
18
- remainingUses: mock.times ?? Infinity,
19
- };
20
- this.mocks.push(entry);
21
- return {
22
- remove: () => {
23
- const index = this.mocks.indexOf(entry);
24
- if (index !== -1) {
25
- this.mocks.splice(index, 1);
26
- }
27
- },
28
- callCount: () => entry.callCount,
29
- calls: () => [...entry.calls],
30
- };
31
- }
32
- clear() {
33
- this.mocks = [];
34
- }
35
- getAll() {
36
- return this.mocks.map((e) => e.definition);
37
- }
38
- match(request) {
39
- for (const entry of this.mocks) {
40
- if (entry.remainingUses <= 0)
41
- continue;
42
- const { definition } = entry;
43
- // Check method match
44
- if (definition.method !== request.method)
45
- continue;
46
- // Check params match if specified
47
- if (definition.params !== undefined) {
48
- const params = request.params ?? {};
49
- if (typeof definition.params === 'function') {
50
- // Custom matcher function
51
- if (!definition.params(params))
52
- continue;
53
- }
54
- else {
55
- // Object deep equality check
56
- if (!this.paramsMatch(definition.params, params))
57
- continue;
58
- }
59
- }
60
- // Found a match - update tracking
61
- entry.callCount++;
62
- entry.calls.push(request);
63
- entry.remainingUses--;
64
- return definition;
65
- }
66
- return undefined;
67
- }
68
- /**
69
- * Check if request params match the mock params definition
70
- */
71
- paramsMatch(expected, actual) {
72
- for (const [key, value] of Object.entries(expected)) {
73
- if (!(key in actual))
74
- return false;
75
- const actualValue = actual[key];
76
- // Handle arrays explicitly
77
- if (Array.isArray(value)) {
78
- if (!Array.isArray(actualValue))
79
- return false;
80
- if (value.length !== actualValue.length)
81
- return false;
82
- for (let i = 0; i < value.length; i++) {
83
- const expectedItem = value[i];
84
- const actualItem = actualValue[i];
85
- if (typeof expectedItem === 'object' && expectedItem !== null) {
86
- if (typeof actualItem !== 'object' || actualItem === null)
87
- return false;
88
- if (!this.paramsMatch(expectedItem, actualItem)) {
89
- return false;
90
- }
91
- }
92
- else if (actualItem !== expectedItem) {
93
- return false;
94
- }
95
- }
96
- }
97
- else if (typeof value === 'object' && value !== null) {
98
- if (typeof actualValue !== 'object' || actualValue === null)
99
- return false;
100
- if (!this.paramsMatch(value, actualValue)) {
101
- return false;
102
- }
103
- }
104
- else if (actualValue !== value) {
105
- return false;
106
- }
107
- }
108
- return true;
109
- }
110
- }
111
- exports.DefaultMockRegistry = DefaultMockRegistry;
112
- /**
113
- * Helper to create mock responses
114
- */
115
- exports.mockResponse = {
116
- /**
117
- * Create a successful JSON-RPC response
118
- */
119
- success(result, id = 1) {
120
- return {
121
- jsonrpc: '2.0',
122
- id,
123
- result,
124
- };
125
- },
126
- /**
127
- * Create an error JSON-RPC response
128
- */
129
- error(code, message, data, id = 1) {
130
- return {
131
- jsonrpc: '2.0',
132
- id,
133
- error: { code, message, data },
134
- };
135
- },
136
- /**
137
- * Create a tool result response
138
- */
139
- toolResult(content, id = 1) {
140
- return {
141
- jsonrpc: '2.0',
142
- id,
143
- result: { content },
144
- };
145
- },
146
- /**
147
- * Create a tools/list response
148
- */
149
- toolsList(tools, id = 1) {
150
- return {
151
- jsonrpc: '2.0',
152
- id,
153
- result: { tools },
154
- };
155
- },
156
- /**
157
- * Create a resources/list response
158
- */
159
- resourcesList(resources, id = 1) {
160
- return {
161
- jsonrpc: '2.0',
162
- id,
163
- result: { resources },
164
- };
165
- },
166
- /**
167
- * Create a resources/read response
168
- */
169
- resourceRead(contents, id = 1) {
170
- return {
171
- jsonrpc: '2.0',
172
- id,
173
- result: { contents },
174
- };
175
- },
176
- /**
177
- * Common MCP errors
178
- */
179
- errors: {
180
- methodNotFound: (method, id = 1) => exports.mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),
181
- invalidParams: (message, id = 1) => exports.mockResponse.error(-32602, message, undefined, id),
182
- internalError: (message, id = 1) => exports.mockResponse.error(-32603, message, undefined, id),
183
- resourceNotFound: (uri, id = 1) => exports.mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),
184
- toolNotFound: (name, id = 1) => exports.mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),
185
- unauthorized: (id = 1) => exports.mockResponse.error(-32001, 'Unauthorized', undefined, id),
186
- forbidden: (id = 1) => exports.mockResponse.error(-32003, 'Forbidden', undefined, id),
187
- },
188
- };
189
- //# sourceMappingURL=mock-registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mock-registry.js","sourceRoot":"","sources":["../../../src/interceptor/mock-registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mock-registry.ts\n * @description Registry for managing request mocks\n */\n\nimport type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';\nimport type { MockDefinition, MockRegistry, MockHandle } from './interceptor.types';\n\n/**\n * Internal mock entry with tracking\n */\ninterface MockEntry {\n definition: MockDefinition;\n callCount: number;\n calls: JsonRpcRequest[];\n remainingUses: number;\n}\n\n/**\n * Default implementation of MockRegistry\n */\nexport class DefaultMockRegistry implements MockRegistry {\n private mocks: MockEntry[] = [];\n\n add(mock: MockDefinition): MockHandle {\n const entry: MockEntry = {\n definition: mock,\n callCount: 0,\n calls: [],\n remainingUses: mock.times ?? Infinity,\n };\n\n this.mocks.push(entry);\n\n return {\n remove: () => {\n const index = this.mocks.indexOf(entry);\n if (index !== -1) {\n this.mocks.splice(index, 1);\n }\n },\n callCount: () => entry.callCount,\n calls: () => [...entry.calls],\n };\n }\n\n clear(): void {\n this.mocks = [];\n }\n\n getAll(): MockDefinition[] {\n return this.mocks.map((e) => e.definition);\n }\n\n match(request: JsonRpcRequest): MockDefinition | undefined {\n for (const entry of this.mocks) {\n if (entry.remainingUses <= 0) continue;\n\n const { definition } = entry;\n\n // Check method match\n if (definition.method !== request.method) continue;\n\n // Check params match if specified\n if (definition.params !== undefined) {\n const params = request.params ?? {};\n\n if (typeof definition.params === 'function') {\n // Custom matcher function\n if (!definition.params(params)) continue;\n } else {\n // Object deep equality check\n if (!this.paramsMatch(definition.params, params)) continue;\n }\n }\n\n // Found a match - update tracking\n entry.callCount++;\n entry.calls.push(request);\n entry.remainingUses--;\n\n return definition;\n }\n\n return undefined;\n }\n\n /**\n * Check if request params match the mock params definition\n */\n private paramsMatch(expected: Record<string, unknown>, actual: Record<string, unknown>): boolean {\n for (const [key, value] of Object.entries(expected)) {\n if (!(key in actual)) return false;\n\n const actualValue = actual[key];\n\n // Handle arrays explicitly\n if (Array.isArray(value)) {\n if (!Array.isArray(actualValue)) return false;\n if (value.length !== actualValue.length) return false;\n for (let i = 0; i < value.length; i++) {\n const expectedItem = value[i];\n const actualItem = actualValue[i];\n if (typeof expectedItem === 'object' && expectedItem !== null) {\n if (typeof actualItem !== 'object' || actualItem === null) return false;\n if (!this.paramsMatch(expectedItem as Record<string, unknown>, actualItem as Record<string, unknown>)) {\n return false;\n }\n } else if (actualItem !== expectedItem) {\n return false;\n }\n }\n } else if (typeof value === 'object' && value !== null) {\n if (typeof actualValue !== 'object' || actualValue === null) return false;\n if (!this.paramsMatch(value as Record<string, unknown>, actualValue as Record<string, unknown>)) {\n return false;\n }\n } else if (actualValue !== value) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n/**\n * Helper to create mock responses\n */\nexport const mockResponse = {\n /**\n * Create a successful JSON-RPC response\n */\n success<T>(result: T, id: string | number = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result,\n };\n },\n\n /**\n * Create an error JSON-RPC response\n */\n error(code: number, message: string, data?: unknown, id: string | number | null = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n error: { code, message, data },\n };\n },\n\n /**\n * Create a tool result response\n */\n toolResult(\n content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { content },\n };\n },\n\n /**\n * Create a tools/list response\n */\n toolsList(\n tools: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown> }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { tools },\n };\n },\n\n /**\n * Create a resources/list response\n */\n resourcesList(\n resources: Array<{ uri: string; name?: string; description?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { resources },\n };\n },\n\n /**\n * Create a resources/read response\n */\n resourceRead(\n contents: Array<{ uri: string; text?: string; blob?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { contents },\n };\n },\n\n /**\n * Common MCP errors\n */\n errors: {\n methodNotFound: (method: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),\n\n invalidParams: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32602, message, undefined, id),\n\n internalError: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32603, message, undefined, id),\n\n resourceNotFound: (uri: string, id: string | number | null = 1) =>\n mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),\n\n toolNotFound: (name: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),\n\n unauthorized: (id: string | number | null = 1) => mockResponse.error(-32001, 'Unauthorized', undefined, id),\n\n forbidden: (id: string | number | null = 1) => mockResponse.error(-32003, 'Forbidden', undefined, id),\n },\n};\n"]}
@@ -1,12 +0,0 @@
1
- "use strict";
2
- /**
3
- * @file index.ts
4
- * @description Barrel exports for MCP Jest matchers
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.mcpMatchers = void 0;
8
- var mcp_matchers_1 = require("./mcp-matchers");
9
- Object.defineProperty(exports, "mcpMatchers", { enumerable: true, get: function () { return mcp_matchers_1.mcpMatchers; } });
10
- // Import the type augmentation to make TypeScript aware of the matchers
11
- require("./matcher-types");
12
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/matchers/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAKpB,wEAAwE;AACxE,2BAAyB","sourcesContent":["/**\n * @file index.ts\n * @description Barrel exports for MCP Jest matchers\n */\n\nexport { mcpMatchers } from './mcp-matchers';\n\n// Re-export types for convenience\nexport type { McpMatchers } from './matcher-types';\n\n// Import the type augmentation to make TypeScript aware of the matchers\nimport './matcher-types';\n"]}
@@ -1,10 +0,0 @@
1
- "use strict";
2
- /**
3
- * @file matcher-types.ts
4
- * @description TypeScript declarations for custom MCP Jest matchers
5
- *
6
- * This file extends Jest's expect interface with MCP-specific matchers.
7
- * Import this file or ensure it's included in your tsconfig to get type checking.
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- //# sourceMappingURL=matcher-types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"matcher-types.js","sourceRoot":"","sources":["../../../src/matchers/matcher-types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * @file matcher-types.ts\n * @description TypeScript declarations for custom MCP Jest matchers\n *\n * This file extends Jest's expect interface with MCP-specific matchers.\n * Import this file or ensure it's included in your tsconfig to get type checking.\n */\n\n// Note: These imports are used for documentation/JSDoc purposes in the interface comments\n// The actual runtime types are in mcp-matchers.ts\n\n/**\n * Custom MCP matchers for Jest\n */\nexport interface McpMatchers<R = unknown> {\n // ═══════════════════════════════════════════════════════════════════\n // TOOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tools array contains a tool with the given name\n * @param toolName - The name of the tool to find\n *\n * @example\n * ```typescript\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * ```\n */\n toContainTool(toolName: string): R;\n\n /**\n * Check if result is successful (not an error)\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * ```\n */\n toBeSuccessful(): R;\n\n /**\n * Check if result is an error, optionally with a specific error code\n * @param expectedCode - Optional specific MCP error code to match\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('unknown-tool', {});\n * expect(result).toBeError();\n * expect(result).toBeError(-32601); // Method not found\n * ```\n */\n toBeError(expectedCode?: number): R;\n\n /**\n * Check if tool result has text content, optionally containing specific text\n * @param expectedText - Optional text that should be contained in the result\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toHaveTextContent();\n * expect(result).toHaveTextContent('success');\n * ```\n */\n toHaveTextContent(expectedText?: string): R;\n\n /**\n * Check if tool result has image content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('generate-image', {});\n * expect(result).toHaveImageContent();\n * ```\n */\n toHaveImageContent(): R;\n\n /**\n * Check if tool result has resource content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('create-file', {});\n * expect(result).toHaveResourceContent();\n * ```\n */\n toHaveResourceContent(): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCE MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if resources array contains a resource with the given URI\n * @param uri - The URI of the resource to find\n *\n * @example\n * ```typescript\n * const resources = await mcp.resources.list();\n * expect(resources).toContainResource('notes://all');\n * ```\n */\n toContainResource(uri: string): R;\n\n /**\n * Check if resource templates array contains a template with the given URI template\n * @param uriTemplate - The URI template to find\n *\n * @example\n * ```typescript\n * const templates = await mcp.resources.listTemplates();\n * expect(templates).toContainResourceTemplate('notes://note/{id}');\n * ```\n */\n toContainResourceTemplate(uriTemplate: string): R;\n\n /**\n * Check if resource content has a specific MIME type\n * @param mimeType - The expected MIME type\n *\n * @example\n * ```typescript\n * const content = await mcp.resources.read('notes://all');\n * expect(content).toHaveMimeType('application/json');\n * ```\n */\n toHaveMimeType(mimeType: string): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPT MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if prompts array contains a prompt with the given name\n * @param name - The name of the prompt to find\n *\n * @example\n * ```typescript\n * const prompts = await mcp.prompts.list();\n * expect(prompts).toContainPrompt('summarize');\n * ```\n */\n toContainPrompt(name: string): R;\n\n /**\n * Check if prompt result has a specific number of messages\n * @param count - The expected number of messages\n *\n * @example\n * ```typescript\n * const result = await mcp.prompts.get('summarize', {});\n * expect(result).toHaveMessages(2);\n * ```\n */\n toHaveMessages(count: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROTOCOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if response is valid JSON-RPC 2.0\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toBeValidJsonRpc();\n * ```\n */\n toBeValidJsonRpc(): R;\n\n /**\n * Check if JSON-RPC response has a result\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toHaveResult();\n * ```\n */\n toHaveResult(): R;\n\n /**\n * Check if JSON-RPC response has an error\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveError();\n * ```\n */\n toHaveError(): R;\n\n /**\n * Check if JSON-RPC response has a specific error code\n * @param code - The expected JSON-RPC error code\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveErrorCode(-32601);\n * ```\n */\n toHaveErrorCode(code: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // UI MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tool result has rendered HTML in _meta['ui/html'].\n * Fails if the HTML is the mdx-fallback (escaped raw content).\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('get_weather', { location: 'London' });\n * expect(result).toHaveRenderedHtml();\n * ```\n */\n toHaveRenderedHtml(): R;\n\n /**\n * Check if HTML contains a specific HTML element tag.\n * @param tag - The HTML tag name to look for (e.g., 'div', 'h1', 'span')\n *\n * @example\n * ```typescript\n * expect(result).toContainHtmlElement('div');\n * expect(result).toContainHtmlElement('h1');\n * ```\n */\n toContainHtmlElement(tag: string): R;\n\n /**\n * Check if a bound value from tool output appears in the rendered HTML.\n * @param value - The value to look for (string or number)\n *\n * @example\n * ```typescript\n * const output = result.json();\n * expect(result).toContainBoundValue(output.location);\n * expect(result).toContainBoundValue(output.temperature);\n * ```\n */\n toContainBoundValue(value: string | number): R;\n\n /**\n * Check if HTML is XSS-safe (no script tags, event handlers, or javascript: URIs).\n *\n * @example\n * ```typescript\n * expect(result).toBeXssSafe();\n * ```\n */\n toBeXssSafe(): R;\n\n /**\n * Check if tool result has widget metadata (ui/resourceUri).\n *\n * @example\n * ```typescript\n * expect(result).toHaveWidgetMetadata();\n * ```\n */\n toHaveWidgetMetadata(): R;\n\n /**\n * Check if HTML has a specific CSS class.\n * @param className - The CSS class name to look for\n *\n * @example\n * ```typescript\n * expect(result).toHaveCssClass('weather-card');\n * ```\n */\n toHaveCssClass(className: string): R;\n\n /**\n * Check that HTML does NOT contain specific content (useful for fallback checks).\n * @param content - The content that should NOT be in the HTML\n *\n * @example\n * ```typescript\n * expect(result).toNotContainRawContent('mdx-fallback');\n * expect(result).toNotContainRawContent('<Alert'); // Custom component should be rendered\n * ```\n */\n toNotContainRawContent(content: string): R;\n\n /**\n * Check if HTML has proper structure (not just escaped text).\n *\n * @example\n * ```typescript\n * expect(result).toHaveProperHtmlStructure();\n * ```\n */\n toHaveProperHtmlStructure(): R;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// JEST TYPE AUGMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\n/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\ndeclare global {\n namespace jest {\n // Extend expect matchers\n interface Matchers<R> extends McpMatchers<R> {}\n\n // Extend asymmetric matchers (for expect.toContainTool etc.)\n interface Expect extends McpMatchers<void> {}\n\n // Extend inverse matchers (for expect.not.toContainTool etc.)\n interface InverseAsymmetricMatchers extends McpMatchers<void> {}\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\n// This export is needed to make this a module\nexport {};\n"]}
@@ -1,395 +0,0 @@
1
- "use strict";
2
- /**
3
- * @file mcp-matchers.ts
4
- * @description Custom Jest matchers for MCP testing
5
- *
6
- * @example
7
- * ```typescript
8
- * import { test, expect } from '@frontmcp/testing';
9
- *
10
- * test('tools work', async ({ mcp }) => {
11
- * const tools = await mcp.tools.list();
12
- * expect(tools).toContainTool('my-tool');
13
- *
14
- * const result = await mcp.tools.call('my-tool', {});
15
- * expect(result).toBeSuccessful();
16
- * expect(result).toHaveTextContent();
17
- * });
18
- * ```
19
- */
20
- Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.mcpMatchers = void 0;
22
- const ui_matchers_1 = require("../ui/ui-matchers");
23
- // ═══════════════════════════════════════════════════════════════════
24
- // TOOL MATCHERS
25
- // ═══════════════════════════════════════════════════════════════════
26
- /**
27
- * Check if tools array contains a tool with the given name
28
- */
29
- const toContainTool = function (received, toolName) {
30
- const tools = received;
31
- if (!Array.isArray(tools)) {
32
- return {
33
- pass: false,
34
- message: () => `Expected an array of tools, but received ${typeof received}`,
35
- };
36
- }
37
- const pass = tools.some((t) => t.name === toolName);
38
- const availableTools = tools.map((t) => t.name).join(', ');
39
- return {
40
- pass,
41
- message: () => pass
42
- ? `Expected tools not to contain "${toolName}"`
43
- : `Expected tools to contain "${toolName}", but got: [${availableTools}]`,
44
- };
45
- };
46
- /**
47
- * Check if result is successful (not an error)
48
- */
49
- const toBeSuccessful = function (received) {
50
- const result = received;
51
- if (typeof result !== 'object' || result === null || !('isSuccess' in result)) {
52
- return {
53
- pass: false,
54
- message: () => `Expected a result wrapper object with isSuccess property`,
55
- };
56
- }
57
- const pass = result.isSuccess;
58
- return {
59
- pass,
60
- message: () => pass
61
- ? 'Expected result not to be successful'
62
- : `Expected result to be successful, but got error: ${result.error?.message ?? 'unknown error'}`,
63
- };
64
- };
65
- /**
66
- * Check if result is an error, optionally with a specific error code
67
- */
68
- const toBeError = function (received, expectedCode) {
69
- const result = received;
70
- if (typeof result !== 'object' || result === null || !('isError' in result)) {
71
- return {
72
- pass: false,
73
- message: () => `Expected a result wrapper object with isError property`,
74
- };
75
- }
76
- let pass = result.isError;
77
- if (pass && expectedCode !== undefined) {
78
- pass = result.error?.code === expectedCode;
79
- }
80
- return {
81
- pass,
82
- message: () => {
83
- if (!result.isError) {
84
- return 'Expected result to be an error, but it was successful';
85
- }
86
- if (expectedCode !== undefined && result.error?.code !== expectedCode) {
87
- return `Expected error code ${expectedCode}, but got ${result.error?.code}`;
88
- }
89
- return 'Expected result not to be an error';
90
- },
91
- };
92
- };
93
- /**
94
- * Check if tool result or resource content has text content, optionally containing specific text
95
- * Works with both ToolResultWrapper and ResourceContentWrapper
96
- */
97
- const toHaveTextContent = function (received, expectedText) {
98
- const result = received;
99
- // Check if it's a valid wrapper object with text() method
100
- if (typeof result !== 'object' || result === null || !('text' in result)) {
101
- return {
102
- pass: false,
103
- message: () => `Expected a ToolResultWrapper or ResourceContentWrapper object with text method`,
104
- };
105
- }
106
- // Get text content - works for both wrapper types
107
- const text = result.text();
108
- // Check if has text content - ToolResultWrapper has hasTextContent, ResourceContentWrapper uses text() !== undefined
109
- const hasText = 'hasTextContent' in result ? result.hasTextContent() : text !== undefined;
110
- let pass = hasText;
111
- if (pass && expectedText !== undefined) {
112
- pass = text?.includes(expectedText) ?? false;
113
- }
114
- return {
115
- pass,
116
- message: () => {
117
- if (!hasText) {
118
- return 'Expected result to have text content';
119
- }
120
- if (expectedText !== undefined && !text?.includes(expectedText)) {
121
- return `Expected text to contain "${expectedText}", but got: "${text}"`;
122
- }
123
- return 'Expected result not to have text content';
124
- },
125
- };
126
- };
127
- /**
128
- * Check if tool result has image content
129
- */
130
- const toHaveImageContent = function (received) {
131
- const result = received;
132
- if (typeof result !== 'object' || result === null || !('hasImageContent' in result)) {
133
- return {
134
- pass: false,
135
- message: () => `Expected a ToolResultWrapper object with hasImageContent method`,
136
- };
137
- }
138
- const pass = result.hasImageContent();
139
- return {
140
- pass,
141
- message: () => (pass ? 'Expected result not to have image content' : 'Expected result to have image content'),
142
- };
143
- };
144
- /**
145
- * Check if tool result has resource content
146
- */
147
- const toHaveResourceContent = function (received) {
148
- const result = received;
149
- if (typeof result !== 'object' || result === null || !('hasResourceContent' in result)) {
150
- return {
151
- pass: false,
152
- message: () => `Expected a ToolResultWrapper object with hasResourceContent method`,
153
- };
154
- }
155
- const pass = result.hasResourceContent();
156
- return {
157
- pass,
158
- message: () => (pass ? 'Expected result not to have resource content' : 'Expected result to have resource content'),
159
- };
160
- };
161
- // ═══════════════════════════════════════════════════════════════════
162
- // RESOURCE MATCHERS
163
- // ═══════════════════════════════════════════════════════════════════
164
- /**
165
- * Check if resources array contains a resource with the given URI
166
- */
167
- const toContainResource = function (received, uri) {
168
- const resources = received;
169
- if (!Array.isArray(resources)) {
170
- return {
171
- pass: false,
172
- message: () => `Expected an array of resources, but received ${typeof received}`,
173
- };
174
- }
175
- const pass = resources.some((r) => r.uri === uri);
176
- const availableUris = resources.map((r) => r.uri).join(', ');
177
- return {
178
- pass,
179
- message: () => pass
180
- ? `Expected resources not to contain "${uri}"`
181
- : `Expected resources to contain "${uri}", but got: [${availableUris}]`,
182
- };
183
- };
184
- /**
185
- * Check if resource templates array contains a template with the given URI template
186
- */
187
- const toContainResourceTemplate = function (received, uriTemplate) {
188
- const templates = received;
189
- if (!Array.isArray(templates)) {
190
- return {
191
- pass: false,
192
- message: () => `Expected an array of resource templates, but received ${typeof received}`,
193
- };
194
- }
195
- const pass = templates.some((t) => t.uriTemplate === uriTemplate);
196
- const availableTemplates = templates.map((t) => t.uriTemplate).join(', ');
197
- return {
198
- pass,
199
- message: () => pass
200
- ? `Expected templates not to contain "${uriTemplate}"`
201
- : `Expected templates to contain "${uriTemplate}", but got: [${availableTemplates}]`,
202
- };
203
- };
204
- /**
205
- * Check if resource content has a specific MIME type
206
- */
207
- const toHaveMimeType = function (received, mimeType) {
208
- const result = received;
209
- if (typeof result !== 'object' || result === null || !('hasMimeType' in result)) {
210
- return {
211
- pass: false,
212
- message: () => `Expected a ResourceContentWrapper object with hasMimeType method`,
213
- };
214
- }
215
- const pass = result.hasMimeType(mimeType);
216
- const actualMimeType = result.mimeType();
217
- return {
218
- pass,
219
- message: () => pass
220
- ? `Expected content not to have MIME type "${mimeType}"`
221
- : `Expected MIME type "${mimeType}", but got "${actualMimeType}"`,
222
- };
223
- };
224
- // ═══════════════════════════════════════════════════════════════════
225
- // PROMPT MATCHERS
226
- // ═══════════════════════════════════════════════════════════════════
227
- /**
228
- * Check if prompts array contains a prompt with the given name
229
- */
230
- const toContainPrompt = function (received, name) {
231
- const prompts = received;
232
- if (!Array.isArray(prompts)) {
233
- return {
234
- pass: false,
235
- message: () => `Expected an array of prompts, but received ${typeof received}`,
236
- };
237
- }
238
- const pass = prompts.some((p) => p.name === name);
239
- const availablePrompts = prompts.map((p) => p.name).join(', ');
240
- return {
241
- pass,
242
- message: () => pass
243
- ? `Expected prompts not to contain "${name}"`
244
- : `Expected prompts to contain "${name}", but got: [${availablePrompts}]`,
245
- };
246
- };
247
- /**
248
- * Check if prompt result has a specific number of messages
249
- */
250
- const toHaveMessages = function (received, count) {
251
- const result = received;
252
- if (typeof result !== 'object' || result === null || !('messages' in result)) {
253
- return {
254
- pass: false,
255
- message: () => `Expected a PromptResultWrapper object with messages property`,
256
- };
257
- }
258
- const actualCount = result.messages?.length ?? 0;
259
- const pass = actualCount === count;
260
- return {
261
- pass,
262
- message: () => pass
263
- ? `Expected prompt not to have ${count} messages`
264
- : `Expected prompt to have ${count} messages, but got ${actualCount}`,
265
- };
266
- };
267
- // ═══════════════════════════════════════════════════════════════════
268
- // PROTOCOL MATCHERS
269
- // ═══════════════════════════════════════════════════════════════════
270
- /**
271
- * Check if response is valid JSON-RPC 2.0
272
- * A valid JSON-RPC 2.0 response must have:
273
- * - jsonrpc: "2.0"
274
- * - id (matching the request, can be null for notifications)
275
- * - Either result OR error (but not both)
276
- */
277
- const toBeValidJsonRpc = function (received) {
278
- const response = received;
279
- if (typeof response !== 'object' || response === null) {
280
- return {
281
- pass: false,
282
- message: () => `Expected an object, but received ${typeof received}`,
283
- };
284
- }
285
- const hasJsonRpc = response['jsonrpc'] === '2.0';
286
- const hasId = 'id' in response;
287
- const hasResult = 'result' in response;
288
- const hasError = 'error' in response;
289
- const hasExactlyOneResultOrError = (hasResult || hasError) && !(hasResult && hasError);
290
- const pass = hasJsonRpc && hasId && hasExactlyOneResultOrError;
291
- return {
292
- pass,
293
- message: () => {
294
- if (pass) {
295
- return 'Expected response not to be valid JSON-RPC';
296
- }
297
- const issues = [];
298
- if (!hasJsonRpc)
299
- issues.push('missing or invalid "jsonrpc": "2.0"');
300
- if (!hasId)
301
- issues.push('missing "id" field');
302
- if (!hasExactlyOneResultOrError) {
303
- if (!hasResult && !hasError)
304
- issues.push('missing "result" or "error"');
305
- else
306
- issues.push('cannot have both "result" and "error"');
307
- }
308
- return `Expected valid JSON-RPC 2.0 response: ${issues.join(', ')}`;
309
- },
310
- };
311
- };
312
- /**
313
- * Check if JSON-RPC response has a result
314
- */
315
- const toHaveResult = function (received) {
316
- const response = received;
317
- if (typeof response !== 'object' || response === null) {
318
- return {
319
- pass: false,
320
- message: () => `Expected an object, but received ${typeof received}`,
321
- };
322
- }
323
- const pass = 'result' in response;
324
- return {
325
- pass,
326
- message: () => (pass ? 'Expected response not to have result' : 'Expected response to have result'),
327
- };
328
- };
329
- /**
330
- * Check if JSON-RPC response has an error
331
- */
332
- const toHaveError = function (received) {
333
- const response = received;
334
- if (typeof response !== 'object' || response === null) {
335
- return {
336
- pass: false,
337
- message: () => `Expected an object, but received ${typeof received}`,
338
- };
339
- }
340
- const pass = 'error' in response;
341
- return {
342
- pass,
343
- message: () => (pass ? 'Expected response not to have error' : 'Expected response to have error'),
344
- };
345
- };
346
- /**
347
- * Check if JSON-RPC response has a specific error code
348
- */
349
- const toHaveErrorCode = function (received, code) {
350
- const response = received;
351
- if (typeof response !== 'object' || response === null) {
352
- return {
353
- pass: false,
354
- message: () => `Expected an object, but received ${typeof received}`,
355
- };
356
- }
357
- const actualCode = response.error?.code;
358
- const pass = actualCode === code;
359
- return {
360
- pass,
361
- message: () => pass
362
- ? `Expected response not to have error code ${code}`
363
- : `Expected error code ${code}, but got ${actualCode ?? 'no error'}`,
364
- };
365
- };
366
- // ═══════════════════════════════════════════════════════════════════
367
- // EXPORTS
368
- // ═══════════════════════════════════════════════════════════════════
369
- /**
370
- * All MCP matchers as an object for expect.extend()
371
- */
372
- exports.mcpMatchers = {
373
- // Tool matchers
374
- toContainTool,
375
- toBeSuccessful,
376
- toBeError,
377
- toHaveTextContent,
378
- toHaveImageContent,
379
- toHaveResourceContent,
380
- // Resource matchers
381
- toContainResource,
382
- toContainResourceTemplate,
383
- toHaveMimeType,
384
- // Prompt matchers
385
- toContainPrompt,
386
- toHaveMessages,
387
- // Protocol matchers
388
- toBeValidJsonRpc,
389
- toHaveResult,
390
- toHaveError,
391
- toHaveErrorCode,
392
- // UI matchers (for testing tool UI responses)
393
- ...ui_matchers_1.uiMatchers,
394
- };
395
- //# sourceMappingURL=mcp-matchers.js.map