feat: add config for kanban columns
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { AppErrorVariant } from "@/error";
|
||||
import { TaskStatus } from "@/model/taskStatus";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseProjectConfig } from "./parseProjectConfig";
|
||||
|
||||
@@ -43,4 +44,32 @@ describe("parseProjectConfig", () => {
|
||||
if (result.isOk()) return;
|
||||
expect(result.error.variant).toBe(AppErrorVariant.INVALID_PROJECT_CONFIG);
|
||||
});
|
||||
|
||||
it("parses shownHiddenColumns", () => {
|
||||
const result = parseProjectConfig(
|
||||
"tags: []\nshownHiddenColumns:\n - backlog\n - cancelled\n",
|
||||
);
|
||||
expect(result.isOk()).toBe(true);
|
||||
if (result.isErr()) return;
|
||||
expect(result.value.shownHiddenColumns).toEqual([
|
||||
TaskStatus.BACKLOG,
|
||||
TaskStatus.CANCELLED,
|
||||
]);
|
||||
});
|
||||
|
||||
it("shownHiddenColumns defaults to empty array when missing", () => {
|
||||
const result = parseProjectConfig("tags: []\n");
|
||||
expect(result.isOk()).toBe(true);
|
||||
if (result.isErr()) return;
|
||||
expect(result.value.shownHiddenColumns).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects shownHiddenColumns with invalid status values", () => {
|
||||
const result = parseProjectConfig(
|
||||
"tags: []\nshownHiddenColumns:\n - backlog\n - not_a_status\n",
|
||||
);
|
||||
expect(result.isErr()).toBe(true);
|
||||
if (result.isOk()) return;
|
||||
expect(result.error.variant).toBe(AppErrorVariant.INVALID_PROJECT_CONFIG);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,11 +6,31 @@ import {
|
||||
normalizeTag,
|
||||
type ProjectConfig,
|
||||
} from "@/model/projectConfig";
|
||||
import { isTaskStatus } from "@/model/taskStatus";
|
||||
|
||||
function parseShownHiddenColumns(
|
||||
value: unknown,
|
||||
): Result<string[], AppError> {
|
||||
if (value === undefined) return ok([]);
|
||||
if (!Array.isArray(value)) {
|
||||
return err(
|
||||
appError(AppErrorVariant.INVALID_PROJECT_CONFIG, {
|
||||
detail: "shownHiddenColumns must be an array of strings",
|
||||
}),
|
||||
);
|
||||
}
|
||||
for (const item of value) {
|
||||
if (typeof item !== "string" || !isTaskStatus(item)) {
|
||||
return err(
|
||||
appError(AppErrorVariant.INVALID_PROJECT_CONFIG, {
|
||||
detail: `invalid status in shownHiddenColumns: ${item}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
return ok(value as string[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse task-folder config.yml content.
|
||||
* Accepts missing/empty tags → [].
|
||||
*/
|
||||
export function parseProjectConfig(
|
||||
content: string,
|
||||
): Result<ProjectConfig, AppError> {
|
||||
@@ -39,33 +59,44 @@ export function parseProjectConfig(
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>;
|
||||
|
||||
let tags: string[];
|
||||
if (record.tags === undefined) {
|
||||
return ok({ tags: [] });
|
||||
}
|
||||
|
||||
if (!Array.isArray(record.tags)) {
|
||||
return err(
|
||||
appError(AppErrorVariant.INVALID_PROJECT_CONFIG, {
|
||||
detail: "tags must be an array of strings",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const tags: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of record.tags) {
|
||||
if (typeof item !== "string") {
|
||||
tags = [];
|
||||
} else {
|
||||
if (!Array.isArray(record.tags)) {
|
||||
return err(
|
||||
appError(AppErrorVariant.INVALID_PROJECT_CONFIG, {
|
||||
detail: "tags must be an array of strings",
|
||||
}),
|
||||
);
|
||||
}
|
||||
const tag = normalizeTag(item);
|
||||
if (!tag || seen.has(tag)) continue;
|
||||
seen.add(tag);
|
||||
tags.push(tag);
|
||||
|
||||
tags = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of record.tags) {
|
||||
if (typeof item !== "string") {
|
||||
return err(
|
||||
appError(AppErrorVariant.INVALID_PROJECT_CONFIG, {
|
||||
detail: "tags must be an array of strings",
|
||||
}),
|
||||
);
|
||||
}
|
||||
const tag = normalizeTag(item);
|
||||
if (!tag || seen.has(tag)) continue;
|
||||
seen.add(tag);
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
return ok({ tags });
|
||||
const shownHiddenColumnsResult = parseShownHiddenColumns(
|
||||
record.shownHiddenColumns,
|
||||
);
|
||||
if (shownHiddenColumnsResult.isErr())
|
||||
return err(shownHiddenColumnsResult.error);
|
||||
|
||||
return ok({
|
||||
tags,
|
||||
shownHiddenColumns: shownHiddenColumnsResult.value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { serializeProjectConfig } from "./serializeProjectConfig";
|
||||
|
||||
describe("serializeProjectConfig", () => {
|
||||
it("round-trips tags", () => {
|
||||
const yaml = serializeProjectConfig({ tags: ["bug", "docs"] });
|
||||
const yaml = serializeProjectConfig({
|
||||
tags: ["bug", "docs"],
|
||||
shownHiddenColumns: [],
|
||||
});
|
||||
expect(yaml).toContain("bug");
|
||||
expect(yaml).toContain("docs");
|
||||
const parsed = parseProjectConfig(yaml);
|
||||
@@ -14,10 +17,33 @@ describe("serializeProjectConfig", () => {
|
||||
});
|
||||
|
||||
it("serializes empty tags", () => {
|
||||
const yaml = serializeProjectConfig({ tags: [] });
|
||||
const yaml = serializeProjectConfig({ tags: [], shownHiddenColumns: [] });
|
||||
const parsed = parseProjectConfig(yaml);
|
||||
expect(parsed.isOk()).toBe(true);
|
||||
if (parsed.isErr()) return;
|
||||
expect(parsed.value.tags).toEqual([]);
|
||||
});
|
||||
|
||||
it("round-trips tags with shownHiddenColumns", () => {
|
||||
const yaml = serializeProjectConfig({
|
||||
tags: ["bug"],
|
||||
shownHiddenColumns: ["backlog", "cancelled"],
|
||||
});
|
||||
const parsed = parseProjectConfig(yaml);
|
||||
expect(parsed.isOk()).toBe(true);
|
||||
if (parsed.isErr()) return;
|
||||
expect(parsed.value.tags).toEqual(["bug"]);
|
||||
expect(parsed.value.shownHiddenColumns).toEqual(["backlog", "cancelled"]);
|
||||
});
|
||||
|
||||
it("serializes empty shownHiddenColumns", () => {
|
||||
const yaml = serializeProjectConfig({
|
||||
tags: [],
|
||||
shownHiddenColumns: [],
|
||||
});
|
||||
const parsed = parseProjectConfig(yaml);
|
||||
expect(parsed.isOk()).toBe(true);
|
||||
if (parsed.isErr()) return;
|
||||
expect(parsed.value.shownHiddenColumns).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,14 +2,15 @@ import * as yaml from "js-yaml";
|
||||
import type { ProjectConfig } from "@/model/projectConfig";
|
||||
|
||||
export function serializeProjectConfig(config: ProjectConfig): string {
|
||||
const data: Record<string, unknown> = { tags: config.tags };
|
||||
if (config.shownHiddenColumns.length > 0) {
|
||||
data.shownHiddenColumns = config.shownHiddenColumns;
|
||||
}
|
||||
return yaml
|
||||
.dump(
|
||||
{ tags: config.tags },
|
||||
{
|
||||
lineWidth: -1,
|
||||
forceQuotes: false,
|
||||
},
|
||||
)
|
||||
.dump(data, {
|
||||
lineWidth: -1,
|
||||
forceQuotes: false,
|
||||
})
|
||||
.trimEnd()
|
||||
.concat("\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user