Files
buzz/desktop/src/features/workstream-board/lib/discoverWorkstreamChannels.test.mjs
Logan Johnson 00acaf4948 feat(desktop): add off-by-default Workstream Board (bullet 1)
Adds the workstreamBoard preview flag, a /workstreams route, and a gated
sidebar destination that surface live loganj-ws-* channel canvases as
minimal cards (channel name, synopsis, orchestrator, assignees). Card
data comes from a single versioned buzz-workstream-card fenced JSON
block per canvas; parse failures (missing block, invalid JSON,
duplicate blocks, unknown version, invalid/missing fields) degrade
only that card to channel metadata plus an inline unavailable state.

Discovery reuses useChannelsQuery with a plain name-prefix filter and
no creator/membership filter; per-card data reuses useCanvasQuery.
Split AppSidebarProps out into the existing AppSidebar.types.ts
companion file to keep AppSidebar.tsx under the repo's 1000-line
file-size ratchet after adding the new sidebar wiring.

Scope is limited to bullet 1 of the Workstream Board spike: no
liveness, PR status, waiting-on resolution, or sorting.

Signed-off-by: loganj <loganj@squareup.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-20 14:57:03 +00:00

118 lines
3.1 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
filterWorkstreamChannels,
WORKSTREAM_CHANNEL_PREFIX,
} from "./discoverWorkstreamChannels.ts";
function buildChannel(overrides) {
return {
id: overrides.id ?? "channel-id",
name: overrides.name,
channelType: "stream",
visibility: "open",
description: "",
topic: null,
purpose: null,
memberCount: overrides.memberPubkeys?.length ?? 0,
memberPubkeys: overrides.memberPubkeys ?? [],
lastMessageAt: null,
archivedAt: null,
participants: [],
participantPubkeys: [],
isMember: overrides.isMember ?? false,
ttlSeconds: null,
ttlDeadline: null,
};
}
test("prefix constant matches the contract-specified prefix", () => {
assert.equal(WORKSTREAM_CHANNEL_PREFIX, "loganj-ws-");
});
test("includes channels whose name starts exactly with the prefix", () => {
const channels = [
buildChannel({ id: "1", name: "loganj-ws-canvas-cards" }),
buildChannel({ id: "2", name: "general" }),
];
const result = filterWorkstreamChannels(channels);
assert.deepEqual(
result.map((c) => c.id),
["1"],
);
});
test("excludes channels that merely contain the prefix mid-name", () => {
const channels = [
buildChannel({ id: "1", name: "not-loganj-ws-canvas-cards" }),
buildChannel({ id: "2", name: "loganj-ws-canvas-cards" }),
];
const result = filterWorkstreamChannels(channels);
assert.deepEqual(
result.map((c) => c.id),
["2"],
);
});
test("excludes a near-miss name missing the trailing hyphen", () => {
const channels = [
buildChannel({ id: "1", name: "loganj-ws" }),
buildChannel({ id: "2", name: "loganj-ws-" }),
];
const result = filterWorkstreamChannels(channels);
assert.deepEqual(
result.map((c) => c.id),
["2"],
);
});
test("applies no creator/membership filter — every matching name is included regardless of who created or joined it", () => {
const channels = [
// Different member sets stand in for "different creators" — the Channel
// type carries no creator field on the list endpoint, so membership
// overlap is the only axis available to prove no ownership filtering.
buildChannel({
id: "mine",
name: "loganj-ws-mine",
isMember: true,
memberPubkeys: ["aa"],
}),
buildChannel({
id: "someone-elses",
name: "loganj-ws-someone-elses",
isMember: false,
memberPubkeys: ["bb", "cc"],
}),
buildChannel({
id: "no-members",
name: "loganj-ws-empty",
isMember: false,
memberPubkeys: [],
}),
];
const result = filterWorkstreamChannels(channels);
assert.deepEqual(result.map((c) => c.id).sort(), [
"mine",
"no-members",
"someone-elses",
]);
});
test("returns an empty array when nothing matches", () => {
const channels = [
buildChannel({ id: "1", name: "general" }),
buildChannel({ id: "2", name: "random" }),
];
assert.deepEqual(filterWorkstreamChannels(channels), []);
});
test("returns an empty array for an empty channel list", () => {
assert.deepEqual(filterWorkstreamChannels([]), []);
});