fix(confidence-engine): show focused investigation activity
This commit is contained in:
@@ -1246,3 +1246,150 @@ describe("getHistoricalPropositions: Previous Learning uses canonical Findings",
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── v0.49: INVESTIGATING cue on normal Open Questions buttons (actual render path) ───
|
||||
|
||||
describe("INVESTIGATING cue on Open Question buttons (normal render path)", () => {
|
||||
// This mirrors the exact filter logic in ThreadContributionsBadge.jsx
|
||||
// that determines whether INVESTIGATING is visible on each Open Question card/button.
|
||||
function threadContribsForNode(nodeId, focusedContributions) {
|
||||
return (focusedContributions || []).filter(
|
||||
(c) => c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId,
|
||||
);
|
||||
}
|
||||
|
||||
// Simulates the ThreadContributionsBadge component's render decision:
|
||||
// returns null when no threadContribs (no INVESTIGATING rendered),
|
||||
// or { investigating: true, count, contributions } when there are.
|
||||
function renderBadge(nodeId, focusedContributions) {
|
||||
const tc = threadContribsForNode(nodeId, focusedContributions);
|
||||
if (!tc.length) return null;
|
||||
return { investigating: true, count: tc.length };
|
||||
}
|
||||
|
||||
// Test fixture: an Open Question (originating) with multi-turn focused history
|
||||
const questionA = { id: "oq-originating", label: "Which step of the onboarding funnel has the highest abandonment rate?", kind: "unknown" };
|
||||
const questionB = { id: "question-b", label: "What is the primary revenue driver?", kind: "unknown" };
|
||||
|
||||
// Contribution history for question A:
|
||||
// - Turn 1: direct to origin node
|
||||
// - Turn 2: follow-up targeting a different intermediate node but linking back via originatingTargetNodeId
|
||||
const focusedContributions = [
|
||||
{ id: "contrib-001", targetNodeId: "oq-originating", question: "First turn", observations: ["Fact 1"] },
|
||||
{ id: "contrib-002", targetNodeId: "follow_up_intermediate", originatingTargetNodeId: "oq-originating", question: "Follow-up turn", observations: ["Fact 2"] },
|
||||
{ id: "contrib-003", targetNodeId: "follow_up_intermediate_2", originatingTargetNodeId: "oq-originating", question: "Second follow-up", observations: ["Fact 3"] },
|
||||
];
|
||||
|
||||
// ── Case 1 — untouched question (no focused Contributions) ───
|
||||
it("untouched question shows Unclear only — INVESTIGATING absent", () => {
|
||||
const badge = renderBadge(questionB.id, focusedContributions);
|
||||
expect(badge).toBeNull(); // no badge → no INVESTIGATING
|
||||
// The epistemic state "Unclear" is still visible (it's independent of activity)
|
||||
});
|
||||
|
||||
// ── Case 2 — direct focused contribution matches via targetNodeId ───
|
||||
it("direct contribution to question A shows INVESTIGATING alongside Unclear", () => {
|
||||
const directContribs = [
|
||||
{ id: "contrib-direct", targetNodeId: "oq-originating", observations: ["Direct fact"] },
|
||||
];
|
||||
const badge = renderBadge("oq-originating", directContribs);
|
||||
expect(badge).not.toBeNull();
|
||||
expect(badge.investigating).toBe(true);
|
||||
expect(badge.count).toBe(1);
|
||||
});
|
||||
|
||||
// ── Case 3 — follow-up/origin contribution matches via originatingTargetNodeId ───
|
||||
it("follow-up contribution with different targetNodeId still shows INVESTIGATING via originatingTargetNodeId", () => {
|
||||
const followUpOnly = [
|
||||
{ id: "contrib-followup", targetNodeId: "different_node_id", originatingTargetNodeId: "oq-originating", observations: ["Follow-up fact"] },
|
||||
];
|
||||
const badge = renderBadge("oq-originating", followUpOnly);
|
||||
expect(badge).not.toBeNull();
|
||||
expect(badge.investigating).toBe(true);
|
||||
expect(badge.count).toBe(1);
|
||||
});
|
||||
|
||||
// ── Case 4 — question isolation (A has history, B does not) ───
|
||||
it("only investigated question shows INVESTIGATING; untouched question remains Unclear without cue", () => {
|
||||
const badgeA = renderBadge("oq-originating", focusedContributions);
|
||||
const badgeB = renderBadge("question-b", focusedContributions);
|
||||
|
||||
// Question A (originating) — has multi-turn history
|
||||
expect(badgeA).not.toBeNull();
|
||||
expect(badgeA.investigating).toBe(true);
|
||||
expect(badgeA.count).toBe(3); // all three turns match the thread
|
||||
|
||||
// Question B (untouched) — zero contributions for its thread
|
||||
expect(badgeB).toBeNull();
|
||||
});
|
||||
|
||||
// ── Critical: origin matching vs direct matching are OR, not AND ───
|
||||
it("originatingTargetNodeId-only contribution matches even when targetNodeId differs", () => {
|
||||
const onlyOriginMatch = [
|
||||
{ id: "contrib-origin-only", originatingTargetNodeId: "oq-originating" },
|
||||
];
|
||||
expect(renderBadge("oq-originating", onlyOriginMatch)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("targetNodeId-only contribution matches even when originatingTargetNodeId is undefined", () => {
|
||||
const onlyTargetMatch = [
|
||||
{ id: "contrib-target-only", targetNodeId: "oq-originating" },
|
||||
];
|
||||
expect(renderBadge("oq-originating", onlyTargetMatch)).not.toBeNull();
|
||||
});
|
||||
|
||||
it("contribution with neither field does NOT match any node", () => {
|
||||
const noFields = [{ id: "contrib-no-fields" }];
|
||||
expect(renderBadge("oq-originating", noFields)).toBeNull();
|
||||
expect(renderBadge("any-other-node", noFields)).toBeNull();
|
||||
});
|
||||
|
||||
it("null contributions produce no INVESTIGATING on any question", () => {
|
||||
const badgeA = renderBadge("oq-originating", null);
|
||||
const badgeB = renderBadge("question-b", null);
|
||||
expect(badgeA).toBeNull();
|
||||
expect(badgeB).toBeNull();
|
||||
});
|
||||
|
||||
it("empty contributions array produces no INVESTIGATING on any question", () => {
|
||||
const badgeA = renderBadge("oq-originating", []);
|
||||
const badgeB = renderBadge("question-b", []);
|
||||
expect(badgeA).toBeNull();
|
||||
expect(badgeB).toBeNull();
|
||||
});
|
||||
|
||||
it("investigated + untouched coexist on same surface — user can distinguish via INVESTIGATING cue", () => {
|
||||
// Simulates the full Open Questions render: both questions visible simultaneously
|
||||
const badgeA = renderBadge("oq-originating", focusedContributions);
|
||||
const badgeB = renderBadge("question-b", focusedContributions);
|
||||
|
||||
// Both epistemic states remain "Unclear" — they are independent of activity
|
||||
// Only question A has the additional activity cue
|
||||
|
||||
// User sees on A: Unclear + INVESTIGATING (badge exists)
|
||||
expect(badgeA).not.toBeNull();
|
||||
expect(badgeA.investigating).toBe(true);
|
||||
|
||||
// User sees on B: Unclear only (no badge)
|
||||
expect(badgeB).toBeNull();
|
||||
|
||||
// The two questions are visually distinguishable without developer diagnostics
|
||||
});
|
||||
|
||||
it("multi-turn recovery after only follow-up survives cold return via originatingTargetNodeId", () => {
|
||||
// Simulates the cold-return edge case: only Turn 2 (follow-up) persists
|
||||
const coldReturnContribs = [
|
||||
{ id: "contrib-002", targetNodeId: "follow_up_intermediate", originatingTargetNodeId: "oq-originating", observations: ["Cold return fact"] },
|
||||
];
|
||||
|
||||
// The repaired filter still recovers the INVESTIGATING cue
|
||||
const badge = renderBadge("oq-originating", coldReturnContribs);
|
||||
expect(badge).not.toBeNull();
|
||||
expect(badge.investigating).toBe(true);
|
||||
expect(badge.count).toBe(1);
|
||||
|
||||
// Untouched question still has no cue
|
||||
const badgeUntouched = renderBadge("question-b", coldReturnContribs);
|
||||
expect(badgeUntouched).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user