clawt 3.1.2 → 3.2.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.
@@ -48,6 +48,7 @@ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
48
48
  CONFIG_INVALID_ENUM: (key: string, validValues: readonly string[]) =>
49
49
  `配置项 ${key} 仅接受以下值: ${validValues.join(', ')}`,
50
50
  CONFIG_INPUT_PROMPT: (key: string) => `输入 ${key} 的新值`,
51
+ CONFIG_SELECT_PROMPT: '选择要修改的配置项',
51
52
  },
52
53
  };
53
54
  });
@@ -58,6 +59,7 @@ import {
58
59
  parseConfigValue,
59
60
  promptConfigValue,
60
61
  formatConfigValue,
62
+ interactiveConfigEditor,
61
63
  } from '../../../src/utils/config-strategy.js';
62
64
 
63
65
  beforeEach(() => {
@@ -184,7 +186,7 @@ describe('promptConfigValue', () => {
184
186
 
185
187
  it('字符串 + 有 allowedValues 使用 Select 提示', async () => {
186
188
  mockSelectRun.mockResolvedValueOnce('iterm2');
187
- const result = await promptConfigValue('terminalApp', 'auto');
189
+ const result = await promptConfigValue('terminalApp', 'auto', ['auto', 'iterm2', 'terminal']);
188
190
  expect(result).toBe('iterm2');
189
191
  expect(mockSelectRun).toHaveBeenCalledTimes(1);
190
192
  });
@@ -217,4 +219,78 @@ describe('formatConfigValue', () => {
217
219
  const result = formatConfigValue('hello');
218
220
  expect(result).toContain('hello');
219
221
  });
222
+
223
+ it('undefined 显示为 (未设置)', () => {
224
+ const result = formatConfigValue(undefined);
225
+ expect(result).toContain('未设置');
226
+ });
227
+
228
+ it('null 显示为 (未设置)', () => {
229
+ const result = formatConfigValue(null);
230
+ expect(result).toContain('未设置');
231
+ });
232
+ });
233
+
234
+ describe('interactiveConfigEditor', () => {
235
+ it('选择配置项并返回新值(布尔类型)', async () => {
236
+ // 第一次 Select 选择配置项,第二次 Select 选择布尔值
237
+ mockSelectRun.mockResolvedValueOnce('enabled');
238
+ mockSelectRun.mockResolvedValueOnce('true');
239
+
240
+ const config = { enabled: false, name: 'test' };
241
+ const definitions = {
242
+ enabled: { description: '是否启用' },
243
+ name: { description: '名称' },
244
+ };
245
+
246
+ const result = await interactiveConfigEditor(config, definitions);
247
+ expect(result.key).toBe('enabled');
248
+ expect(result.newValue).toBe(true);
249
+ });
250
+
251
+ it('选择配置项并返回新值(字符串类型)', async () => {
252
+ mockSelectRun.mockResolvedValueOnce('name');
253
+ mockInputRun.mockResolvedValueOnce('new-name');
254
+
255
+ const config = { enabled: false, name: 'test' };
256
+ const definitions = {
257
+ enabled: { description: '是否启用' },
258
+ name: { description: '名称' },
259
+ };
260
+
261
+ const result = await interactiveConfigEditor(config, definitions);
262
+ expect(result.key).toBe('name');
263
+ expect(result.newValue).toBe('new-name');
264
+ });
265
+
266
+ it('支持自定义 selectPrompt', async () => {
267
+ mockSelectRun.mockResolvedValueOnce('name');
268
+ mockInputRun.mockResolvedValueOnce('value');
269
+
270
+ const config = { name: 'test' };
271
+ const definitions = { name: { description: '名称' } };
272
+
273
+ await interactiveConfigEditor(config, definitions, {
274
+ selectPrompt: '自定义提示',
275
+ });
276
+
277
+ // 验证 Select 被调用(无需检查具体参数,因为 mock 不保留构造参数)
278
+ expect(mockSelectRun).toHaveBeenCalledTimes(1);
279
+ });
280
+
281
+ it('有 allowedValues 的字符串配置项使用 Select 提示', async () => {
282
+ mockSelectRun.mockResolvedValueOnce('mode');
283
+ mockSelectRun.mockResolvedValueOnce('fast');
284
+
285
+ const config = { mode: 'normal' };
286
+ const definitions = {
287
+ mode: { description: '模式', allowedValues: ['normal', 'fast', 'slow'] as const },
288
+ };
289
+
290
+ const result = await interactiveConfigEditor(config, definitions);
291
+ expect(result.key).toBe('mode');
292
+ expect(result.newValue).toBe('fast');
293
+ // 两次 Select:一次选择配置项,一次选择枚举值
294
+ expect(mockSelectRun).toHaveBeenCalledTimes(2);
295
+ });
220
296
  });
@@ -49,6 +49,7 @@ import {
49
49
  saveProjectConfig,
50
50
  requireProjectConfig,
51
51
  getMainWorkBranch,
52
+ getValidateRunCommand,
52
53
  } from '../../../src/utils/project-config.js';
53
54
 
54
55
  const mockedExistsSync = vi.mocked(existsSync);
@@ -134,3 +135,34 @@ describe('getMainWorkBranch', () => {
134
135
  expect(getMainWorkBranch()).toBe('develop');
135
136
  });
136
137
  });
138
+
139
+ describe('getValidateRunCommand', () => {
140
+ it('配置中有 validateRunCommand 时返回对应值', () => {
141
+ mockedExistsSync.mockReturnValue(true);
142
+ mockedReadFileSync.mockReturnValue(JSON.stringify({
143
+ clawtMainWorkBranch: 'main',
144
+ validateRunCommand: 'npm test',
145
+ }));
146
+ expect(getValidateRunCommand()).toBe('npm test');
147
+ });
148
+
149
+ it('配置中无 validateRunCommand 时返回 undefined', () => {
150
+ mockedExistsSync.mockReturnValue(true);
151
+ mockedReadFileSync.mockReturnValue(JSON.stringify({ clawtMainWorkBranch: 'main' }));
152
+ expect(getValidateRunCommand()).toBeUndefined();
153
+ });
154
+
155
+ it('配置文件不存在时返回 undefined', () => {
156
+ mockedExistsSync.mockReturnValue(false);
157
+ expect(getValidateRunCommand()).toBeUndefined();
158
+ });
159
+
160
+ it('validateRunCommand 为空字符串时返回 undefined', () => {
161
+ mockedExistsSync.mockReturnValue(true);
162
+ mockedReadFileSync.mockReturnValue(JSON.stringify({
163
+ clawtMainWorkBranch: 'main',
164
+ validateRunCommand: '',
165
+ }));
166
+ expect(getValidateRunCommand()).toBeUndefined();
167
+ });
168
+ });
@@ -106,10 +106,10 @@ describe('readSnapshotTreeHash', () => {
106
106
  });
107
107
 
108
108
  describe('writeSnapshot', () => {
109
- it('正确写入两个文件', () => {
109
+ it('正确写入三个文件', () => {
110
110
  writeSnapshot('proj', 'branch', 'tree123', 'head456');
111
111
  expect(mockedEnsureDir).toHaveBeenCalledWith('/tmp/test-snapshots/proj');
112
- expect(mockedWriteFileSync).toHaveBeenCalledTimes(2);
112
+ expect(mockedWriteFileSync).toHaveBeenCalledTimes(3);
113
113
  expect(mockedWriteFileSync).toHaveBeenCalledWith(
114
114
  '/tmp/test-snapshots/proj/branch.tree',
115
115
  'tree123',
@@ -120,6 +120,11 @@ describe('writeSnapshot', () => {
120
120
  'head456',
121
121
  'utf-8',
122
122
  );
123
+ expect(mockedWriteFileSync).toHaveBeenCalledWith(
124
+ '/tmp/test-snapshots/proj/branch.staged',
125
+ '',
126
+ 'utf-8',
127
+ );
123
128
  });
124
129
  });
125
130
 
@@ -127,7 +132,7 @@ describe('removeSnapshot', () => {
127
132
  it('删除存在的文件', () => {
128
133
  mockedExistsSync.mockReturnValue(true);
129
134
  removeSnapshot('proj', 'branch');
130
- expect(mockedUnlinkSync).toHaveBeenCalledTimes(2);
135
+ expect(mockedUnlinkSync).toHaveBeenCalledTimes(3);
131
136
  });
132
137
 
133
138
  it('文件不存在时不抛错', () => {