diff options
| author | Adam Malczewski <[email protected]> | 2026-06-26 22:21:55 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-26 22:23:39 +0900 |
| commit | c333fcec32b1f90bf0da6bb14d2609c20e38a74f (patch) | |
| tree | 0db3ec77a6838c4f800c362df0de3c6cd8544431 /src/features/workspaces/logic | |
| parent | 1285564f12238b22f6b39b9f3fbcecaca8456911 (diff) | |
| download | dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.tar.gz dispatch-web-c333fcec32b1f90bf0da6bb14d2609c20e38a74f.zip | |
style: switch from tabs to 2-space indentation (incl. svelte)
Diffstat (limited to 'src/features/workspaces/logic')
| -rw-r--r-- | src/features/workspaces/logic/route.test.ts | 112 | ||||
| -rw-r--r-- | src/features/workspaces/logic/route.ts | 26 | ||||
| -rw-r--r-- | src/features/workspaces/logic/view-model.test.ts | 98 | ||||
| -rw-r--r-- | src/features/workspaces/logic/view-model.ts | 32 |
4 files changed, 134 insertions, 134 deletions
diff --git a/src/features/workspaces/logic/route.test.ts b/src/features/workspaces/logic/route.test.ts index a6da3e1..96e0ff3 100644 --- a/src/features/workspaces/logic/route.test.ts +++ b/src/features/workspaces/logic/route.test.ts @@ -1,77 +1,77 @@ import { describe, expect, it } from "vitest"; import { - DEFAULT_WORKSPACE_ID, - isValidSlug, - parsePath, - WORKSPACE_SLUG_RE, - workspacePath, + DEFAULT_WORKSPACE_ID, + isValidSlug, + parsePath, + WORKSPACE_SLUG_RE, + workspacePath, } from "./route"; describe("parsePath", () => { - it("treats the root path as home", () => { - expect(parsePath("/")).toEqual({ kind: "home" }); - expect(parsePath("")).toEqual({ kind: "home" }); - }); + it("treats the root path as home", () => { + expect(parsePath("/")).toEqual({ kind: "home" }); + expect(parsePath("")).toEqual({ kind: "home" }); + }); - it("trims surrounding slashes", () => { - expect(parsePath("//")).toEqual({ kind: "home" }); - expect(parsePath("/my-ws/")).toEqual({ kind: "workspace", id: "my-ws" }); - }); + it("trims surrounding slashes", () => { + expect(parsePath("//")).toEqual({ kind: "home" }); + expect(parsePath("/my-ws/")).toEqual({ kind: "workspace", id: "my-ws" }); + }); - it("parses a single segment as a workspace id", () => { - expect(parsePath("/default")).toEqual({ kind: "workspace", id: "default" }); - expect(parsePath("/my-workspace")).toEqual({ kind: "workspace", id: "my-workspace" }); - expect(parsePath("/ws1")).toEqual({ kind: "workspace", id: "ws1" }); - }); + it("parses a single segment as a workspace id", () => { + expect(parsePath("/default")).toEqual({ kind: "workspace", id: "default" }); + expect(parsePath("/my-workspace")).toEqual({ kind: "workspace", id: "my-workspace" }); + expect(parsePath("/ws1")).toEqual({ kind: "workspace", id: "ws1" }); + }); - it("takes only the first segment of a deeper path", () => { - expect(parsePath("/foo/bar")).toEqual({ kind: "workspace", id: "foo" }); - expect(parsePath("/foo/bar/baz")).toEqual({ kind: "workspace", id: "foo" }); - }); + it("takes only the first segment of a deeper path", () => { + expect(parsePath("/foo/bar")).toEqual({ kind: "workspace", id: "foo" }); + expect(parsePath("/foo/bar/baz")).toEqual({ kind: "workspace", id: "foo" }); + }); - it("URL-decodes the segment", () => { - expect(parsePath("/my%20ws")).toEqual({ kind: "workspace", id: "my ws" }); - }); + it("URL-decodes the segment", () => { + expect(parsePath("/my%20ws")).toEqual({ kind: "workspace", id: "my ws" }); + }); - it("does not validate the slug — an invalid id is still a workspace route", () => { - expect(parsePath("/UPPER")).toEqual({ kind: "workspace", id: "UPPER" }); - expect(parsePath("/has space")).toEqual({ kind: "workspace", id: "has space" }); - }); + it("does not validate the slug — an invalid id is still a workspace route", () => { + expect(parsePath("/UPPER")).toEqual({ kind: "workspace", id: "UPPER" }); + expect(parsePath("/has space")).toEqual({ kind: "workspace", id: "has space" }); + }); }); describe("isValidSlug", () => { - it("accepts lowercase alphanumeric + internal hyphens", () => { - expect(isValidSlug("default")).toBe(true); - expect(isValidSlug("my-workspace")).toBe(true); - expect(isValidSlug("a")).toBe(true); - expect(isValidSlug("ws-1")).toBe(true); - }); + it("accepts lowercase alphanumeric + internal hyphens", () => { + expect(isValidSlug("default")).toBe(true); + expect(isValidSlug("my-workspace")).toBe(true); + expect(isValidSlug("a")).toBe(true); + expect(isValidSlug("ws-1")).toBe(true); + }); - it("accepts up to 40 chars", () => { - expect(isValidSlug("a".repeat(40))).toBe(true); - }); + it("accepts up to 40 chars", () => { + expect(isValidSlug("a".repeat(40))).toBe(true); + }); - it("rejects empty and too-long", () => { - expect(isValidSlug("")).toBe(false); - expect(isValidSlug("a".repeat(41))).toBe(false); - }); + it("rejects empty and too-long", () => { + expect(isValidSlug("")).toBe(false); + expect(isValidSlug("a".repeat(41))).toBe(false); + }); - it("rejects uppercase, spaces, and leading/trailing hyphens", () => { - expect(isValidSlug("MyWS")).toBe(false); - expect(isValidSlug("has space")).toBe(false); - expect(isValidSlug("-leading")).toBe(false); - expect(isValidSlug("trailing-")).toBe(false); - expect(isValidSlug("double--hyphen")).toBe(true); // internal doubles are allowed by the regex - }); + it("rejects uppercase, spaces, and leading/trailing hyphens", () => { + expect(isValidSlug("MyWS")).toBe(false); + expect(isValidSlug("has space")).toBe(false); + expect(isValidSlug("-leading")).toBe(false); + expect(isValidSlug("trailing-")).toBe(false); + expect(isValidSlug("double--hyphen")).toBe(true); // internal doubles are allowed by the regex + }); - it("WORKSPACE_SLUG_RE matches the default id", () => { - expect(WORKSPACE_SLUG_RE.test(DEFAULT_WORKSPACE_ID)).toBe(true); - }); + it("WORKSPACE_SLUG_RE matches the default id", () => { + expect(WORKSPACE_SLUG_RE.test(DEFAULT_WORKSPACE_ID)).toBe(true); + }); }); describe("workspacePath", () => { - it("builds the URL path for a workspace id", () => { - expect(workspacePath("default")).toBe("/default"); - expect(workspacePath("my-ws")).toBe("/my-ws"); - }); + it("builds the URL path for a workspace id", () => { + expect(workspacePath("default")).toBe("/default"); + expect(workspacePath("my-ws")).toBe("/my-ws"); + }); }); diff --git a/src/features/workspaces/logic/route.ts b/src/features/workspaces/logic/route.ts index e30dc16..015c6d6 100644 --- a/src/features/workspaces/logic/route.ts +++ b/src/features/workspaces/logic/route.ts @@ -30,12 +30,12 @@ export const DEFAULT_WORKSPACE_ID = "default"; * surfaces the error). */ export function parsePath(pathname: string): Route { - const trimmed = pathname.replace(/^\/+|\/+$/g, ""); - if (trimmed === "") return { kind: "home" }; - const first = trimmed.split("/")[0] ?? ""; - const id = safeDecode(first); - if (id === "") return { kind: "home" }; - return { kind: "workspace", id }; + const trimmed = pathname.replace(/^\/+|\/+$/g, ""); + if (trimmed === "") return { kind: "home" }; + const first = trimmed.split("/")[0] ?? ""; + const id = safeDecode(first); + if (id === "") return { kind: "home" }; + return { kind: "workspace", id }; } /** @@ -43,7 +43,7 @@ export function parsePath(pathname: string): Route { * Used by the home view's "new workspace" input before navigating. */ export function isValidSlug(slug: string): boolean { - return WORKSPACE_SLUG_RE.test(slug); + return WORKSPACE_SLUG_RE.test(slug); } /** @@ -51,13 +51,13 @@ export function isValidSlug(slug: string): boolean { * workspace. Pure: id in, path string out. */ export function workspacePath(id: string): string { - return `/${id}`; + return `/${id}`; } function safeDecode(segment: string): string { - try { - return decodeURIComponent(segment); - } catch { - return segment; - } + try { + return decodeURIComponent(segment); + } catch { + return segment; + } } diff --git a/src/features/workspaces/logic/view-model.test.ts b/src/features/workspaces/logic/view-model.test.ts index a4ed19c..86342e7 100644 --- a/src/features/workspaces/logic/view-model.test.ts +++ b/src/features/workspaces/logic/view-model.test.ts @@ -3,66 +3,66 @@ import { describe, expect, it } from "vitest"; import { pageTitle, relativeTime } from "./view-model"; describe("relativeTime", () => { - const now = 1_000_000_000_000; // 2001-09-09 + const now = 1_000_000_000_000; // 2001-09-09 - it("is 'now' within a minute", () => { - expect(relativeTime(now, now)).toBe("now"); - expect(relativeTime(now - 59_000, now)).toBe("now"); - }); + it("is 'now' within a minute", () => { + expect(relativeTime(now, now)).toBe("now"); + expect(relativeTime(now - 59_000, now)).toBe("now"); + }); - it("is minutes under an hour", () => { - expect(relativeTime(now - 5 * 60_000, now)).toBe("5m"); - expect(relativeTime(now - 59 * 60_000, now)).toBe("59m"); - }); + it("is minutes under an hour", () => { + expect(relativeTime(now - 5 * 60_000, now)).toBe("5m"); + expect(relativeTime(now - 59 * 60_000, now)).toBe("59m"); + }); - it("is hours under a day", () => { - expect(relativeTime(now - 2 * 60 * 60_000, now)).toBe("2h"); - }); + it("is hours under a day", () => { + expect(relativeTime(now - 2 * 60 * 60_000, now)).toBe("2h"); + }); - it("is days under a week", () => { - expect(relativeTime(now - 3 * 24 * 60 * 60_000, now)).toBe("3d"); - }); + it("is days under a week", () => { + expect(relativeTime(now - 3 * 24 * 60 * 60_000, now)).toBe("3d"); + }); - it("is a short date beyond a week", () => { - // 7+ days ago: just check it is a MM/DD string. - const s = relativeTime(now - 10 * 24 * 60 * 60_000, now); - expect(s).toMatch(/^\d{2}\/\d{2}$/); - }); + it("is a short date beyond a week", () => { + // 7+ days ago: just check it is a MM/DD string. + const s = relativeTime(now - 10 * 24 * 60 * 60_000, now); + expect(s).toMatch(/^\d{2}\/\d{2}$/); + }); }); describe("pageTitle", () => { - // Minimal valid WorkspaceEntry (the irrelevant metadata is zeroed). - const ws = (id: string, title: string): WorkspaceEntry => ({ - id, - title, - defaultCwd: null, - defaultComputerId: null, - createdAt: 0, - lastActivityAt: 0, - conversationCount: 0, - }); + // Minimal valid WorkspaceEntry (the irrelevant metadata is zeroed). + const ws = (id: string, title: string): WorkspaceEntry => ({ + id, + title, + defaultCwd: null, + defaultComputerId: null, + createdAt: 0, + lastActivityAt: 0, + conversationCount: 0, + }); - it("is 'Dispatch' for the home route", () => { - expect(pageTitle({ kind: "home" }, [])).toBe("Dispatch"); - expect(pageTitle({ kind: "home" }, [ws("default", "Default")])).toBe("Dispatch"); - }); + it("is 'Dispatch' for the home route", () => { + expect(pageTitle({ kind: "home" }, [])).toBe("Dispatch"); + expect(pageTitle({ kind: "home" }, [ws("default", "Default")])).toBe("Dispatch"); + }); - it("is 'Dispatch: {title}' for a workspace with a display title", () => { - const list = [ws("default", "Default"), ws("my-ws", "My Workspace")]; - expect(pageTitle({ kind: "workspace", id: "my-ws" }, list)).toBe("Dispatch: My Workspace"); - }); + it("is 'Dispatch: {title}' for a workspace with a display title", () => { + const list = [ws("default", "Default"), ws("my-ws", "My Workspace")]; + expect(pageTitle({ kind: "workspace", id: "my-ws" }, list)).toBe("Dispatch: My Workspace"); + }); - it("falls back to the slug (id) until the list has loaded the workspace", () => { - expect(pageTitle({ kind: "workspace", id: "pending" }, [])).toBe("Dispatch: pending"); - }); + it("falls back to the slug (id) until the list has loaded the workspace", () => { + expect(pageTitle({ kind: "workspace", id: "pending" }, [])).toBe("Dispatch: pending"); + }); - it("uses the id as the title when it was never customized (defaults to id)", () => { - const list = [ws("default", "default")]; - expect(pageTitle({ kind: "workspace", id: "default" }, list)).toBe("Dispatch: default"); - }); + it("uses the id as the title when it was never customized (defaults to id)", () => { + const list = [ws("default", "default")]; + expect(pageTitle({ kind: "workspace", id: "default" }, list)).toBe("Dispatch: default"); + }); - it("matches by id, not title", () => { - const list = [ws("a", "shared-title"), ws("b", "shared-title")]; - expect(pageTitle({ kind: "workspace", id: "b" }, list)).toBe("Dispatch: shared-title"); - }); + it("matches by id, not title", () => { + const list = [ws("a", "shared-title"), ws("b", "shared-title")]; + expect(pageTitle({ kind: "workspace", id: "b" }, list)).toBe("Dispatch: shared-title"); + }); }); diff --git a/src/features/workspaces/logic/view-model.ts b/src/features/workspaces/logic/view-model.ts index e904514..6dd64c6 100644 --- a/src/features/workspaces/logic/view-model.ts +++ b/src/features/workspaces/logic/view-model.ts @@ -14,9 +14,9 @@ import type { Route } from "./route"; * workspaces in, string out. */ export function pageTitle(route: Route, workspaces: readonly WorkspaceEntry[]): string { - if (route.kind === "home") return "Dispatch"; - const ws = workspaces.find((w) => w.id === route.id); - return `Dispatch: ${ws?.title ?? route.id}`; + if (route.kind === "home") return "Dispatch"; + const ws = workspaces.find((w) => w.id === route.id); + return `Dispatch: ${ws?.title ?? route.id}`; } /** @@ -25,17 +25,17 @@ export function pageTitle(route: Route, workspaces: readonly WorkspaceEntry[]): * (a workspace just created) read as "now". */ export function relativeTime(then: number, now: number): string { - const diff = now - then; - if (diff < 60_000) return "now"; - const mins = Math.floor(diff / 60_000); - if (mins < 60) return `${mins}m`; - const hours = Math.floor(mins / 60); - if (hours < 24) return `${hours}h`; - const days = Math.floor(hours / 24); - if (days < 7) return `${days}d`; - // Beyond a week: a short date (MM/DD). Uses UTC parts for determinism in tests. - const d = new Date(then); - const month = String(d.getUTCMonth() + 1).padStart(2, "0"); - const day = String(d.getUTCDate()).padStart(2, "0"); - return `${month}/${day}`; + const diff = now - then; + if (diff < 60_000) return "now"; + const mins = Math.floor(diff / 60_000); + if (mins < 60) return `${mins}m`; + const hours = Math.floor(mins / 60); + if (hours < 24) return `${hours}h`; + const days = Math.floor(hours / 24); + if (days < 7) return `${days}d`; + // Beyond a week: a short date (MM/DD). Uses UTC parts for determinism in tests. + const d = new Date(then); + const month = String(d.getUTCMonth() + 1).padStart(2, "0"); + const day = String(d.getUTCDate()).padStart(2, "0"); + return `${month}/${day}`; } |
