diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..ee9d1a22 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +{ + "name": "OpenCode Development Container", + "image": "mcr.microsoft.com/devcontainers/typescript-node:5-22-bookworm", + "features": { + "ghcr.io/devcontainers-extra/features/bun:1": { + "version": "1.3.14" + } + } +} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e05ce79..58739c5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,6 +43,11 @@ jobs: - name: Setup Bun uses: ./.github/actions/setup-bun + - name: Run updater action tests + timeout-minutes: 2 + working-directory: packages/app + run: bun test --conditions=solid --preload ./happydom.ts ./src/components/updater-action.test.ts + - name: Configure git identity run: | git config --global user.email "bot@opencode.ai" diff --git a/packages/app/src/components/updater-action.test.ts b/packages/app/src/components/updater-action.test.ts index 46e7c732..b4ddc855 100644 --- a/packages/app/src/components/updater-action.test.ts +++ b/packages/app/src/components/updater-action.test.ts @@ -1,26 +1,63 @@ import { describe, expect, test } from "bun:test" +import type { UpdaterState } from "@/updater" import { updaterAction } from "./updater-action" describe("updaterAction", () => { - test("disables update actions when the platform has no updater", () => { - expect(updaterAction(undefined)).toEqual({ label: "settings.updates.action.checkNow" }) - }) + const cases: { + name: string + state: UpdaterState | undefined + expected: ReturnType + }[] = [ + { + name: "uses check now without an action when state is unavailable", + state: undefined, + expected: { label: "settings.updates.action.checkNow" }, + }, + { + name: "uses check now without an action when updates are disabled", + state: { status: "disabled" }, + expected: { label: "settings.updates.action.checkNow" }, + }, + { + name: "allows checking when the updater is idle", + state: { status: "idle" }, + expected: { label: "settings.updates.action.checkNow", run: "check" }, + }, + { + name: "shows checking without an action while checking", + state: { status: "checking" }, + expected: { label: "settings.updates.action.checking" }, + }, + { + name: "shows downloading without an action while downloading", + state: { status: "downloading", version: "1.2.3", percent: 50 }, + expected: { label: "settings.updates.action.downloading" }, + }, + { + name: "allows installation when an update is ready", + state: { status: "ready", version: "1.2.3" }, + expected: { label: "toast.update.action.installRestart", run: "install" }, + }, + { + name: "shows installing without an action while installing", + state: { status: "installing", version: "1.2.3" }, + expected: { label: "settings.updates.action.installing" }, + }, + { + name: "allows another check when already up to date", + state: { status: "up-to-date" }, + expected: { label: "settings.updates.action.checkNow", run: "check" }, + }, + { + name: "allows retrying a check after an error", + state: { status: "error", message: "Update failed" }, + expected: { label: "settings.updates.action.checkNow", run: "check" }, + }, + ] - test("projects updater transitions into one settings action", () => { - expect(updaterAction({ status: "idle" })).toEqual({ - label: "settings.updates.action.checkNow", - run: "check", + for (const item of cases) { + test(item.name, () => { + expect(updaterAction(item.state)).toEqual(item.expected) }) - expect(updaterAction({ status: "checking" })).toEqual({ label: "settings.updates.action.checking" }) - expect(updaterAction({ status: "downloading", version: "2.0.0" })).toEqual({ - label: "settings.updates.action.downloading", - }) - expect(updaterAction({ status: "ready", version: "2.0.0" })).toEqual({ - label: "toast.update.action.installRestart", - run: "install", - }) - expect(updaterAction({ status: "installing", version: "2.0.0" })).toEqual({ - label: "settings.updates.action.installing", - }) - }) + } }) diff --git a/packages/app/src/components/updater-action.ts b/packages/app/src/components/updater-action.ts index 9c13c542..a37cf610 100644 --- a/packages/app/src/components/updater-action.ts +++ b/packages/app/src/components/updater-action.ts @@ -4,22 +4,24 @@ import { usePlatform } from "@/context/platform" import { useLanguage } from "@/context/language" import { showToast } from "@/utils/toast" -export function updaterAction(state: UpdaterState | undefined) { - if (!state) return { label: "settings.updates.action.checkNow" as const } - switch (state.status) { - case "checking": - return { label: "settings.updates.action.checking" as const } - case "downloading": - return { label: "settings.updates.action.downloading" as const } - case "ready": - return { label: "toast.update.action.installRestart" as const, run: "install" as const } - case "installing": - return { label: "settings.updates.action.installing" as const } - case "disabled": - return { label: "settings.updates.action.checkNow" as const } - default: - return { label: "settings.updates.action.checkNow" as const, run: "check" as const } - } +const updaterActions = { + disabled: { label: "settings.updates.action.checkNow" }, + idle: { label: "settings.updates.action.checkNow", run: "check" }, + checking: { label: "settings.updates.action.checking" }, + downloading: { label: "settings.updates.action.downloading" }, + ready: { label: "toast.update.action.installRestart", run: "install" }, + installing: { label: "settings.updates.action.installing" }, + "up-to-date": { label: "settings.updates.action.checkNow", run: "check" }, + error: { label: "settings.updates.action.checkNow", run: "check" }, +} as const satisfies Record + +type UpdaterAction = { + label: (typeof updaterActions)[UpdaterState["status"]]["label"] + run?: "check" | "install" +} + +export function updaterAction(state: UpdaterState | undefined): UpdaterAction { + return updaterActions[state?.status ?? "disabled"] } export function useUpdaterAction() {