Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/components/AboutDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import * as checkUpdate from "../lib/check-update";
import * as tauri from "../lib/tauri";
import { AboutDialog } from "./AboutDialog";

afterEach(() => {
vi.restoreAllMocks();
});

const release = (version: string) => ({
tagName: `v${version}`,
version,
htmlUrl: `https://github.com/oratis/Markup/releases/tag/v${version}`,
publishedAt: "2026-01-01T00:00:00Z",
name: null,
body: null,
});

describe("AboutDialog", () => {
it("shows the bundle id and renders the version once getVersion resolves", async () => {
vi.spyOn(tauri, "getVersion").mockResolvedValue("0.9.9");
Expand Down Expand Up @@ -34,4 +44,38 @@ describe("AboutDialog", () => {
fireEvent.click(screen.getByText(/^close$/i));
expect(onClose).toHaveBeenCalled();
});

it("Check for Updates reports when you're on the latest version", async () => {
vi.spyOn(tauri, "getVersion").mockResolvedValue("1.0.0");
vi.spyOn(checkUpdate, "checkUpdateAgainstGithub").mockResolvedValue({
hasUpdate: false,
current: "1.0.0",
latest: release("1.0.0"),
dismissed: false,
});
render(<AboutDialog onClose={() => {}} />);
fireEvent.click(screen.getByText("Check for Updates"));
await waitFor(() =>
expect(screen.getByText("You're on the latest version")).toBeInTheDocument(),
);
});

it("Check for Updates surfaces a newer release as a Get button", async () => {
vi.spyOn(tauri, "getVersion").mockResolvedValue("1.0.0");
vi.spyOn(checkUpdate, "checkUpdateAgainstGithub").mockResolvedValue({
hasUpdate: true,
current: "1.0.0",
latest: release("1.1.0"),
dismissed: false,
});
render(<AboutDialog onClose={() => {}} />);
fireEvent.click(screen.getByText("Check for Updates"));
await waitFor(() => expect(screen.getByText("Get v1.1.0")).toBeInTheDocument());
});

it("always offers a Changelog link", () => {
vi.spyOn(tauri, "getVersion").mockResolvedValue("1.0.0");
render(<AboutDialog onClose={() => {}} />);
expect(screen.getByText("Changelog")).toBeInTheDocument();
});
});
66 changes: 61 additions & 5 deletions src/components/AboutDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import { useEffect, useState } from "react";
import { type LatestRelease, checkUpdateAgainstGithub } from "../lib/check-update";
import { useT } from "../lib/i18n";
import { getVersion } from "../lib/tauri";

Expand All @@ -7,18 +9,39 @@ interface Props {
}

const REPO_URL = "https://github.com/oratis/Markup";
const RELEASES_URL = `${REPO_URL}/releases`;
const BUNDLE_ID = "com.appkon.markup";

type UpdateState =
| { kind: "idle" }
| { kind: "checking" }
| { kind: "upToDate" }
| { kind: "available"; latest: LatestRelease }
| { kind: "error" };

export function AboutDialog({ onClose }: Props) {
const t = useT();
const [version, setVersion] = useState<string>("…");
const [update, setUpdate] = useState<UpdateState>({ kind: "idle" });

useEffect(() => {
getVersion()
.then((v) => setVersion(v))
.catch(() => setVersion("dev"));
}, []);

const openExternal = (url: string) => {
openUrl(url).catch((e) => console.warn("about: failed to open url", e));
};

const checkUpdates = async () => {
setUpdate({ kind: "checking" });
const r = await checkUpdateAgainstGithub();
if (!r.latest) setUpdate({ kind: "error" });
else if (r.hasUpdate) setUpdate({ kind: "available", latest: r.latest });
else setUpdate({ kind: "upToDate" });
};

return (
<div
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
Expand All @@ -37,15 +60,48 @@ export function AboutDialog({ onClose }: Props) {
</div>
<div className="font-mono">{BUNDLE_ID}</div>
</div>

{/* Update check + changelog */}
<div className="mt-5 flex flex-col items-center gap-2 text-[12px]">
{update.kind === "available" ? (
<button
onClick={() => openExternal(update.latest.htmlUrl)}
className="px-3 py-1 rounded bg-blue-500 text-white hover:bg-blue-600"
>
{t("about.updateAvailable", update.latest.tagName)}
</button>
) : (
<button
onClick={checkUpdates}
disabled={update.kind === "checking"}
className="px-3 py-1 rounded border border-black/10 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/10 disabled:opacity-50"
>
{update.kind === "checking"
? t("about.checking")
: t("about.checkUpdates")}
</button>
)}
{update.kind === "upToDate" && (
<div className="opacity-60">{t("about.upToDate")}</div>
)}
{update.kind === "error" && (
<div className="opacity-60">{t("about.checkFailed")}</div>
)}
<button
onClick={() => openExternal(RELEASES_URL)}
className="underline opacity-70 hover:opacity-100"
>
{t("about.changelog")}
</button>
</div>

<div className="mt-5 text-[11px]">
<a
href={REPO_URL}
target="_blank"
rel="noreferrer"
<button
onClick={() => openExternal(REPO_URL)}
className="underline opacity-80 hover:opacity-100"
>
{REPO_URL}
</a>
</button>
</div>
<button
onClick={onClose}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export const en = {
"about.tagline": "High-performance Markdown editor for macOS",
"about.version": "Version",
"about.close": "Close",
"about.checkUpdates": "Check for Updates",
"about.checking": "Checking…",
"about.upToDate": "You're on the latest version",
"about.updateAvailable": "Get {0}",
"about.checkFailed": "Couldn't check for updates",
"about.changelog": "Changelog",

// command palette / quick open
"palette.placeholder": "Run a command…",
Expand Down
6 changes: 6 additions & 0 deletions src/lib/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ export const zh: Strings = {
"about.tagline": "面向 macOS 的高性能 Markdown 编辑器",
"about.version": "版本",
"about.close": "关闭",
"about.checkUpdates": "检查更新",
"about.checking": "检查中…",
"about.upToDate": "已是最新版本",
"about.updateAvailable": "获取 {0}",
"about.checkFailed": "无法检查更新",
"about.changelog": "更新日志",

// command palette / quick open
"palette.placeholder": "运行命令…",
Expand Down
Loading