clawt 2.8.2 → 2.9.0

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.
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ // mock node:fs
4
+ vi.mock('node:fs', () => ({
5
+ existsSync: vi.fn(),
6
+ readFileSync: vi.fn(),
7
+ writeFileSync: vi.fn(),
8
+ unlinkSync: vi.fn(),
9
+ readdirSync: vi.fn(),
10
+ rmdirSync: vi.fn(),
11
+ }));
12
+
13
+ // mock logger
14
+ vi.mock('../../../src/logger/index.js', () => ({
15
+ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
16
+ }));
17
+
18
+ // mock fs 工具
19
+ vi.mock('../../../src/utils/fs.js', () => ({
20
+ ensureDir: vi.fn(),
21
+ }));
22
+
23
+ // mock 常量路径
24
+ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
25
+ const original = await importOriginal<typeof import('../../../src/constants/index.js')>();
26
+ return {
27
+ ...original,
28
+ VALIDATE_SNAPSHOTS_DIR: '/tmp/test-snapshots',
29
+ };
30
+ });
31
+
32
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync, rmdirSync } from 'node:fs';
33
+ import { ensureDir } from '../../../src/utils/fs.js';
34
+ import {
35
+ getSnapshotPath,
36
+ hasSnapshot,
37
+ readSnapshot,
38
+ readSnapshotTreeHash,
39
+ writeSnapshot,
40
+ removeSnapshot,
41
+ removeProjectSnapshots,
42
+ } from '../../../src/utils/validate-snapshot.js';
43
+
44
+ const mockedExistsSync = vi.mocked(existsSync);
45
+ const mockedReadFileSync = vi.mocked(readFileSync);
46
+ const mockedWriteFileSync = vi.mocked(writeFileSync);
47
+ const mockedUnlinkSync = vi.mocked(unlinkSync);
48
+ const mockedReaddirSync = vi.mocked(readdirSync);
49
+ const mockedRmdirSync = vi.mocked(rmdirSync);
50
+ const mockedEnsureDir = vi.mocked(ensureDir);
51
+
52
+ describe('getSnapshotPath', () => {
53
+ it('路径拼接正确', () => {
54
+ const path = getSnapshotPath('my-project', 'feature');
55
+ expect(path).toBe('/tmp/test-snapshots/my-project/feature.tree');
56
+ });
57
+ });
58
+
59
+ describe('hasSnapshot', () => {
60
+ it('快照存在时返回 true', () => {
61
+ mockedExistsSync.mockReturnValue(true);
62
+ expect(hasSnapshot('proj', 'branch')).toBe(true);
63
+ });
64
+
65
+ it('快照不存在时返回 false', () => {
66
+ mockedExistsSync.mockReturnValue(false);
67
+ expect(hasSnapshot('proj', 'branch')).toBe(false);
68
+ });
69
+ });
70
+
71
+ describe('readSnapshot', () => {
72
+ it('正常读取 treeHash 和 headCommitHash', () => {
73
+ mockedExistsSync.mockReturnValue(true);
74
+ mockedReadFileSync.mockImplementation((path: any) => {
75
+ if (String(path).endsWith('.tree')) return 'tree123\n';
76
+ if (String(path).endsWith('.head')) return 'head456\n';
77
+ return '';
78
+ });
79
+ const result = readSnapshot('proj', 'branch');
80
+ expect(result.treeHash).toBe('tree123');
81
+ expect(result.headCommitHash).toBe('head456');
82
+ });
83
+
84
+ it('文件不存在时返回空字符串', () => {
85
+ mockedExistsSync.mockReturnValue(false);
86
+ const result = readSnapshot('proj', 'branch');
87
+ expect(result.treeHash).toBe('');
88
+ expect(result.headCommitHash).toBe('');
89
+ });
90
+ });
91
+
92
+ describe('readSnapshotTreeHash', () => {
93
+ it('返回 treeHash', () => {
94
+ mockedExistsSync.mockReturnValue(true);
95
+ mockedReadFileSync.mockImplementation((path: any) => {
96
+ if (String(path).endsWith('.tree')) return 'tree789\n';
97
+ if (String(path).endsWith('.head')) return 'head000\n';
98
+ return '';
99
+ });
100
+ expect(readSnapshotTreeHash('proj', 'branch')).toBe('tree789');
101
+ });
102
+ });
103
+
104
+ describe('writeSnapshot', () => {
105
+ it('正确写入两个文件', () => {
106
+ writeSnapshot('proj', 'branch', 'tree123', 'head456');
107
+ expect(mockedEnsureDir).toHaveBeenCalledWith('/tmp/test-snapshots/proj');
108
+ expect(mockedWriteFileSync).toHaveBeenCalledTimes(2);
109
+ expect(mockedWriteFileSync).toHaveBeenCalledWith(
110
+ '/tmp/test-snapshots/proj/branch.tree',
111
+ 'tree123',
112
+ 'utf-8',
113
+ );
114
+ expect(mockedWriteFileSync).toHaveBeenCalledWith(
115
+ '/tmp/test-snapshots/proj/branch.head',
116
+ 'head456',
117
+ 'utf-8',
118
+ );
119
+ });
120
+ });
121
+
122
+ describe('removeSnapshot', () => {
123
+ it('删除存在的文件', () => {
124
+ mockedExistsSync.mockReturnValue(true);
125
+ removeSnapshot('proj', 'branch');
126
+ expect(mockedUnlinkSync).toHaveBeenCalledTimes(2);
127
+ });
128
+
129
+ it('文件不存在时不抛错', () => {
130
+ mockedExistsSync.mockReturnValue(false);
131
+ expect(() => removeSnapshot('proj', 'branch')).not.toThrow();
132
+ expect(mockedUnlinkSync).not.toHaveBeenCalled();
133
+ });
134
+ });
135
+
136
+ describe('removeProjectSnapshots', () => {
137
+ it('删除所有文件并删除空目录', () => {
138
+ mockedExistsSync.mockReturnValue(true);
139
+ // @ts-expect-error readdirSync 返回类型简化
140
+ mockedReaddirSync.mockReturnValue(['branch.tree', 'branch.head']);
141
+ removeProjectSnapshots('proj');
142
+ expect(mockedUnlinkSync).toHaveBeenCalledTimes(2);
143
+ expect(mockedRmdirSync).toHaveBeenCalledWith('/tmp/test-snapshots/proj');
144
+ });
145
+
146
+ it('项目目录不存在时不操作', () => {
147
+ mockedExistsSync.mockReturnValue(false);
148
+ removeProjectSnapshots('proj');
149
+ expect(mockedUnlinkSync).not.toHaveBeenCalled();
150
+ });
151
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ // mock shell
4
+ vi.mock('../../../src/utils/shell.js', () => ({
5
+ execCommand: vi.fn(),
6
+ }));
7
+
8
+ // mock git(validateMainWorktree 依赖 getGitCommonDir)
9
+ vi.mock('../../../src/utils/git.js', () => ({
10
+ getGitCommonDir: vi.fn(),
11
+ }));
12
+
13
+ // mock logger
14
+ vi.mock('../../../src/logger/index.js', () => ({
15
+ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
16
+ }));
17
+
18
+ import { execCommand } from '../../../src/utils/shell.js';
19
+ import { getGitCommonDir } from '../../../src/utils/git.js';
20
+ import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from '../../../src/utils/validation.js';
21
+ import { ClawtError } from '../../../src/errors/index.js';
22
+
23
+ const mockedExecCommand = vi.mocked(execCommand);
24
+ const mockedGetGitCommonDir = vi.mocked(getGitCommonDir);
25
+
26
+ describe('validateMainWorktree', () => {
27
+ it('.git 返回时正常通过', () => {
28
+ mockedGetGitCommonDir.mockReturnValue('.git');
29
+ expect(() => validateMainWorktree()).not.toThrow();
30
+ });
31
+
32
+ it('非 .git 时抛出 ClawtError', () => {
33
+ mockedGetGitCommonDir.mockReturnValue('/path/to/.git');
34
+ expect(() => validateMainWorktree()).toThrow(ClawtError);
35
+ });
36
+
37
+ it('命令失败时抛出 ClawtError', () => {
38
+ mockedGetGitCommonDir.mockImplementation(() => { throw new Error('not a git repo'); });
39
+ expect(() => validateMainWorktree()).toThrow(ClawtError);
40
+ });
41
+ });
42
+
43
+ describe('validateGitInstalled', () => {
44
+ it('Git 已安装时正常通过', () => {
45
+ mockedExecCommand.mockReturnValue('git version 2.40.0');
46
+ expect(() => validateGitInstalled()).not.toThrow();
47
+ });
48
+
49
+ it('Git 未安装时抛出 ClawtError', () => {
50
+ mockedExecCommand.mockImplementation(() => { throw new Error('not found'); });
51
+ expect(() => validateGitInstalled()).toThrow(ClawtError);
52
+ });
53
+ });
54
+
55
+ describe('validateClaudeCodeInstalled', () => {
56
+ it('Claude Code 已安装时正常通过', () => {
57
+ mockedExecCommand.mockReturnValue('claude 1.0.0');
58
+ expect(() => validateClaudeCodeInstalled()).not.toThrow();
59
+ });
60
+
61
+ it('Claude Code 未安装时抛出 ClawtError', () => {
62
+ mockedExecCommand.mockImplementation(() => { throw new Error('not found'); });
63
+ expect(() => validateClaudeCodeInstalled()).toThrow(ClawtError);
64
+ });
65
+ });
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { findExactMatch, findFuzzyMatches, resolveTargetWorktree } from '../../../src/utils/worktree-matcher.js';
3
+ import { createWorktreeInfo, createWorktreeList } from '../../helpers/fixtures.js';
4
+ import { ClawtError } from '../../../src/errors/index.js';
5
+ import type { WorktreeResolveMessages } from '../../../src/utils/worktree-matcher.js';
6
+
7
+ // mock enquirer
8
+ vi.mock('enquirer', () => ({
9
+ default: {
10
+ Select: vi.fn().mockImplementation(({ choices }: { choices: Array<{ name: string }> }) => ({
11
+ run: vi.fn().mockResolvedValue(choices[0].name),
12
+ })),
13
+ },
14
+ }));
15
+
16
+ /** 测试用消息配置 */
17
+ const testMessages: WorktreeResolveMessages = {
18
+ noWorktrees: '无可用 worktree',
19
+ selectBranch: '请选择分支',
20
+ multipleMatches: (keyword: string) => `"${keyword}" 匹配到多个分支`,
21
+ noMatch: (keyword: string, branches: string[]) =>
22
+ `未找到匹配 "${keyword}",可用:${branches.join(', ')}`,
23
+ };
24
+
25
+ describe('findExactMatch', () => {
26
+ it('精确匹配返回正确的 WorktreeInfo', () => {
27
+ const worktrees = [
28
+ createWorktreeInfo({ branch: 'feature-a' }),
29
+ createWorktreeInfo({ branch: 'feature-b' }),
30
+ ];
31
+ const result = findExactMatch(worktrees, 'feature-b');
32
+ expect(result).toBeDefined();
33
+ expect(result!.branch).toBe('feature-b');
34
+ });
35
+
36
+ it('无匹配返回 undefined', () => {
37
+ const worktrees = [createWorktreeInfo({ branch: 'feature-a' })];
38
+ expect(findExactMatch(worktrees, 'feature-b')).toBeUndefined();
39
+ });
40
+
41
+ it('空列表返回 undefined', () => {
42
+ expect(findExactMatch([], 'any')).toBeUndefined();
43
+ });
44
+
45
+ it('大小写敏感(精确匹配)', () => {
46
+ const worktrees = [createWorktreeInfo({ branch: 'Feature-A' })];
47
+ expect(findExactMatch(worktrees, 'feature-a')).toBeUndefined();
48
+ });
49
+ });
50
+
51
+ describe('findFuzzyMatches', () => {
52
+ it('子串匹配(大小写不敏感)', () => {
53
+ const worktrees = [
54
+ createWorktreeInfo({ branch: 'feature-login' }),
55
+ createWorktreeInfo({ branch: 'feature-logout' }),
56
+ createWorktreeInfo({ branch: 'bugfix-auth' }),
57
+ ];
58
+ const result = findFuzzyMatches(worktrees, 'LOG');
59
+ expect(result).toHaveLength(2);
60
+ expect(result.map((w) => w.branch)).toEqual(['feature-login', 'feature-logout']);
61
+ });
62
+
63
+ it('空列表返回空数组', () => {
64
+ expect(findFuzzyMatches([], 'test')).toEqual([]);
65
+ });
66
+
67
+ it('无匹配返回空数组', () => {
68
+ const worktrees = [createWorktreeInfo({ branch: 'feature-a' })];
69
+ expect(findFuzzyMatches(worktrees, 'xyz')).toEqual([]);
70
+ });
71
+
72
+ it('全部匹配时返回全部', () => {
73
+ const worktrees = createWorktreeList(3);
74
+ const result = findFuzzyMatches(worktrees, 'branch');
75
+ expect(result).toHaveLength(3);
76
+ });
77
+ });
78
+
79
+ describe('resolveTargetWorktree', () => {
80
+ it('空列表抛出 ClawtError', async () => {
81
+ await expect(resolveTargetWorktree([], testMessages, 'any')).rejects.toThrow(ClawtError);
82
+ await expect(resolveTargetWorktree([], testMessages, 'any')).rejects.toThrow('无可用 worktree');
83
+ });
84
+
85
+ it('单个 worktree 且不传分支名时直接返回', async () => {
86
+ const worktrees = [createWorktreeInfo({ branch: 'only-one' })];
87
+ const result = await resolveTargetWorktree(worktrees, testMessages);
88
+ expect(result.branch).toBe('only-one');
89
+ });
90
+
91
+ it('精确匹配优先', async () => {
92
+ const worktrees = [
93
+ createWorktreeInfo({ branch: 'feat' }),
94
+ createWorktreeInfo({ branch: 'feature' }),
95
+ ];
96
+ const result = await resolveTargetWorktree(worktrees, testMessages, 'feat');
97
+ expect(result.branch).toBe('feat');
98
+ });
99
+
100
+ it('模糊匹配唯一结果直接返回', async () => {
101
+ const worktrees = [
102
+ createWorktreeInfo({ branch: 'feature-login' }),
103
+ createWorktreeInfo({ branch: 'bugfix-auth' }),
104
+ ];
105
+ const result = await resolveTargetWorktree(worktrees, testMessages, 'login');
106
+ expect(result.branch).toBe('feature-login');
107
+ });
108
+
109
+ it('无匹配抛出 ClawtError 并包含可用分支', async () => {
110
+ const worktrees = [
111
+ createWorktreeInfo({ branch: 'feature-a' }),
112
+ createWorktreeInfo({ branch: 'feature-b' }),
113
+ ];
114
+ await expect(resolveTargetWorktree(worktrees, testMessages, 'xyz')).rejects.toThrow(ClawtError);
115
+ });
116
+ });
@@ -0,0 +1,181 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ // mock git 函数
4
+ vi.mock('../../../src/utils/git.js', () => ({
5
+ getProjectName: vi.fn().mockReturnValue('my-project'),
6
+ gitWorktreeList: vi.fn().mockReturnValue(''),
7
+ createWorktree: vi.fn(),
8
+ removeWorktreeByPath: vi.fn(),
9
+ deleteBranch: vi.fn(),
10
+ gitWorktreePrune: vi.fn(),
11
+ getCommitCountAhead: vi.fn(),
12
+ getDiffStat: vi.fn(),
13
+ isWorkingDirClean: vi.fn(),
14
+ }));
15
+
16
+ // mock branch
17
+ vi.mock('../../../src/utils/branch.js', () => ({
18
+ sanitizeBranchName: vi.fn((name: string) => name),
19
+ generateBranchNames: vi.fn((name: string, count: number) =>
20
+ count === 1 ? [name] : Array.from({ length: count }, (_, i) => `${name}-${i + 1}`),
21
+ ),
22
+ validateBranchesNotExist: vi.fn(),
23
+ }));
24
+
25
+ // mock fs
26
+ vi.mock('../../../src/utils/fs.js', () => ({
27
+ ensureDir: vi.fn(),
28
+ removeEmptyDir: vi.fn(),
29
+ }));
30
+
31
+ // mock node:fs
32
+ vi.mock('node:fs', () => ({
33
+ existsSync: vi.fn(),
34
+ readdirSync: vi.fn(),
35
+ }));
36
+
37
+ // mock constants
38
+ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
39
+ const original = await importOriginal<typeof import('../../../src/constants/index.js')>();
40
+ return {
41
+ ...original,
42
+ WORKTREES_DIR: '/tmp/test-worktrees',
43
+ };
44
+ });
45
+
46
+ // mock logger
47
+ vi.mock('../../../src/logger/index.js', () => ({
48
+ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
49
+ }));
50
+
51
+ import { existsSync, readdirSync } from 'node:fs';
52
+ import {
53
+ getProjectName,
54
+ gitWorktreeList,
55
+ createWorktree as gitCreateWorktree,
56
+ removeWorktreeByPath,
57
+ deleteBranch,
58
+ gitWorktreePrune,
59
+ getCommitCountAhead,
60
+ getDiffStat,
61
+ isWorkingDirClean,
62
+ } from '../../../src/utils/git.js';
63
+ import { sanitizeBranchName, validateBranchesNotExist } from '../../../src/utils/branch.js';
64
+ import { ensureDir, removeEmptyDir } from '../../../src/utils/fs.js';
65
+ import { createWorktrees, getProjectWorktrees, cleanupWorktrees, getWorktreeStatus } from '../../../src/utils/worktree.js';
66
+ import { createWorktreeInfo } from '../../helpers/fixtures.js';
67
+
68
+ const mockedExistsSync = vi.mocked(existsSync);
69
+ const mockedReaddirSync = vi.mocked(readdirSync);
70
+ const mockedGitWorktreeList = vi.mocked(gitWorktreeList);
71
+ const mockedGitCreateWorktree = vi.mocked(gitCreateWorktree);
72
+ const mockedRemoveWorktreeByPath = vi.mocked(removeWorktreeByPath);
73
+ const mockedDeleteBranch = vi.mocked(deleteBranch);
74
+ const mockedGetCommitCountAhead = vi.mocked(getCommitCountAhead);
75
+ const mockedGetDiffStat = vi.mocked(getDiffStat);
76
+ const mockedIsWorkingDirClean = vi.mocked(isWorkingDirClean);
77
+
78
+ describe('createWorktrees', () => {
79
+ it('单个 worktree 创建', () => {
80
+ const result = createWorktrees('feature', 1);
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0].branch).toBe('feature');
83
+ expect(result[0].path).toContain('feature');
84
+ expect(mockedGitCreateWorktree).toHaveBeenCalledTimes(1);
85
+ });
86
+
87
+ it('多个 worktree 创建', () => {
88
+ const result = createWorktrees('task', 3);
89
+ expect(result).toHaveLength(3);
90
+ expect(result[0].branch).toBe('task-1');
91
+ expect(result[1].branch).toBe('task-2');
92
+ expect(result[2].branch).toBe('task-3');
93
+ expect(mockedGitCreateWorktree).toHaveBeenCalledTimes(3);
94
+ });
95
+
96
+ it('调用分支名清理和存在性校验', () => {
97
+ createWorktrees('feature', 1);
98
+ expect(sanitizeBranchName).toHaveBeenCalledWith('feature');
99
+ expect(validateBranchesNotExist).toHaveBeenCalled();
100
+ expect(ensureDir).toHaveBeenCalled();
101
+ });
102
+ });
103
+
104
+ describe('getProjectWorktrees', () => {
105
+ it('项目目录不存在时返回空数组', () => {
106
+ mockedExistsSync.mockReturnValue(false);
107
+ expect(getProjectWorktrees()).toEqual([]);
108
+ });
109
+
110
+ it('正确解析和交叉验证', () => {
111
+ mockedExistsSync.mockReturnValue(true);
112
+ mockedGitWorktreeList.mockReturnValue(
113
+ '/repo abc [main]\n/tmp/test-worktrees/my-project/feature def [feature]',
114
+ );
115
+ mockedReaddirSync.mockReturnValue([
116
+ { name: 'feature', isDirectory: () => true },
117
+ { name: 'orphan', isDirectory: () => true },
118
+ { name: 'file.txt', isDirectory: () => false },
119
+ ] as any);
120
+ const result = getProjectWorktrees();
121
+ expect(result).toHaveLength(1);
122
+ expect(result[0].branch).toBe('feature');
123
+ });
124
+ });
125
+
126
+ describe('cleanupWorktrees', () => {
127
+ it('正确调用删除流程', () => {
128
+ const worktrees = [
129
+ createWorktreeInfo({ branch: 'a', path: '/path/a' }),
130
+ createWorktreeInfo({ branch: 'b', path: '/path/b' }),
131
+ ];
132
+ cleanupWorktrees(worktrees);
133
+ expect(mockedRemoveWorktreeByPath).toHaveBeenCalledTimes(2);
134
+ expect(mockedDeleteBranch).toHaveBeenCalledTimes(2);
135
+ expect(gitWorktreePrune).toHaveBeenCalled();
136
+ expect(removeEmptyDir).toHaveBeenCalled();
137
+ });
138
+
139
+ it('单个删除失败不影响后续', () => {
140
+ mockedRemoveWorktreeByPath.mockImplementationOnce(() => { throw new Error('fail'); });
141
+ const worktrees = [
142
+ createWorktreeInfo({ branch: 'a', path: '/path/a' }),
143
+ createWorktreeInfo({ branch: 'b', path: '/path/b' }),
144
+ ];
145
+ // 不应抛出异常
146
+ expect(() => cleanupWorktrees(worktrees)).not.toThrow();
147
+ });
148
+ });
149
+
150
+ describe('getWorktreeStatus', () => {
151
+ it('正确聚合状态信息', () => {
152
+ mockedGetCommitCountAhead.mockReturnValue(5);
153
+ mockedGetDiffStat.mockReturnValue({ insertions: 100, deletions: 20 });
154
+ mockedIsWorkingDirClean.mockReturnValue(false);
155
+ const worktree = createWorktreeInfo();
156
+ const result = getWorktreeStatus(worktree);
157
+ expect(result).toEqual({
158
+ commitCount: 5,
159
+ insertions: 100,
160
+ deletions: 20,
161
+ hasDirtyFiles: true,
162
+ });
163
+ });
164
+
165
+ it('工作区干净时 hasDirtyFiles 为 false', () => {
166
+ mockedGetCommitCountAhead.mockReturnValue(0);
167
+ mockedGetDiffStat.mockReturnValue({ insertions: 0, deletions: 0 });
168
+ mockedIsWorkingDirClean.mockReturnValue(true);
169
+ const worktree = createWorktreeInfo();
170
+ const result = getWorktreeStatus(worktree);
171
+ expect(result).not.toBeNull();
172
+ expect(result!.hasDirtyFiles).toBe(false);
173
+ });
174
+
175
+ it('获取失败时返回 null', () => {
176
+ mockedGetCommitCountAhead.mockImplementation(() => { throw new Error('fail'); });
177
+ const worktree = createWorktreeInfo();
178
+ const result = getWorktreeStatus(worktree);
179
+ expect(result).toBeNull();
180
+ });
181
+ });
package/tsconfig.json CHANGED
@@ -12,6 +12,6 @@
12
12
  "resolveJsonModule": true,
13
13
  "forceConsistentCasingInFileNames": true
14
14
  },
15
- "include": ["src/**/*", "scripts/**/*"],
15
+ "include": ["src/**/*", "scripts/**/*", "tests/**/*"],
16
16
  "exclude": ["node_modules", "dist"]
17
17
  }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ testTimeout: 10000,
7
+ environment: 'node',
8
+ setupFiles: ['tests/helpers/setup.ts'],
9
+ coverage: {
10
+ provider: 'v8',
11
+ include: ['src/**/*.ts'],
12
+ exclude: [
13
+ 'src/index.ts',
14
+ 'src/types/**',
15
+ 'src/logger/**',
16
+ ],
17
+ reporter: ['text', 'lcov', 'html'],
18
+ },
19
+ restoreMocks: true,
20
+ clearMocks: true,
21
+ },
22
+ });