Q160: a goal's pace is measured against its target date, and claimed only when it has one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
459dcf0ea2
commit
c9117fe46d
18 files changed
+172
-24
No files matched your search
@@ -72,6 +72,13 @@ test("the pinned CEO conversation is the Messages front door", async () => {
|
||||
// The message itself says where the reply stands.
|
||||
await expect(chatLog.locator(".chat-message", { hasText: "How are we tracking against the goal?" }).first()).toContainText("CEO will reply; queued.");
|
||||
|
||||
// A goal's pace is measured against its target date; without one, no pace is claimed.
|
||||
await page.getByRole("button", { name: "Home" }).click();
|
||||
await expect(page.getByText("no target date")).toBeVisible();
|
||||
await page.getByLabel("Target date").fill("2099-12-31");
|
||||
await expect(page.getByText("on track", { exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel("Target date")).toHaveValue("2099-12-31");
|
||||
|
||||
// From a task, its goal, run and back are one click each.
|
||||
await page.getByRole("button", { name: "Work" }).click();
|
||||
// The CEO's reply to the founder is a chat vehicle, not work: it stays off the board.
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
|
||||
describe("a goal's target date", () => {
|
||||
test("is set at creation or later, cleared with null, and refused when it is not a date", () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-goal-due-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
let orgId = "";
|
||||
let goalId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
goalId = organization.createGoal({ orgId, title: "Launch", description: "d", ownerAgentId: null, dueAt: "2026-12-31T23:59:59.000Z" });
|
||||
});
|
||||
const dueAt = () => (connection.db.prepare("select due_at from goals where id = ?").get(goalId) as { due_at: string | null }).due_at;
|
||||
expect(dueAt()).toBe("2026-12-31T23:59:59.000Z");
|
||||
organization.setGoalDueDate({ orgId, goalId, dueAt: null });
|
||||
expect(dueAt()).toBeNull();
|
||||
expect(() => organization.setGoalDueDate({ orgId, goalId, dueAt: "soon" })).toThrow('"soon" is not a date.');
|
||||
expect(() => organization.setGoalDueDate({ orgId, goalId, dueAt: "2026-02-30T23:59:59.000Z" })).toThrow('"2026-02-30T23:59:59.000Z" is not a date.');
|
||||
organization.setGoalDueDate({ orgId, goalId, dueAt: "2026-02-28T23:59:59Z" });
|
||||
expect(dueAt()).toBe("2026-02-28T23:59:59.000Z");
|
||||
expect(() => organization.setGoalDueDate({ orgId, goalId: "gol_missing", dueAt: null })).toThrow("Goal was not found.");
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { goalProgress } from "../../src/renderer/src/pages/home/goal-progress";
|
||||
import type { GoalRecord, TaskRecord } from "../../src/shared/domain";
|
||||
|
||||
const goal = (dueAt: string | null) => ({ id: "gol_1", createdAt: "2026-01-01T00:00:00.000Z", dueAt }) as GoalRecord;
|
||||
const tasks = (done: number, total: number) => Array.from({ length: total }, (_, index) => ({ goalId: "gol_1", status: index < done ? "done" : "in_progress" })) as TaskRecord[];
|
||||
|
||||
describe("goal progress", () => {
|
||||
test("without a target date it reports completion only, never a pace", () => {
|
||||
expect(goalProgress(goal(null), tasks(1, 4), new Date("2026-01-10T00:00:00.000Z"))).toEqual({ percent: 25, pace: null });
|
||||
});
|
||||
|
||||
test("with a target date the pace compares completion with the time elapsed toward it", () => {
|
||||
const due = goal("2026-01-11T00:00:00.000Z");
|
||||
const day = (n: number) => new Date(`2026-01-${String(n).padStart(2, "0")}T00:00:00.000Z`);
|
||||
expect(goalProgress(due, tasks(1, 4), day(6))).toEqual({ percent: 25, pace: "behind" });
|
||||
expect(goalProgress(due, tasks(2, 4), day(6))).toEqual({ percent: 50, pace: "on_track" });
|
||||
expect(goalProgress(due, tasks(3, 4), day(6))).toEqual({ percent: 75, pace: "ahead" });
|
||||
expect(goalProgress(due, tasks(3, 4), day(12))).toEqual({ percent: 75, pace: "overdue" });
|
||||
expect(goalProgress(due, tasks(4, 4), day(12))).toEqual({ percent: 100, pace: "on_track" });
|
||||
// Counted, not rounded: one unfinished task of many is still overdue.
|
||||
expect(goalProgress(due, tasks(200, 201), day(12))).toEqual({ percent: 100, pace: "overdue" });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user