generated from nhcarrigan/template
452fe185df
## Summary This PR brings Hikari Desktop up to full compatibility with Claude Code CLI versions v2.1.68 through v2.1.74, implementing all changelog items audited in issues #200–#218. ## Changes ### Bug Fixes - Remove deprecated Claude Opus 4.0 and 4.1 models from the model selector - Auto-migrate users pinned to deprecated models to Opus 4.6 ### New Features - Add cron tool support (`CronCreate`, `CronDelete`, `CronList`) with character state mapping and `CLAUDE_CODE_DISABLE_CRON` settings toggle - Handle `EnterWorktree` and `ExitWorktree` tools in character state mapping and tool display - Add CLI update check with npm registry indicator in the version bar - Add `agent_type` field and support the Agent tool rename from CLI v2.1.69 - Consume `worktree` field from status line hook events - Display per-agent model override in the agent monitor tree - Expose Claude Code CLI built-in slash commands (`/simplify`, `/loop`, `/batch`, `/memory`, `/context`) in the command menu with CLI badges - Add `includeGitInstructions` toggle in settings - Add `ENABLE_CLAUDEAI_MCP_SERVERS` opt-out setting - Linkify MCP binary file paths (PDFs, audio, Office docs) in markdown output - Add auto-memory panel, `/memory` slash command shortcut, and unified toast notification system - Toast notifications for `WorktreeCreate` and `WorktreeRemove` hook events - Sort session resume list by most recent activity, with most recent user message as preview - Convert WSL Linux paths to Windows UNC paths when opening binary files via `open_binary_file` command - Expose `autoMemoryDirectory` setting in ConfigSidebar (Agent Settings section) - Add `/context` as a CLI built-in in the slash command menu - Expose `modelOverrides` setting as a JSON textarea in ConfigSidebar (for AWS Bedrock, Google Vertex, etc.) > **Note:** The CLI update check commit does not have a corresponding issue — it was a bonus addition during the audit sprint. ## Closes Closes #200 Closes #201 Closes #202 Closes #205 Closes #206 Closes #207 Closes #208 Closes #209 Closes #210 Closes #211 Closes #212 Closes #213 Closes #214 Closes #215 Closes #216 Closes #217 Closes #218 Reviewed-on: #221 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
/**
|
|
* CliVersion Component Tests
|
|
*
|
|
* Tests the version comparison logic used by the CliVersion component,
|
|
* which compares the installed CLI version against the supported version.
|
|
*
|
|
* What this component does:
|
|
* - Displays the installed Claude CLI version
|
|
* - Displays the highest audited/supported CLI version
|
|
* - Shows a warning when the installed version is ahead of or behind supported
|
|
*
|
|
* Manual testing checklist:
|
|
* - [ ] Installed version is fetched and displayed on mount
|
|
* - [ ] "current" badge shows in green when versions match
|
|
* - [ ] "ahead" badge shows in amber when installed is newer than supported
|
|
* - [ ] "behind" badge shows in red when installed is older than supported
|
|
* - [ ] Warning message appears for "ahead" and "behind" states
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
const SUPPORTED_CLI_VERSION = "2.1.74";
|
|
|
|
function compareVersions(a: string, b: string): number {
|
|
const aParts = a.split(".").map(Number);
|
|
const bParts = b.split(".").map(Number);
|
|
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
const aVal = aParts[i] ?? 0;
|
|
const bVal = bParts[i] ?? 0;
|
|
if (aVal > bVal) return 1;
|
|
if (aVal < bVal) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---
|
|
|
|
describe("SUPPORTED_CLI_VERSION", () => {
|
|
it("is defined and non-empty", () => {
|
|
expect(SUPPORTED_CLI_VERSION).toBeTruthy();
|
|
});
|
|
|
|
it("matches the expected audited version", () => {
|
|
expect(SUPPORTED_CLI_VERSION).toBe("2.1.74");
|
|
});
|
|
});
|
|
|
|
describe("compareVersions", () => {
|
|
describe("equal versions", () => {
|
|
it("returns 0 for identical versions", () => {
|
|
expect(compareVersions("1.0.0", "1.0.0")).toBe(0);
|
|
});
|
|
|
|
it("returns 0 for the supported CLI version against itself", () => {
|
|
expect(compareVersions(SUPPORTED_CLI_VERSION, SUPPORTED_CLI_VERSION)).toBe(0);
|
|
});
|
|
|
|
it("returns 0 for 0.0.0 vs 0.0.0", () => {
|
|
expect(compareVersions("0.0.0", "0.0.0")).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("major version differences", () => {
|
|
it("returns 1 when a has a higher major version", () => {
|
|
expect(compareVersions("2.0.0", "1.0.0")).toBe(1);
|
|
});
|
|
|
|
it("returns -1 when a has a lower major version", () => {
|
|
expect(compareVersions("1.0.0", "2.0.0")).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe("minor version differences", () => {
|
|
it("returns 1 when a has a higher minor version", () => {
|
|
expect(compareVersions("1.2.0", "1.1.0")).toBe(1);
|
|
});
|
|
|
|
it("returns -1 when a has a lower minor version", () => {
|
|
expect(compareVersions("1.1.0", "1.2.0")).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe("patch version differences", () => {
|
|
it("returns 1 when a has a higher patch version", () => {
|
|
expect(compareVersions("1.0.2", "1.0.1")).toBe(1);
|
|
});
|
|
|
|
it("returns -1 when a has a lower patch version", () => {
|
|
expect(compareVersions("1.0.1", "1.0.2")).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe("major version takes precedence", () => {
|
|
it("returns 1 when a has a higher major but lower minor", () => {
|
|
expect(compareVersions("2.0.0", "1.9.9")).toBe(1);
|
|
});
|
|
|
|
it("returns -1 when a has a lower major but higher minor", () => {
|
|
expect(compareVersions("1.9.9", "2.0.0")).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe("unequal segment counts", () => {
|
|
it("treats missing segments as 0 (a shorter than b)", () => {
|
|
expect(compareVersions("1.0", "1.0.0")).toBe(0);
|
|
});
|
|
|
|
it("treats missing segments as 0 (a longer than b)", () => {
|
|
expect(compareVersions("1.0.0", "1.0")).toBe(0);
|
|
});
|
|
|
|
it("correctly compares when a has an extra non-zero segment", () => {
|
|
expect(compareVersions("1.0.1", "1.0")).toBe(1);
|
|
});
|
|
|
|
it("correctly compares when b has an extra non-zero segment", () => {
|
|
expect(compareVersions("1.0", "1.0.1")).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe("supported CLI version comparisons", () => {
|
|
it("returns 1 for a version ahead of supported", () => {
|
|
expect(compareVersions("2.2.0", SUPPORTED_CLI_VERSION)).toBe(1);
|
|
});
|
|
|
|
it("returns -1 for a version behind supported", () => {
|
|
expect(compareVersions("2.1.0", SUPPORTED_CLI_VERSION)).toBe(-1);
|
|
});
|
|
|
|
it("returns 0 for exactly the supported version", () => {
|
|
expect(compareVersions("2.1.74", SUPPORTED_CLI_VERSION)).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Mirrors the updateAvailable derived logic in CliVersion.svelte
|
|
function isUpdateAvailable(installedVersion: string, latestNpmVersion: string | null): boolean {
|
|
if (!latestNpmVersion || installedVersion === "Loading..." || installedVersion === "Unknown") {
|
|
return false;
|
|
}
|
|
const semverMatch = /(\d+\.\d+\.\d+)/.exec(installedVersion);
|
|
if (!semverMatch) return false;
|
|
return compareVersions(semverMatch[1], latestNpmVersion) < 0;
|
|
}
|
|
|
|
describe("updateAvailable", () => {
|
|
it("returns false when latestNpmVersion is null", () => {
|
|
expect(isUpdateAvailable("2.1.70", null)).toBe(false);
|
|
});
|
|
|
|
it("returns false when installed is Loading...", () => {
|
|
expect(isUpdateAvailable("Loading...", "2.1.74")).toBe(false);
|
|
});
|
|
|
|
it("returns false when installed is Unknown", () => {
|
|
expect(isUpdateAvailable("Unknown", "2.1.74")).toBe(false);
|
|
});
|
|
|
|
it("returns false when installed equals latest", () => {
|
|
expect(isUpdateAvailable("2.1.74", "2.1.74")).toBe(false);
|
|
});
|
|
|
|
it("returns false when installed is ahead of latest", () => {
|
|
expect(isUpdateAvailable("2.1.75", "2.1.74")).toBe(false);
|
|
});
|
|
|
|
it("returns true when installed is behind latest", () => {
|
|
expect(isUpdateAvailable("2.1.70", "2.1.74")).toBe(true);
|
|
});
|
|
|
|
it("returns true when installed has a lower minor version", () => {
|
|
expect(isUpdateAvailable("2.0.99", "2.1.74")).toBe(true);
|
|
});
|
|
|
|
it("handles version strings with extra info like '2.1.70 (build 123)'", () => {
|
|
expect(isUpdateAvailable("2.1.70 (build 123)", "2.1.74")).toBe(true);
|
|
});
|
|
|
|
it("returns false for unparseable installed version", () => {
|
|
expect(isUpdateAvailable("not-a-version", "2.1.74")).toBe(false);
|
|
});
|
|
});
|