Merge branch 'main' into mingholy/fix/acp-model-list

This commit is contained in:
tanzhenxin 2026-02-05 19:01:17 +08:00 committed by GitHub
commit a31699ed73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
351 changed files with 20743 additions and 5801 deletions

View file

@ -65,7 +65,7 @@ describe('handleAutoUpdate', () => {
mockSettings = {
merged: {
general: {
disableAutoUpdate: false,
enableAutoUpdate: true,
},
},
} as LoadedSettings;
@ -94,32 +94,29 @@ describe('handleAutoUpdate', () => {
expect(mockSpawn).not.toHaveBeenCalled();
});
it('should do nothing if update nag is disabled', () => {
mockSettings.merged.general!.disableUpdateNag = true;
handleAutoUpdate(mockUpdateInfo, mockSettings, '/root', mockSpawn);
expect(mockGetInstallationInfo).not.toHaveBeenCalled();
expect(mockUpdateEventEmitter.emit).not.toHaveBeenCalled();
expect(mockSpawn).not.toHaveBeenCalled();
});
it('should emit "update-received" but not update if auto-updates are disabled', () => {
mockSettings.merged.general!.disableAutoUpdate = true;
it('should show manual update message when enableAutoUpdate is false', () => {
// When enableAutoUpdate is false, gemini.tsx won't call checkForUpdates(),
// but if handleAutoUpdate is still called, it should show a manual update message.
mockSettings.merged.general!.enableAutoUpdate = false;
mockGetInstallationInfo.mockReturnValue({
updateCommand: 'npm i -g @qwen-code/qwen-code@latest',
updateMessage: 'Please update manually.',
updateMessage:
'Please run npm i -g @qwen-code/qwen-code@latest to update',
isGlobal: true,
packageManager: PackageManager.NPM,
});
handleAutoUpdate(mockUpdateInfo, mockSettings, '/root', mockSpawn);
expect(mockUpdateEventEmitter.emit).toHaveBeenCalledTimes(1);
// Should still emit update-received with manual update message
expect(mockUpdateEventEmitter.emit).toHaveBeenCalledWith(
'update-received',
{
message: 'An update is available!\nPlease update manually.',
message:
'An update is available!\nPlease run npm i -g @qwen-code/qwen-code@latest to update',
},
);
// Should NOT spawn update when enableAutoUpdate is false
expect(mockSpawn).not.toHaveBeenCalled();
});

View file

@ -24,13 +24,14 @@ export function handleAutoUpdate(
return;
}
if (settings.merged.general?.disableUpdateNag) {
return;
}
// enableAutoUpdate is checked in gemini.tsx before calling this function,
// so if we get here, auto-update is enabled (or undefined, which defaults to enabled).
const isAutoUpdateEnabled =
settings.merged.general?.enableAutoUpdate !== false;
const installationInfo = getInstallationInfo(
projectRoot,
settings.merged.general?.disableAutoUpdate ?? false,
isAutoUpdateEnabled,
);
let combinedMessage = info.message;
@ -42,10 +43,8 @@ export function handleAutoUpdate(
message: combinedMessage,
});
if (
!installationInfo.updateCommand ||
settings.merged.general?.disableAutoUpdate
) {
// Don't automatically run the update if auto-update is disabled or no update command
if (!installationInfo.updateCommand || !isAutoUpdateEnabled) {
return;
}
const isNightly = info.update.latest.includes('nightly');

View file

@ -178,13 +178,15 @@ describe('getInstallationInfo', () => {
throw new Error('Command failed');
});
const info = getInstallationInfo(projectRoot, false);
// isAutoUpdateEnabled = true -> "Attempting to automatically update"
const info = getInstallationInfo(projectRoot, true);
expect(info.packageManager).toBe(PackageManager.PNPM);
expect(info.isGlobal).toBe(true);
expect(info.updateCommand).toBe('pnpm add -g @qwen-code/qwen-code@latest');
expect(info.updateMessage).toContain('Attempting to automatically update');
const infoDisabled = getInstallationInfo(projectRoot, true);
// isAutoUpdateEnabled = false -> "Please run..."
const infoDisabled = getInstallationInfo(projectRoot, false);
expect(infoDisabled.updateMessage).toContain('Please run pnpm add');
});
@ -196,7 +198,8 @@ describe('getInstallationInfo', () => {
throw new Error('Command failed');
});
const info = getInstallationInfo(projectRoot, false);
// isAutoUpdateEnabled = true -> "Attempting to automatically update"
const info = getInstallationInfo(projectRoot, true);
expect(info.packageManager).toBe(PackageManager.YARN);
expect(info.isGlobal).toBe(true);
expect(info.updateCommand).toBe(
@ -204,7 +207,8 @@ describe('getInstallationInfo', () => {
);
expect(info.updateMessage).toContain('Attempting to automatically update');
const infoDisabled = getInstallationInfo(projectRoot, true);
// isAutoUpdateEnabled = false -> "Please run..."
const infoDisabled = getInstallationInfo(projectRoot, false);
expect(infoDisabled.updateMessage).toContain('Please run yarn global add');
});
@ -216,13 +220,15 @@ describe('getInstallationInfo', () => {
throw new Error('Command failed');
});
const info = getInstallationInfo(projectRoot, false);
// isAutoUpdateEnabled = true -> "Attempting to automatically update"
const info = getInstallationInfo(projectRoot, true);
expect(info.packageManager).toBe(PackageManager.BUN);
expect(info.isGlobal).toBe(true);
expect(info.updateCommand).toBe('bun add -g @qwen-code/qwen-code@latest');
expect(info.updateMessage).toContain('Attempting to automatically update');
const infoDisabled = getInstallationInfo(projectRoot, true);
// isAutoUpdateEnabled = false -> "Please run..."
const infoDisabled = getInstallationInfo(projectRoot, false);
expect(infoDisabled.updateMessage).toContain('Please run bun add');
});
@ -301,7 +307,8 @@ describe('getInstallationInfo', () => {
throw new Error('Command failed');
});
const info = getInstallationInfo(projectRoot, false);
// isAutoUpdateEnabled = true -> "Attempting to automatically update"
const info = getInstallationInfo(projectRoot, true);
expect(info.packageManager).toBe(PackageManager.NPM);
expect(info.isGlobal).toBe(true);
expect(info.updateCommand).toBe(
@ -309,7 +316,8 @@ describe('getInstallationInfo', () => {
);
expect(info.updateMessage).toContain('Attempting to automatically update');
const infoDisabled = getInstallationInfo(projectRoot, true);
// isAutoUpdateEnabled = false -> "Please run..."
const infoDisabled = getInstallationInfo(projectRoot, false);
expect(infoDisabled.updateMessage).toContain('Please run npm install');
});
});

View file

@ -30,7 +30,7 @@ export interface InstallationInfo {
export function getInstallationInfo(
projectRoot: string,
isAutoUpdateDisabled: boolean,
isAutoUpdateEnabled: boolean,
): InstallationInfo {
const cliPath = process.argv[1];
if (!cliPath) {
@ -99,9 +99,9 @@ export function getInstallationInfo(
packageManager: PackageManager.PNPM,
isGlobal: true,
updateCommand,
updateMessage: isAutoUpdateDisabled
? `Please run ${updateCommand} to update`
: 'Installed with pnpm. Attempting to automatically update now...',
updateMessage: isAutoUpdateEnabled
? 'Installed with pnpm. Attempting to automatically update now...'
: `Please run ${updateCommand} to update`,
};
}
@ -112,9 +112,9 @@ export function getInstallationInfo(
packageManager: PackageManager.YARN,
isGlobal: true,
updateCommand,
updateMessage: isAutoUpdateDisabled
? `Please run ${updateCommand} to update`
: 'Installed with yarn. Attempting to automatically update now...',
updateMessage: isAutoUpdateEnabled
? 'Installed with yarn. Attempting to automatically update now...'
: `Please run ${updateCommand} to update`,
};
}
@ -132,9 +132,9 @@ export function getInstallationInfo(
packageManager: PackageManager.BUN,
isGlobal: true,
updateCommand,
updateMessage: isAutoUpdateDisabled
? `Please run ${updateCommand} to update`
: 'Installed with bun. Attempting to automatically update now...',
updateMessage: isAutoUpdateEnabled
? 'Installed with bun. Attempting to automatically update now...'
: `Please run ${updateCommand} to update`,
};
}
@ -165,9 +165,9 @@ export function getInstallationInfo(
packageManager: PackageManager.NPM,
isGlobal: true,
updateCommand,
updateMessage: isAutoUpdateDisabled
? `Please run ${updateCommand} to update`
: 'Installed with npm. Attempting to automatically update now...',
updateMessage: isAutoUpdateEnabled
? 'Installed with npm. Attempting to automatically update now...'
: `Please run ${updateCommand} to update`,
};
} catch (error) {
console.log(error);

View file

@ -121,7 +121,7 @@ describe('SettingsUtils', () => {
description: 'Accessibility settings.',
showInDialog: false,
properties: {
disableLoadingPhrases: {
enableLoadingPhrases: {
type: 'boolean',
label: 'Disable Loading Phrases',
category: 'UI',
@ -285,14 +285,14 @@ describe('SettingsUtils', () => {
it('should handle nested settings correctly', () => {
const settings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
});
const mergedSettings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: false } },
ui: { accessibility: { enableLoadingPhrases: false } },
});
const value = getEffectiveValue(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
mergedSettings,
);
@ -316,7 +316,7 @@ describe('SettingsUtils', () => {
it('should return all setting keys', () => {
const keys = getAllSettingKeys();
expect(keys).toContain('test');
expect(keys).toContain('ui.accessibility.disableLoadingPhrases');
expect(keys).toContain('ui.accessibility.enableLoadingPhrases');
});
});
@ -343,9 +343,9 @@ describe('SettingsUtils', () => {
describe('isValidSettingKey', () => {
it('should return true for valid setting keys', () => {
expect(isValidSettingKey('ui.requiresRestart')).toBe(true);
expect(
isValidSettingKey('ui.accessibility.disableLoadingPhrases'),
).toBe(true);
expect(isValidSettingKey('ui.accessibility.enableLoadingPhrases')).toBe(
true,
);
});
it('should return false for invalid setting keys', () => {
@ -358,7 +358,7 @@ describe('SettingsUtils', () => {
it('should return correct category for valid settings', () => {
expect(getSettingCategory('ui.requiresRestart')).toBe('UI');
expect(
getSettingCategory('ui.accessibility.disableLoadingPhrases'),
getSettingCategory('ui.accessibility.enableLoadingPhrases'),
).toBe('UI');
});
@ -392,7 +392,7 @@ describe('SettingsUtils', () => {
const uiSettings = categories['UI'];
const uiKeys = uiSettings.map((s) => s.key);
expect(uiKeys).toContain('ui.requiresRestart');
expect(uiKeys).toContain('ui.accessibility.disableLoadingPhrases');
expect(uiKeys).toContain('ui.accessibility.enableLoadingPhrases');
expect(uiKeys).not.toContain('ui.theme'); // This is now marked false
});
@ -422,7 +422,7 @@ describe('SettingsUtils', () => {
const keys = booleanSettings.map((s) => s.key);
expect(keys).toContain('ui.requiresRestart');
expect(keys).toContain('ui.accessibility.disableLoadingPhrases');
expect(keys).toContain('ui.accessibility.enableLoadingPhrases');
expect(keys).not.toContain('privacy.usageStatisticsEnabled');
expect(keys).not.toContain('security.auth.selectedType'); // Advanced setting
expect(keys).not.toContain('security.auth.useExternal'); // Advanced setting
@ -455,7 +455,7 @@ describe('SettingsUtils', () => {
expect(dialogKeys).toContain('ui.requiresRestart');
// Should include nested settings marked for dialog
expect(dialogKeys).toContain('ui.accessibility.disableLoadingPhrases');
expect(dialogKeys).toContain('ui.accessibility.enableLoadingPhrases');
// Should NOT include settings marked as hidden
expect(dialogKeys).not.toContain('ui.theme'); // Hidden
@ -602,14 +602,14 @@ describe('SettingsUtils', () => {
it('should return true when value differs from default', () => {
expect(isSettingModified('ui.requiresRestart', true)).toBe(true);
expect(
isSettingModified('ui.accessibility.disableLoadingPhrases', true),
isSettingModified('ui.accessibility.enableLoadingPhrases', true),
).toBe(true);
});
it('should return false when value matches default', () => {
expect(isSettingModified('ui.requiresRestart', false)).toBe(false);
expect(
isSettingModified('ui.accessibility.disableLoadingPhrases', false),
isSettingModified('ui.accessibility.enableLoadingPhrases', false),
).toBe(false);
});
});
@ -629,11 +629,11 @@ describe('SettingsUtils', () => {
it('should return true for nested settings that exist', () => {
const settings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
});
expect(
settingExistsInScope(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
),
).toBe(true);
@ -643,7 +643,7 @@ describe('SettingsUtils', () => {
const settings = makeMockSettings({});
expect(
settingExistsInScope(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
),
).toBe(false);
@ -653,7 +653,7 @@ describe('SettingsUtils', () => {
const settings = makeMockSettings({ ui: { accessibility: {} } });
expect(
settingExistsInScope(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
),
).toBe(false);
@ -675,25 +675,25 @@ describe('SettingsUtils', () => {
it('should set nested setting value', () => {
const pendingSettings = makeMockSettings({});
const result = setPendingSettingValue(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
true,
pendingSettings,
);
expect(result.ui?.accessibility?.disableLoadingPhrases).toBe(true);
expect(result.ui?.accessibility?.enableLoadingPhrases).toBe(true);
});
it('should preserve existing nested settings', () => {
const pendingSettings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: false } },
ui: { accessibility: { enableLoadingPhrases: false } },
});
const result = setPendingSettingValue(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
true,
pendingSettings,
);
expect(result.ui?.accessibility?.disableLoadingPhrases).toBe(true);
expect(result.ui?.accessibility?.enableLoadingPhrases).toBe(true);
});
it('should not mutate original settings', () => {
@ -1030,7 +1030,7 @@ describe('SettingsUtils', () => {
const settings = makeMockSettings({}); // nested setting doesn't exist
const result = isDefaultValue(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
);
expect(result).toBe(true);
@ -1038,11 +1038,11 @@ describe('SettingsUtils', () => {
it('should return false when nested setting exists in scope', () => {
const settings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
}); // nested setting exists
const result = isDefaultValue(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
);
expect(result).toBe(false);
@ -1080,14 +1080,14 @@ describe('SettingsUtils', () => {
it('should return false for nested settings that exist in scope', () => {
const settings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
});
const mergedSettings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
});
const result = isValueInherited(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
mergedSettings,
);
@ -1097,11 +1097,11 @@ describe('SettingsUtils', () => {
it('should return true for nested settings that do not exist in scope', () => {
const settings = makeMockSettings({});
const mergedSettings = makeMockSettings({
ui: { accessibility: { disableLoadingPhrases: true } },
ui: { accessibility: { enableLoadingPhrases: true } },
});
const result = isValueInherited(
'ui.accessibility.disableLoadingPhrases',
'ui.accessibility.enableLoadingPhrases',
settings,
mergedSettings,
);