import { EnvironmentId } from "@t3tools/contracts";import type { ServerUpdateState } from "@t3tools/client-runtime/state/server";import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; // Pinned so the direction cases below read as fixed versions instead of// arithmetic on whatever version this checkout happens to be at.const branding = vi.hoisted(() => ({ APP_VERSION: "0.0.34" }));vi.mock("./branding", () => branding); import { APP_VERSION } from "./branding";import { buildVersionMismatchDismissalKey, dismissServerUpdateFailure, dismissVersionMismatch, isServerUpdateFailureDismissed, isVersionMismatchDismissed, resolveServerConfigVersionMismatch, resolveServerSelfUpdateCapability, resolveVersionMismatch, serverUpdateGuidance, supportsDesktopAppUpdate,} from "./versionSkew"; const MISMATCH_HINT = "Version mismatch. Try syncing the client and server to the same T3 Code version."; describe("versionSkew", () => { beforeEach(() => { branding.APP_VERSION = "0.0.34"; }); it("dismisses only the current failed attempt without clearing its retry state", () => { const failure = { status: "failed", stage: "downloading", fromVersion: "0.0.33", targetVersion: "0.0.34", message: "Download failed.", } as const satisfies ServerUpdateState; const retryFailure = { ...failure }; const otherEnvironmentFailure = { ...failure }; dismissServerUpdateFailure(failure); expect(isServerUpdateFailureDismissed(failure)).toBe(true); expect(failure.status).toBe("failed"); expect(failure.message).toBe("Download failed."); expect(isServerUpdateFailureDismissed(retryFailure)).toBe(false); expect(isServerUpdateFailureDismissed(otherEnvironmentFailure)).toBe(false); }); it("does not dismiss an update that is still running", () => { const running = { status: "running", stage: "resuming", fromVersion: "0.0.33", targetVersion: "0.0.34", } as const satisfies ServerUpdateState; dismissServerUpdateFailure(running); expect(isServerUpdateFailureDismissed(running)).toBe(false); }); it("does not warn when versions match", () => { expect(resolveVersionMismatch(APP_VERSION)).toBeNull(); }); it("returns a mismatch when the server is behind the client", () => { expect(resolveVersionMismatch("0.0.33")).toEqual({ clientVersion: "0.0.34", serverVersion: "0.0.33", hint: MISMATCH_HINT, }); }); it("does not warn when the server is ahead of the client", () => { expect(resolveVersionMismatch("0.0.35")).toBeNull(); expect(resolveVersionMismatch("9.9.9")).toBeNull(); }); it("does not warn when a nightly and a stable build share a core version", () => { expect(resolveVersionMismatch("0.0.34-nightly.20260818.1124")).toBeNull(); branding.APP_VERSION = "0.0.34-nightly.20260818.1124"; expect(resolveVersionMismatch("0.0.34")).toBeNull(); }); it.each(["0.0.34-nightly.20260823.1124", "0.0.34-nightly.20260824.1124"])( "warns when nightly server %s is behind a nightly client on the same release", (serverVersion) => { branding.APP_VERSION = "0.0.34-nightly.20260824.1125"; expect(resolveVersionMismatch(serverVersion)).toEqual({ clientVersion: "0.0.34-nightly.20260824.1125", serverVersion, hint: MISMATCH_HINT, }); }, ); it("does not warn when a nightly server is ahead on the same release", () => { branding.APP_VERSION = "0.0.34-nightly.20260824.1125"; expect(resolveVersionMismatch("0.0.34-nightly.20260824.1126")).toBeNull(); }); it("treats a nightly server built past the client as ahead, not skew", () => { expect(resolveVersionMismatch("0.0.35-nightly.20260818.1124")).toBeNull(); }); it("still warns when a nightly client outruns the server by a release", () => { branding.APP_VERSION = "0.0.35-nightly.20260818.1124"; expect(resolveVersionMismatch("0.0.34")).toEqual({ clientVersion: "0.0.35-nightly.20260818.1124", serverVersion: "0.0.34", hint: MISMATCH_HINT, }); }); it("falls back to string inequality when a version is not semver", () => { expect(resolveVersionMismatch("dev")).toEqual({ clientVersion: "0.0.34", serverVersion: "dev", hint: MISMATCH_HINT, }); branding.APP_VERSION = "dev"; expect(resolveVersionMismatch("dev")).toBeNull(); expect(resolveVersionMismatch("0.0.34")).toMatchObject({ serverVersion: "0.0.34" }); }); it("reads the server version from config descriptors", () => { expect( resolveServerConfigVersionMismatch({ environment: { environmentId: EnvironmentId.make("environment-1"), label: "Remote", platform: { os: "darwin", arch: "arm64", }, serverVersion: "0.0.33", capabilities: { repositoryIdentity: true, }, }, }), ).toMatchObject({ serverVersion: "0.0.33", }); }); it("keys dismissals by environment, client version, and server version", () => { const environmentId = EnvironmentId.make("environment-dismissal"); const key = buildVersionMismatchDismissalKey(environmentId, { clientVersion: APP_VERSION, serverVersion: "9.9.9", }); expect(key).toBe(`${environmentId}:${APP_VERSION}:9.9.9`); expect(isVersionMismatchDismissed(key)).toBe(false); dismissVersionMismatch(key); expect(isVersionMismatchDismissed(key)).toBe(true); expect( isVersionMismatchDismissed( buildVersionMismatchDismissalKey(environmentId, { clientVersion: APP_VERSION, serverVersion: "9.9.10", }), ), ).toBe(false); }); it("reads desktop-managed update capabilities from config descriptors", () => { expect( resolveServerSelfUpdateCapability({ environment: { environmentId: EnvironmentId.make("environment-desktop"), label: "Desktop", platform: { os: "darwin", arch: "arm64" }, serverVersion: "9.9.9", capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed", }, }, }), ).toBe("desktop-managed"); expect(resolveServerSelfUpdateCapability(null)).toBeNull(); }); it("detects remote desktop-app update support from config descriptors", () => { const descriptor = (desktopAppUpdate?: boolean) => ({ environment: { environmentId: EnvironmentId.make("environment-desktop"), label: "Desktop", platform: { os: "darwin", arch: "arm64" } as const, serverVersion: "9.9.9", capabilities: { repositoryIdentity: true, serverSelfUpdate: "desktop-managed" as const, ...(desktopAppUpdate === undefined ? {} : { desktopAppUpdate }), }, }, }); expect(supportsDesktopAppUpdate(descriptor(true))).toBe(true); expect(supportsDesktopAppUpdate(descriptor(false))).toBe(false); expect(supportsDesktopAppUpdate(descriptor())).toBe(false); expect(supportsDesktopAppUpdate(null)).toBe(false); }); it("matches version-drift guidance to the advertised update path", () => { expect(serverUpdateGuidance("respawn")).toBe("Update to stay in sync"); expect(serverUpdateGuidance("desktop-managed")).toBe("Update the desktop app"); });});
import { EnvironmentId } from "@t3tools/contracts";
import type { ServerUpdateState } from "@t3tools/client-runtime/state/server";
import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
// Pinned so the direction cases below read as fixed versions instead of
// arithmetic on whatever version this checkout happens to be at.
const branding = vi.hoisted(() => ({ APP_VERSION: "0.0.34" }));
vi.mock("./branding", () => branding);
import { APP_VERSION } from "./branding";
import {
buildVersionMismatchDismissalKey,
dismissServerUpdateFailure,
dismissVersionMismatch,
isServerUpdateFailureDismissed,
isVersionMismatchDismissed,
resolveServerConfigVersionMismatch,
resolveServerSelfUpdateCapability,
resolveVersionMismatch,
serverUpdateGuidance,
supportsDesktopAppUpdate,
} from "./versionSkew";
const MISMATCH_HINT =
"Version mismatch. Try syncing the client and server to the same T3 Code version.";
describe("versionSkew", () => {
beforeEach(() => {
branding.APP_VERSION = "0.0.34";
});
it("dismisses only the current failed attempt without clearing its retry state", () => {
const failure = {
status: "failed",
stage: "downloading",
fromVersion: "0.0.33",
targetVersion: "0.0.34",
message: "Download failed.",
} as const satisfies ServerUpdateState;
const retryFailure = { ...failure };
const otherEnvironmentFailure = { ...failure };
dismissServerUpdateFailure(failure);
expect(isServerUpdateFailureDismissed(failure)).toBe(true);
expect(failure.status).toBe("failed");
expect(failure.message).toBe("Download failed.");
expect(isServerUpdateFailureDismissed(retryFailure)).toBe(false);
expect(isServerUpdateFailureDismissed(otherEnvironmentFailure)).toBe(false);
});
it("does not dismiss an update that is still running", () => {
const running = {
status: "running",
stage: "resuming",
fromVersion: "0.0.33",
targetVersion: "0.0.34",
} as const satisfies ServerUpdateState;
dismissServerUpdateFailure(running);
expect(isServerUpdateFailureDismissed(running)).toBe(false);
});
it("does not warn when versions match", () => {
expect(resolveVersionMismatch(APP_VERSION)).toBeNull();
});
it("returns a mismatch when the server is behind the client", () => {
expect(resolveVersionMismatch("0.0.33")).toEqual({
clientVersion: "0.0.34",
serverVersion: "0.0.33",
hint: MISMATCH_HINT,
});
});
it("does not warn when the server is ahead of the client", () => {
expect(resolveVersionMismatch("0.0.35")).toBeNull();
expect(resolveVersionMismatch("9.9.9")).toBeNull();
});
it("does not warn when a nightly and a stable build share a core version", () => {
expect(resolveVersionMismatch("0.0.34-nightly.20260818.1124")).toBeNull();
branding.APP_VERSION = "0.0.34-nightly.20260818.1124";
expect(resolveVersionMismatch("0.0.34")).toBeNull();
});
it.each(["0.0.34-nightly.20260823.1124", "0.0.34-nightly.20260824.1124"])(
"warns when nightly server %s is behind a nightly client on the same release",
(serverVersion) => {
branding.APP_VERSION = "0.0.34-nightly.20260824.1125";
expect(resolveVersionMismatch(serverVersion)).toEqual({
clientVersion: "0.0.34-nightly.20260824.1125",
serverVersion,
hint: MISMATCH_HINT,
});
},
);
it("does not warn when a nightly server is ahead on the same release", () => {
branding.APP_VERSION = "0.0.34-nightly.20260824.1125";
expect(resolveVersionMismatch("0.0.34-nightly.20260824.1126")).toBeNull();
});
it("treats a nightly server built past the client as ahead, not skew", () => {
expect(resolveVersionMismatch("0.0.35-nightly.20260818.1124")).toBeNull();
});
it("still warns when a nightly client outruns the server by a release", () => {
branding.APP_VERSION = "0.0.35-nightly.20260818.1124";
expect(resolveVersionMismatch("0.0.34")).toEqual({
clientVersion: "0.0.35-nightly.20260818.1124",
serverVersion: "0.0.34",
hint: MISMATCH_HINT,
});
});
it("falls back to string inequality when a version is not semver", () => {
expect(resolveVersionMismatch("dev")).toEqual({
clientVersion: "0.0.34",
serverVersion: "dev",
hint: MISMATCH_HINT,
});
branding.APP_VERSION = "dev";
expect(resolveVersionMismatch("dev")).toBeNull();
expect(resolveVersionMismatch("0.0.34")).toMatchObject({ serverVersion: "0.0.34" });
});
it("reads the server version from config descriptors", () => {
expect(
resolveServerConfigVersionMismatch({
environment: {
environmentId: EnvironmentId.make("environment-1"),
label: "Remote",
platform: {
os: "darwin",
arch: "arm64",
},
serverVersion: "0.0.33",
capabilities: {
repositoryIdentity: true,
},
},
}),
).toMatchObject({
serverVersion: "0.0.33",
});
});
it("keys dismissals by environment, client version, and server version", () => {
const environmentId = EnvironmentId.make("environment-dismissal");
const key = buildVersionMismatchDismissalKey(environmentId, {
clientVersion: APP_VERSION,
serverVersion: "9.9.9",
});
expect(key).toBe(`${environmentId}:${APP_VERSION}:9.9.9`);
expect(isVersionMismatchDismissed(key)).toBe(false);
dismissVersionMismatch(key);
expect(isVersionMismatchDismissed(key)).toBe(true);
expect(
isVersionMismatchDismissed(
buildVersionMismatchDismissalKey(environmentId, {
clientVersion: APP_VERSION,
serverVersion: "9.9.10",
}),
),
).toBe(false);
});
it("reads desktop-managed update capabilities from config descriptors", () => {
expect(
resolveServerSelfUpdateCapability({
environment: {
environmentId: EnvironmentId.make("environment-desktop"),
label: "Desktop",
platform: { os: "darwin", arch: "arm64" },
serverVersion: "9.9.9",
capabilities: {
repositoryIdentity: true,
serverSelfUpdate: "desktop-managed",
},
},
}),
).toBe("desktop-managed");
expect(resolveServerSelfUpdateCapability(null)).toBeNull();
});
it("detects remote desktop-app update support from config descriptors", () => {
const descriptor = (desktopAppUpdate?: boolean) => ({
environment: {
environmentId: EnvironmentId.make("environment-desktop"),
label: "Desktop",
platform: { os: "darwin", arch: "arm64" } as const,
serverVersion: "9.9.9",
capabilities: {
repositoryIdentity: true,
serverSelfUpdate: "desktop-managed" as const,
...(desktopAppUpdate === undefined ? {} : { desktopAppUpdate }),
},
},
});
expect(supportsDesktopAppUpdate(descriptor(true))).toBe(true);
expect(supportsDesktopAppUpdate(descriptor(false))).toBe(false);
expect(supportsDesktopAppUpdate(descriptor())).toBe(false);
expect(supportsDesktopAppUpdate(null)).toBe(false);
});
it("matches version-drift guidance to the advertised update path", () => {
expect(serverUpdateGuidance("respawn")).toBe("Update to stay in sync");
expect(serverUpdateGuidance("desktop-managed")).toBe("Update the desktop app");
});
});