Skip to content

Commit a4ec2f0

Browse files
committed
chore: add ESLint flat config for v9 compatibility
- Create eslint.config.mjs for ESLint v9 flat config format - Disable @typescript-eslint/no-empty-object-type (pre-existing issues) - Remove unused imports from AppRenderer.tsx - Clean up AppRenderer.test.tsx (remove unused helper)
1 parent d2d350c commit a4ec2f0

File tree

4 files changed

+101
-65
lines changed

4 files changed

+101
-65
lines changed

eslint.config.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import typescriptEslint from '@typescript-eslint/eslint-plugin';
2+
import typescriptParser from '@typescript-eslint/parser';
3+
import reactPlugin from 'eslint-plugin-react';
4+
import prettier from 'eslint-config-prettier';
5+
6+
export default [
7+
{
8+
ignores: [
9+
'node_modules/**',
10+
'dist/**',
11+
'coverage/**',
12+
'**/*.log',
13+
'**/*.js',
14+
'**/*.mjs',
15+
'**/*.cjs',
16+
'sdks/typescript/client/src/remote-dom/iframe-bundle.ts',
17+
'examples/**',
18+
],
19+
},
20+
{
21+
files: ['**/*.ts', '**/*.tsx'],
22+
plugins: {
23+
'@typescript-eslint': typescriptEslint,
24+
react: reactPlugin,
25+
},
26+
languageOptions: {
27+
parser: typescriptParser,
28+
parserOptions: {
29+
ecmaVersion: 'latest',
30+
sourceType: 'module',
31+
ecmaFeatures: {
32+
jsx: true,
33+
},
34+
},
35+
},
36+
settings: {
37+
react: {
38+
version: 'detect',
39+
},
40+
},
41+
rules: {
42+
...typescriptEslint.configs.recommended.rules,
43+
...reactPlugin.configs.recommended.rules,
44+
...reactPlugin.configs['jsx-runtime'].rules,
45+
'react/prop-types': 'off',
46+
'@typescript-eslint/no-explicit-any': 'off',
47+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
48+
'@typescript-eslint/no-empty-object-type': 'off',
49+
},
50+
},
51+
prettier,
52+
];

sdks/typescript/client/src/components/AppRenderer.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ import {
2424
type McpUiOpenLinkRequest,
2525
type McpUiOpenLinkResult,
2626
type McpUiSizeChangedNotification,
27-
type McpUiToolInputNotification,
2827
type McpUiToolInputPartialNotification,
29-
type McpUiToolCancelledNotification,
3028
type McpUiHostContext,
3129
} from '@modelcontextprotocol/ext-apps/app-bridge';
3230

sdks/typescript/client/src/components/__tests__/AppFrame.test.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ import '@testing-library/jest-dom';
55
import { AppFrame, type AppFrameProps } from '../AppFrame';
66
import * as appHostUtils from '../../utils/app-host-utils';
77

8-
// Mock PostMessageTransport
9-
vi.mock('@modelcontextprotocol/ext-apps/app-bridge', async () => {
10-
const actual = await vi.importActual('@modelcontextprotocol/ext-apps/app-bridge');
11-
return {
12-
...actual,
13-
PostMessageTransport: vi.fn().mockImplementation(() => ({})),
14-
};
15-
});
8+
// Mock the ext-apps module
9+
vi.mock('@modelcontextprotocol/ext-apps/app-bridge', () => ({
10+
AppBridge: vi.fn(),
11+
PostMessageTransport: vi.fn().mockImplementation(() => ({})),
12+
}));
1613

1714
// Track registered handlers
1815
let registeredOninitialized: (() => void) | null = null;

sdks/typescript/client/src/components/__tests__/AppRenderer.test.tsx

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import React, { useRef } from 'react';
1+
import React from 'react';
22
import { render, screen, waitFor, act } from '@testing-library/react';
33
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
44
import '@testing-library/jest-dom';
55

66
import { AppRenderer, type AppRendererProps, type AppRendererHandle } from '../AppRenderer';
77
import * as appHostUtils from '../../utils/app-host-utils';
88

9-
// Track mock AppBridge instance
10-
let mockAppBridgeInstance: any = null;
11-
129
// Mock AppFrame to capture props
1310
const mockAppFrame = vi.fn();
1411
vi.mock('../AppFrame', () => ({
@@ -29,29 +26,34 @@ vi.mock('../../utils/app-host-utils', () => ({
2926
readToolUiResourceHtml: vi.fn(),
3027
}));
3128

29+
// Store mock bridge instance for test access
30+
let mockBridgeInstance: any = null;
31+
3232
// Mock AppBridge constructor
33-
vi.mock('@modelcontextprotocol/ext-apps/app-bridge', () => ({
34-
AppBridge: vi.fn().mockImplementation(() => {
35-
mockAppBridgeInstance = {
36-
onmessage: null,
37-
onopenlink: null,
38-
onloggingmessage: null,
39-
oncalltool: null,
40-
onlistresources: null,
41-
onlistresourcetemplates: null,
42-
onreadresource: null,
43-
onlistprompts: null,
44-
setHostContext: vi.fn(),
45-
sendToolInputPartial: vi.fn(),
46-
sendToolCancelled: vi.fn(),
47-
sendToolListChanged: vi.fn(),
48-
sendResourceListChanged: vi.fn(),
49-
sendPromptListChanged: vi.fn(),
50-
sendResourceTeardown: vi.fn(),
51-
};
52-
return mockAppBridgeInstance;
53-
}),
54-
}));
33+
vi.mock('@modelcontextprotocol/ext-apps/app-bridge', () => {
34+
return {
35+
AppBridge: vi.fn().mockImplementation(function() {
36+
mockBridgeInstance = {
37+
onmessage: null,
38+
onopenlink: null,
39+
onloggingmessage: null,
40+
oncalltool: null,
41+
onlistresources: null,
42+
onlistresourcetemplates: null,
43+
onreadresource: null,
44+
onlistprompts: null,
45+
setHostContext: vi.fn(),
46+
sendToolInputPartial: vi.fn(),
47+
sendToolCancelled: vi.fn(),
48+
sendToolListChanged: vi.fn(),
49+
sendResourceListChanged: vi.fn(),
50+
sendPromptListChanged: vi.fn(),
51+
sendResourceTeardown: vi.fn(),
52+
};
53+
return mockBridgeInstance;
54+
}),
55+
};
56+
});
5557

5658
// Mock MCP Client
5759
const mockClient = {
@@ -61,18 +63,6 @@ const mockClient = {
6163
}),
6264
};
6365

64-
// Helper component to test ref
65-
const AppRendererWithRef = (props: AppRendererProps & { onRef?: (handle: AppRendererHandle | null) => void }) => {
66-
const ref = useRef<AppRendererHandle>(null);
67-
68-
// Call onRef when ref is available
69-
if (props.onRef && ref.current) {
70-
props.onRef(ref.current);
71-
}
72-
73-
return <AppRenderer ref={ref} {...props} />;
74-
};
75-
7666
describe('<AppRenderer />', () => {
7767
const defaultProps: AppRendererProps = {
7868
client: mockClient as any,
@@ -82,7 +72,7 @@ describe('<AppRenderer />', () => {
8272

8373
beforeEach(() => {
8474
vi.clearAllMocks();
85-
mockAppBridgeInstance = null;
75+
mockBridgeInstance = null;
8676
mockAppFrame.mockClear();
8777

8878
// Default mock implementations
@@ -268,21 +258,21 @@ describe('<AppRenderer />', () => {
268258
render(<AppRenderer {...defaultProps} hostContext={hostContext} />);
269259

270260
await waitFor(() => {
271-
expect(mockAppBridgeInstance?.setHostContext).toHaveBeenCalledWith(hostContext);
261+
expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith(hostContext);
272262
});
273263
});
274264

275265
it('should update hostContext when prop changes', async () => {
276266
const { rerender } = render(<AppRenderer {...defaultProps} hostContext={{ theme: 'light' as const }} />);
277267

278268
await waitFor(() => {
279-
expect(mockAppBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'light' });
269+
expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'light' });
280270
});
281271

282272
rerender(<AppRenderer {...defaultProps} hostContext={{ theme: 'dark' as const }} />);
283273

284274
await waitFor(() => {
285-
expect(mockAppBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'dark' });
275+
expect(mockBridgeInstance?.setHostContext).toHaveBeenCalledWith({ theme: 'dark' });
286276
});
287277
});
288278
});
@@ -294,7 +284,7 @@ describe('<AppRenderer />', () => {
294284
render(<AppRenderer {...defaultProps} toolInputPartial={toolInputPartial} />);
295285

296286
await waitFor(() => {
297-
expect(mockAppBridgeInstance?.sendToolInputPartial).toHaveBeenCalledWith(toolInputPartial);
287+
expect(mockBridgeInstance?.sendToolInputPartial).toHaveBeenCalledWith(toolInputPartial);
298288
});
299289
});
300290
});
@@ -304,7 +294,7 @@ describe('<AppRenderer />', () => {
304294
render(<AppRenderer {...defaultProps} toolCancelled={true} />);
305295

306296
await waitFor(() => {
307-
expect(mockAppBridgeInstance?.sendToolCancelled).toHaveBeenCalledWith({});
297+
expect(mockBridgeInstance?.sendToolCancelled).toHaveBeenCalledWith({});
308298
});
309299
});
310300

@@ -315,7 +305,7 @@ describe('<AppRenderer />', () => {
315305
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
316306
});
317307

318-
expect(mockAppBridgeInstance?.sendToolCancelled).not.toHaveBeenCalled();
308+
expect(mockBridgeInstance?.sendToolCancelled).not.toHaveBeenCalled();
319309
});
320310
});
321311

@@ -329,7 +319,6 @@ describe('<AppRenderer />', () => {
329319
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
330320
});
331321

332-
// Wait for ref to be populated
333322
await waitFor(() => {
334323
expect(ref.current).not.toBeNull();
335324
});
@@ -338,7 +327,7 @@ describe('<AppRenderer />', () => {
338327
ref.current?.sendToolListChanged();
339328
});
340329

341-
expect(mockAppBridgeInstance?.sendToolListChanged).toHaveBeenCalled();
330+
expect(mockBridgeInstance?.sendToolListChanged).toHaveBeenCalled();
342331
});
343332

344333
it('should expose sendResourceListChanged via ref', async () => {
@@ -358,7 +347,7 @@ describe('<AppRenderer />', () => {
358347
ref.current?.sendResourceListChanged();
359348
});
360349

361-
expect(mockAppBridgeInstance?.sendResourceListChanged).toHaveBeenCalled();
350+
expect(mockBridgeInstance?.sendResourceListChanged).toHaveBeenCalled();
362351
});
363352

364353
it('should expose sendPromptListChanged via ref', async () => {
@@ -378,7 +367,7 @@ describe('<AppRenderer />', () => {
378367
ref.current?.sendPromptListChanged();
379368
});
380369

381-
expect(mockAppBridgeInstance?.sendPromptListChanged).toHaveBeenCalled();
370+
expect(mockBridgeInstance?.sendPromptListChanged).toHaveBeenCalled();
382371
});
383372

384373
it('should expose sendResourceTeardown via ref', async () => {
@@ -398,7 +387,7 @@ describe('<AppRenderer />', () => {
398387
ref.current?.sendResourceTeardown();
399388
});
400389

401-
expect(mockAppBridgeInstance?.sendResourceTeardown).toHaveBeenCalledWith({});
390+
expect(mockBridgeInstance?.sendResourceTeardown).toHaveBeenCalledWith({});
402391
});
403392
});
404393

@@ -413,7 +402,7 @@ describe('<AppRenderer />', () => {
413402
});
414403

415404
// The handler should be registered
416-
expect(mockAppBridgeInstance?.oncalltool).toBeDefined();
405+
expect(mockBridgeInstance?.oncalltool).toBeDefined();
417406
});
418407

419408
it('should register onListResources handler on AppBridge', async () => {
@@ -425,7 +414,7 @@ describe('<AppRenderer />', () => {
425414
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
426415
});
427416

428-
expect(mockAppBridgeInstance?.onlistresources).toBeDefined();
417+
expect(mockBridgeInstance?.onlistresources).toBeDefined();
429418
});
430419

431420
it('should register onListResourceTemplates handler on AppBridge', async () => {
@@ -437,7 +426,7 @@ describe('<AppRenderer />', () => {
437426
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
438427
});
439428

440-
expect(mockAppBridgeInstance?.onlistresourcetemplates).toBeDefined();
429+
expect(mockBridgeInstance?.onlistresourcetemplates).toBeDefined();
441430
});
442431

443432
it('should register onReadResource handler on AppBridge', async () => {
@@ -449,7 +438,7 @@ describe('<AppRenderer />', () => {
449438
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
450439
});
451440

452-
expect(mockAppBridgeInstance?.onreadresource).toBeDefined();
441+
expect(mockBridgeInstance?.onreadresource).toBeDefined();
453442
});
454443

455444
it('should register onListPrompts handler on AppBridge', async () => {
@@ -461,7 +450,7 @@ describe('<AppRenderer />', () => {
461450
expect(screen.getByTestId('app-frame')).toBeInTheDocument();
462451
});
463452

464-
expect(mockAppBridgeInstance?.onlistprompts).toBeDefined();
453+
expect(mockBridgeInstance?.onlistprompts).toBeDefined();
465454
});
466455
});
467456

0 commit comments

Comments
 (0)