fix: resolve build errors and clean up scenario page
- Remove undefined variable reference (run.conversation) in header - Extract ExpandedRunDetails component to reduce JSX nesting - Fix vitest configs: rename to .mjs and add ESM-compatible __dirname - Inline tailwind base config to avoid module resolution issues - Remove unused imports (Streamdown, viewMode state) This fixes the Turbopack parsing error that was preventing the build.
This commit is contained in:
parent
f5d7364c96
commit
01b7295daa
6 changed files with 202 additions and 224 deletions
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@
|
||||||
"clean": "rm -rf .next .turbo",
|
"clean": "rm -rf .next .turbo",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test:integration": "INTEGRATION_TESTS=true vitest run --config vitest.integration.config.ts",
|
"test:integration": "INTEGRATION_TESTS=true vitest run --config vitest.integration.config.mjs",
|
||||||
"test:integration:watch": "INTEGRATION_TESTS=true vitest --config vitest.integration.config.ts",
|
"test:integration:watch": "INTEGRATION_TESTS=true vitest --config vitest.integration.config.mjs",
|
||||||
"test:setup-credentials": "tsx src/test/integration/_helpers/setup-test-credentials.ts",
|
"test:setup-credentials": "tsx src/test/integration/_helpers/setup-test-credentials.ts",
|
||||||
"test:setup-openai-key": "tsx src/test/integration/_helpers/setup-openai-key.ts",
|
"test:setup-openai-key": "tsx src/test/integration/_helpers/setup-openai-key.ts",
|
||||||
"test:cleanup-orphans": "tsx src/test/integration/_helpers/cleanup-orphans.ts",
|
"test:cleanup-orphans": "tsx src/test/integration/_helpers/cleanup-orphans.ts",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
import type { ScenarioRun } from './page';
|
||||||
|
|
||||||
|
interface ExpandedRunDetailsProps {
|
||||||
|
run: ScenarioRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExpandedRunDetails({ run }: ExpandedRunDetailsProps) {
|
||||||
|
return (
|
||||||
|
<div className="px-4 pb-4 border-t border-border/50">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
|
{/* Evaluator */}
|
||||||
|
{run.evaluator?.verdict && (
|
||||||
|
<div className="p-3 bg-surface-secondary rounded-lg">
|
||||||
|
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
||||||
|
LLM Evaluation
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
{run.evaluator.model && <span className="text-xs">{run.evaluator.model}</span>}
|
||||||
|
</div>
|
||||||
|
{run.evaluator?.reason && (
|
||||||
|
<p className="text-sm text-foreground-secondary">{run.evaluator.reason}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Usage Stats */}
|
||||||
|
<div className="p-3 bg-surface-secondary rounded-lg">
|
||||||
|
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
||||||
|
Usage
|
||||||
|
</h4>
|
||||||
|
{run.usage ? (
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-foreground-tertiary">Duration:</span>{' '}
|
||||||
|
<span className="text-foreground">
|
||||||
|
{run.usage?.executionTimeMs
|
||||||
|
? `${Math.floor(run.usage.executionTimeMs / 1000)}s`
|
||||||
|
: '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-foreground-tertiary">Tokens:</span>{' '}
|
||||||
|
<span className="text-foreground">
|
||||||
|
{run.usage?.totalTokens?.toLocaleString() || '—'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-foreground-tertiary">Retries:</span>{' '}
|
||||||
|
<span className="text-foreground">{run.retryCount || 0}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-foreground-secondary">No usage data available</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Output (if owner) */}
|
||||||
|
{run.output && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
||||||
|
Output
|
||||||
|
</h4>
|
||||||
|
<pre className="p-3 bg-surface-secondary rounded-lg text-sm text-foreground overflow-x-auto whitespace-pre-wrap">
|
||||||
|
{run.output}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error Log (if owner and error) */}
|
||||||
|
{run.errorLog && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<h4 className="text-xs font-semibold text-error uppercase tracking-wide mb-2">
|
||||||
|
Error Log
|
||||||
|
</h4>
|
||||||
|
<pre className="p-3 bg-error/5 border border-error/20 rounded-lg text-sm text-error overflow-x-auto whitespace-pre-wrap">
|
||||||
|
{run.errorLog}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Conversation History */}
|
||||||
|
{run.conversation && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide">
|
||||||
|
Conversation History
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{run.conversation.map((msg) => (
|
||||||
|
<div key={msg.id}>
|
||||||
|
{msg.role === 'USER' && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<div className="max-w-[80%] rounded-lg p-4 bg-primary text-primary-foreground">
|
||||||
|
<div className="text-sm whitespace-pre-wrap">{msg.content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{msg.role === 'ASSISTANT' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{msg.content && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
|
||||||
|
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
||||||
|
{msg.content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{msg.role === 'TOOL' && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="max-w-[80%] rounded-lg border border-border bg-surface-secondary overflow-hidden">
|
||||||
|
<div className="p-3">
|
||||||
|
<div className="text-sm font-medium text-foreground">
|
||||||
|
{msg.toolName || 'Unknown Tool'}
|
||||||
|
</div>
|
||||||
|
{msg.toolResult != null && (
|
||||||
|
<div className="mt-2 pt-2 border-t border-border/50">
|
||||||
|
<pre className="text-xs text-success overflow-x-auto whitespace-pre-wrap break-all">
|
||||||
|
{typeof msg.toolResult === 'string'
|
||||||
|
? msg.toolResult
|
||||||
|
: JSON.stringify(msg.toolResult, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { notFound, useParams } from 'next/navigation';
|
import { notFound, useParams } from 'next/navigation';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Streamdown } from 'streamdown';
|
|
||||||
import { AppHeader } from '~/components/AppHeader';
|
import { AppHeader } from '~/components/AppHeader';
|
||||||
|
import { ExpandedRunDetails } from './ExpandedRunDetails';
|
||||||
|
|
||||||
interface ScenarioRun {
|
export interface ScenarioRun {
|
||||||
id: string;
|
id: string;
|
||||||
status: string;
|
status: string;
|
||||||
retryCount: number;
|
retryCount: number;
|
||||||
|
|
@ -167,7 +167,6 @@ export default function CollectionScenarioDetailPage(): React.ReactElement {
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
const [runError, setRunError] = useState<string | null>(null);
|
const [runError, setRunError] = useState<string | null>(null);
|
||||||
const [expandedRunId, setExpandedRunId] = useState<string | null>(null);
|
const [expandedRunId, setExpandedRunId] = useState<string | null>(null);
|
||||||
const [viewMode, setViewMode] = useState<'chat' | 'debug'>('chat');
|
|
||||||
|
|
||||||
const fetchScenario = useCallback(async () => {
|
const fetchScenario = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -261,43 +260,13 @@ export default function CollectionScenarioDetailPage(): React.ReactElement {
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
{scenario.name || 'Unnamed Scenario'}
|
{scenario.name || 'Unnamed Scenario'}
|
||||||
</h1>
|
</h1>
|
||||||
{scenario.description && (
|
{scenario.description && (
|
||||||
<p className="text-foreground-secondary mt-2">{scenario.description}</p>
|
<p className="text-foreground-secondary mt-2">{scenario.description}</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2">
|
|
||||||
{scenario.isOwner && (
|
|
||||||
<Button onClick={handleRunScenario} disabled={isRunning}>
|
|
||||||
{isRunning ? (
|
|
||||||
<>
|
|
||||||
<Icon icon="loader" className="w-4 h-4 mr-1.5 animate-spin" />
|
|
||||||
Running...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon icon="arrowRight" className="w-4 h-4 mr-1.5" />
|
|
||||||
Run Scenario
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{scenario.isOwner && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
if (run.conversation && run.conversation.length > 0) {
|
|
||||||
setViewMode(viewMode === 'chat' ? 'debug' : 'chat');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={!run.conversation || run.conversation.length === 0}
|
|
||||||
>
|
|
||||||
{viewMode === 'chat' ? 'View Raw' : 'View Chat'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{scenario.isOwner && (
|
{scenario.isOwner && (
|
||||||
<Button onClick={handleRunScenario} disabled={isRunning}>
|
<Button onClick={handleRunScenario} disabled={isRunning}>
|
||||||
|
|
@ -421,187 +390,7 @@ export default function CollectionScenarioDetailPage(): React.ReactElement {
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Run Details (Expanded) */}
|
{/* Run Details (Expanded) */}
|
||||||
{expandedRunId === run.id && (
|
{expandedRunId === run.id && <ExpandedRunDetails run={run} />}
|
||||||
<div className="px-4 pb-4 border-t border-border/50">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
||||||
{/* Evaluator */}
|
|
||||||
{run.evaluator?.verdict && (
|
|
||||||
<div className="p-3 bg-surface-secondary rounded-lg">
|
|
||||||
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
|
||||||
LLM Evaluation
|
|
||||||
</h4>
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<StatusBadge status={run.evaluator.verdict} />
|
|
||||||
{run.evaluator.model && (
|
|
||||||
<Badge variant="secondary" size="sm">
|
|
||||||
{run.evaluator.model}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{run.evaluator?.reason && (
|
|
||||||
<p className="text-sm text-foreground-secondary">
|
|
||||||
{run.evaluator.reason}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Usage Stats */}
|
|
||||||
<div className="p-3 bg-surface-secondary rounded-lg">
|
|
||||||
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
|
||||||
Usage
|
|
||||||
</h4>
|
|
||||||
{run.usage ? (
|
|
||||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
||||||
<div>
|
|
||||||
<span className="text-foreground-tertiary">Duration:</span>{' '}
|
|
||||||
<span className="text-foreground">
|
|
||||||
{formatDuration(run.usage.executionTimeMs || null)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-foreground-tertiary">Tokens:</span>{' '}
|
|
||||||
<span className="text-foreground">
|
|
||||||
{run.usage.totalTokens?.toLocaleString() || '—'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-foreground-tertiary">Retries:</span>{' '}
|
|
||||||
<span className="text-foreground">{run.retryCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-sm text-foreground-secondary">
|
|
||||||
No usage data available
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Output (if owner) */}
|
|
||||||
{run.output && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide mb-2">
|
|
||||||
Output
|
|
||||||
</h4>
|
|
||||||
<pre className="p-3 bg-surface-secondary rounded-lg text-sm text-foreground overflow-x-auto whitespace-pre-wrap">
|
|
||||||
{run.output}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Error Log (if owner and error) */}
|
|
||||||
{run.errorLog && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<h4 className="text-xs font-semibold text-error uppercase tracking-wide mb-2">
|
|
||||||
Error Log
|
|
||||||
</h4>
|
|
||||||
<pre className="p-3 bg-error/5 border border-error/20 rounded-lg text-sm text-error overflow-x-auto whitespace-pre-wrap">
|
|
||||||
{run.errorLog}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Conversation History */}
|
|
||||||
{run.conversation && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h4 className="text-xs font-semibold text-foreground-secondary uppercase tracking-wide">
|
|
||||||
Conversation History
|
|
||||||
</h4>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
variant={viewMode === 'chat' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setViewMode('chat')}
|
|
||||||
>
|
|
||||||
Chat
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={viewMode === 'debug' ? 'default' : 'outline'}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setViewMode('debug')}
|
|
||||||
>
|
|
||||||
Raw
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{viewMode === 'debug' ? (
|
|
||||||
<div className="p-4">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h5 className="text-sm font-medium text-foreground">
|
|
||||||
Raw Messages ({run.conversation.length})
|
|
||||||
</h5>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
JSON.stringify(run.conversation, null, 2)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="copy" size="xs" className="mr-2" />
|
|
||||||
Copy JSON
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<pre className="text-xs font-mono bg-surface-secondary border border-border rounded-lg p-4 overflow-x-auto whitespace-pre-wrap">
|
|
||||||
{JSON.stringify(run.conversation, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{run.conversation.map((msg) => (
|
|
||||||
<div key={msg.id}>
|
|
||||||
{msg.role === 'USER' && (
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<div className="max-w-[80%] rounded-lg p-4 bg-primary text-primary-foreground">
|
|
||||||
<div className="text-sm whitespace-pre-wrap">
|
|
||||||
{msg.content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{msg.role === 'ASSISTANT' && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{msg.content && (
|
|
||||||
<div className="flex justify-start">
|
|
||||||
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
|
|
||||||
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
|
|
||||||
<Streamdown>{msg.content}</Streamdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{msg.role === 'TOOL' && (
|
|
||||||
<div className="flex justify-start">
|
|
||||||
<div className="max-w-[80%] rounded-lg border border-border bg-surface-secondary overflow-hidden">
|
|
||||||
<div className="p-3">
|
|
||||||
<div className="text-sm font-medium text-foreground">
|
|
||||||
{msg.toolName || 'Unknown Tool'}
|
|
||||||
</div>
|
|
||||||
{msg.toolResult && (
|
|
||||||
<div className="mt-2 pt-2 border-t border-border/50">
|
|
||||||
<pre className="text-xs text-success overflow-x-auto whitespace-pre-wrap break-all">
|
|
||||||
{typeof msg.toolResult === 'string'
|
|
||||||
? msg.toolResult
|
|
||||||
: JSON.stringify(msg.toolResult, null, 2)}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
22
apps/web/vitest.config.mjs
Normal file
22
apps/web/vitest.config.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { resolve, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'node',
|
||||||
|
include: ['src/**/*.test.ts'],
|
||||||
|
exclude: ['src/test/integration/**/*.test.ts', 'node_modules/**'],
|
||||||
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
|
testTimeout: 30000, // 30s for API calls
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
27
apps/web/vitest.integration.config.mjs
Normal file
27
apps/web/vitest.integration.config.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { resolve, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'node',
|
||||||
|
include: ['src/test/integration/**/*.integration.test.ts'],
|
||||||
|
setupFiles: ['./src/test/integration/setup-integration.ts'],
|
||||||
|
testTimeout: 60000, // 60s for API calls to production
|
||||||
|
// Run tests sequentially to avoid race conditions on shared test data
|
||||||
|
sequence: {
|
||||||
|
concurrent: false,
|
||||||
|
},
|
||||||
|
// Fail fast on integration tests
|
||||||
|
bail: 5,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue