/// import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vite.dev/config/ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; import { playwright } from '@vitest/browser-playwright'; const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(dirname, '../..'); // Aliases shared between the top-level resolve and the unit vitest project. // Vitest projects don't inherit `resolve` from the parent, so the unit project // redeclares them — keeping a single source here prevents the two from drifting // (e.g. if a new core/* alias is added). const sharedAliases = { '@inspector/core': path.resolve(dirname, '../../core'), '@modelcontextprotocol/inspector-test-server': path.resolve(dirname, '../../test-servers/src/index.ts'), }; const sharedDedupe = ['react', 'react-dom']; // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon export default defineConfig({ plugins: [react()], resolve: { // NOTE: the unit vitest project (below) overrides this — see comment there. alias: sharedAliases, // Source files in core/ import bare modules (react, @testing-library/react, // etc.) that only exist in clients/web/node_modules. Dedupe ensures Vite // resolves them from this package rather than walking up from core/'s // location (which has no node_modules of its own yet). dedupe: sharedDedupe, }, server: { fs: { allow: [path.resolve(dirname, '../..')], }, }, test: { coverage: { provider: 'v8', reporter: ['text', 'html', 'json-summary'], include: [ 'src/components/**/*.{ts,tsx}', 'src/utils/**/*.{ts,tsx}', path.join(repoRoot, 'core/mcp/**/*.{ts,tsx}'), path.join(repoRoot, 'core/react/**/*.{ts,tsx}'), ], exclude: [ '**/*.stories.{ts,tsx}', '**/*.test.{ts,tsx}', '**/*.fixtures.{ts,tsx}', '**/index.{ts,tsx}', 'src/components/**/types.ts', // Pure-type modules: `interface`/`type` declarations only, no runtime // statements. Excluding them keeps the report clean (would otherwise // surface as misleading 0/0 rows). path.join(repoRoot, 'core/mcp/types.ts'), path.join(repoRoot, 'core/mcp/elicitationCreateMessage.ts'), path.join(repoRoot, 'core/mcp/samplingCreateMessage.ts'), path.join(repoRoot, 'core/mcp/sessionStorage.ts'), path.join(repoRoot, 'core/mcp/inspectorClientProtocol.ts'), // inspectorClientEventTarget.ts is types + a single empty-body class // (extends TypedEventTarget). v8/istanbul records 0 statements for it // today. TODO(#1243): drop this exclusion once the class gains real // behavior as the v1.5 InspectorClient port progresses. path.join(repoRoot, 'core/mcp/inspectorClientEventTarget.ts'), path.join(repoRoot, 'core/mcp/__tests__/**'), // v1.5-ported runtime files (#1302) whose v1.5 tests are excluded from // the unit project pending a node-env vitest setup. Tracked in #1307 — // drop each entry below as the corresponding test family comes online. path.join(repoRoot, 'core/mcp/inspectorClient.ts'), path.join(repoRoot, 'core/mcp/oauthManager.ts'), path.join(repoRoot, 'core/mcp/fetchTracking.ts'), path.join(repoRoot, 'core/mcp/messageTrackingTransport.ts'), path.join(repoRoot, 'core/mcp/config.ts'), path.join(repoRoot, 'core/mcp/node/**'), path.join(repoRoot, 'core/mcp/remote/**'), path.join(repoRoot, 'core/auth/**'), path.join(repoRoot, 'core/storage/**'), path.join(repoRoot, 'core/logging/**'), path.join(repoRoot, 'test-servers/**'), ], thresholds: { perFile: true, lines: 90, statements: 85, functions: 80, branches: 50, }, }, projects: [ { extends: true, // Vitest projects don't inherit `resolve` from the parent, so we // redeclare the shared aliases here. The extra `react` alias is // required because we move the unit project root to repoRoot below // (so vitest's coverage transformer can reach core/), and the repo // root has no node_modules of its own — bare `react` imports from // core/react/*.ts would otherwise fail to resolve. resolve: { alias: [ // sharedAliases first as exact-match entries ...Object.entries(sharedAliases).map(([find, replacement]) => ({ find, replacement })), { find: /^react$/, replacement: path.resolve(dirname, 'node_modules/react') }, // v1.5 core/ modules (#1302) import these from clients/web/node_modules, // but the unit project runs from repoRoot (which has no node_modules of // its own). Use anchored regex `find` patterns so the package's own // `exports` field handles subpath resolution (otherwise a bare `hono` // string alias would rewrite `hono/streaming` to `/streaming`, // bypassing the exports map). { find: /^pino$/, replacement: path.resolve(dirname, 'node_modules/pino') }, { find: /^pino\/browser\.js$/, replacement: path.resolve(dirname, 'node_modules/pino/browser.js') }, { find: /^zustand$/, replacement: path.resolve(dirname, 'node_modules/zustand') }, { find: /^zustand\/middleware$/, replacement: path.resolve(dirname, 'node_modules/zustand/middleware.js') }, { find: /^zustand\/vanilla$/, replacement: path.resolve(dirname, 'node_modules/zustand/vanilla.js') }, { find: /^hono$/, replacement: path.resolve(dirname, 'node_modules/hono/dist/index.js') }, { find: /^hono\/streaming$/, replacement: path.resolve(dirname, 'node_modules/hono/dist/helper/streaming/index.js') }, { find: /^@hono\/node-server$/, replacement: path.resolve(dirname, 'node_modules/@hono/node-server') }, { find: /^atomically$/, replacement: path.resolve(dirname, 'node_modules/atomically') }, { find: /^express$/, replacement: path.resolve(dirname, 'node_modules/express') }, { find: /^yaml$/, replacement: path.resolve(dirname, 'node_modules/yaml') }, ], dedupe: sharedDedupe, }, test: { name: 'unit', environment: 'happy-dom', // Root the unit project at the repo root so vitest's coverage // transformer (which only processes files inside a project root) // can reach core/ modules. Without this, untested core/ files fall // back to raw-TS parsing in rolldown, which can't handle TS-only // syntax (e.g. `import type`) — silently dropping them and bypassing // the per-file gate. root: repoRoot, // No `globals: true` — every test file imports `describe`, `it`, // `expect`, `vi` explicitly from "vitest". This keeps the pattern // consistent and avoids relying on auto-cleanup tied to Vitest's // global lifecycle hooks; cleanup is invoked manually in setup.ts. include: ['clients/web/src/**/*.test.{ts,tsx}'], // These v1.5-ported tests need either a node-env vitest project // (they spawn real HTTP/stdio servers via test-servers/, run // end-to-end OAuth flows, or talk to fs/network) or substantial // happy-dom-friendly mocks. Tracked in #1307 — remove each entry // below as the corresponding test starts passing. exclude: [ 'clients/web/src/test/core/inspectorClient.test.ts', 'clients/web/src/test/core/inspectorClient-oauth.test.ts', 'clients/web/src/test/core/inspectorClient-oauth-e2e.test.ts', 'clients/web/src/test/core/inspectorClient-oauth-fetchFn.test.ts', 'clients/web/src/test/core/inspectorClient-oauth-remote-storage-e2e.test.ts', 'clients/web/src/test/core/transport.test.ts', 'clients/web/src/test/core/remote-transport.test.ts', 'clients/web/src/test/core/remote-server-config.test.ts', 'clients/web/src/test/core/storage-adapters.test.ts', 'clients/web/src/test/core/auth/storage-node.test.ts', 'clients/web/src/test/core/auth/oauth-callback-server.test.ts', // discovery.test.ts + state-machine.test.ts mock the SDK auth // module, but happy-dom + Vitest mock resolution drops the mock // (real fetch fires → CORS). Excluded pending mock rework. 'clients/web/src/test/core/auth/discovery.test.ts', 'clients/web/src/test/core/auth/state-machine.test.ts', ], setupFiles: [path.join(dirname, 'src/test/setup.ts')], }, }, { extends: true, plugins: [ // The plugin will run tests for the stories defined in your Storybook config // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest storybookTest({ configDir: path.join(dirname, '.storybook'), }), ], test: { name: 'storybook', browser: { enabled: true, headless: true, provider: playwright({}), instances: [ { browser: 'chromium', }, ], }, setupFiles: ['.storybook/vitest.setup.ts'], }, }, ], }, });