Files
fusion/packages/dashboard/app/components/__tests__/workflowStatusCounts.test.ts
gsxdsm 7eceec00cc FN-6763: add themed workflow status switcher
Add a reusable dashboard workflow picker that shows inline task status counts across board and list views.

- Replace native workflow selects with a themed accessible dropdown component.
- Share workflow status count calculation between board and list views.
- Add styling, i18n labels, tests, and dashboard docs for the switcher behavior.

Files changed:
 docs/dashboard-guide.md                            |   3 +-
 packages/dashboard/app/components/Board.tsx        |  28 +--
 packages/dashboard/app/components/Lane.css         |   2 +
 .../dashboard/app/components/LeftSidebarNav.tsx    |   2 +-
 packages/dashboard/app/components/ListView.css     |  17 --
 packages/dashboard/app/components/ListView.tsx     |  31 ++-
 .../dashboard/app/components/WorkflowSwitcher.css  | 186 ++++++++++++++
 .../dashboard/app/components/WorkflowSwitcher.tsx  | 270 +++++++++++++++++++++
 .../app/components/__tests__/Board.test.tsx        |  60 +++--
 .../app/components/__tests__/ListView.test.tsx     |  79 +++++-
 .../components/__tests__/WorkflowSwitcher.test.tsx |  92 +++++++
 .../__tests__/workflowStatusCounts.test.ts         | 122 ++++++++++
 .../app/components/workflowStatusCounts.ts         |  54 +++++
 packages/i18n/locales/en/app.json                  |   9 +
 packages/i18n/locales/es/app.json                  |   9 +
 packages/i18n/locales/fr/app.json                  |   9 +
 packages/i18n/locales/ko/app.json                  |   9 +
 packages/i18n/locales/zh-CN/app.json               |   9 +
 packages/i18n/locales/zh-TW/app.json               |   9 +
 19 files changed, 930 insertions(+), 70 deletions(-)

Fusion-Task-Id: FN-6763
Fusion-Task-Lineage: 3503ea85-841a-4cd3-9960-1b22ef2cd4ea
2026-06-20 02:08:36 -07:00

123 lines
4.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { Task } from "@fusion/core";
import type { BoardWorkflowsPayload } from "../../api";
import { computeWorkflowStatusCounts } from "../workflowStatusCounts";
const boardWorkflows: BoardWorkflowsPayload = {
flagEnabled: true,
defaultWorkflowId: "default",
taskWorkflowIds: {},
workflows: [
{
id: "default",
name: "Default",
columns: [
{ id: "todo", name: "Todo", flags: { intake: true } },
{ id: "ready", name: "Ready", flags: {} },
{ id: "active", name: "Active", flags: { countsTowardWip: true } },
{ id: "review", name: "Review", flags: { countsTowardWip: true, mergeBlocker: true } },
{ id: "done", name: "Done", flags: { complete: true } },
{ id: "archived", name: "Archived", flags: { archived: true } },
],
},
{
id: "design",
name: "Design",
columns: [
{ id: "design-todo", name: "Todo", flags: { intake: true } },
{ id: "design-active", name: "Active", flags: { countsTowardWip: true } },
{ id: "design-done", name: "Done", flags: { complete: true } },
{ id: "design-archived", name: "Archived", flags: { archived: true } },
],
},
{
id: "empty",
name: "Empty",
columns: [
{ id: "empty-todo", name: "Todo", flags: { intake: true } },
{ id: "empty-active", name: "Active", flags: { countsTowardWip: true } },
{ id: "empty-done", name: "Done", flags: { complete: true } },
],
},
],
};
function task(id: string, column: string): Task {
return {
id,
title: id,
description: id,
column,
dependencies: [],
steps: [],
currentStep: 0,
} as Task;
}
describe("computeWorkflowStatusCounts", () => {
it("returns an empty map when workflow metadata is unavailable", () => {
expect(computeWorkflowStatusCounts([task("FN-1", "todo")], null).size).toBe(0);
expect(computeWorkflowStatusCounts(undefined, undefined).size).toBe(0);
});
it("initializes every workflow with zero counts for empty and duplicate/populated states", () => {
const counts = computeWorkflowStatusCounts([], boardWorkflows);
expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 0 });
expect(counts.get("design")).toEqual({ todo: 0, inProgress: 0, done: 0 });
expect(counts.get("empty")).toEqual({ todo: 0, inProgress: 0, done: 0 });
});
it("classifies todo, in-progress, and done buckets from workflow column flags", () => {
const counts = computeWorkflowStatusCounts(
[
task("FN-todo", "todo"),
task("FN-ready", "ready"),
task("FN-active", "active"),
task("FN-review", "review"),
task("FN-done", "done"),
],
boardWorkflows,
);
expect(counts.get("default")).toEqual({ todo: 2, inProgress: 2, done: 1 });
});
it("falls back to the default workflow when a task has no workflow assignment", () => {
const counts = computeWorkflowStatusCounts([task("FN-unassigned", "done")], boardWorkflows);
expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 1 });
});
it("counts tasks independently for their assigned workflow", () => {
const counts = computeWorkflowStatusCounts(
[task("FN-design-todo", "design-todo"), task("FN-design-active", "design-active"), task("FN-design-done", "design-done")],
{
...boardWorkflows,
taskWorkflowIds: {
"FN-design-todo": "design",
"FN-design-active": "design",
"FN-design-done": "design",
},
},
);
expect(counts.get("design")).toEqual({ todo: 1, inProgress: 1, done: 1 });
expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 0 });
});
it("excludes archived-column tasks and ignores unknown workflows or columns", () => {
const counts = computeWorkflowStatusCounts(
[task("FN-archived", "archived"), task("FN-unknown-column", "missing"), task("FN-unknown-workflow", "todo")],
{
...boardWorkflows,
taskWorkflowIds: {
"FN-unknown-workflow": "missing-workflow",
},
},
);
expect(counts.get("default")).toEqual({ todo: 0, inProgress: 0, done: 0 });
});
});