import { describe, it } from "node:test"; import * as assert from "node:assert/strict"; import { STARTER_WATCHLIST, STARTER_PACK_FOCUSED, STARTER_PACK_STANDARD, ONBOARDING_DISCLAIMER, defaultDrawdownTolerancePct, defaultRiskTolerance, starterPackFor, resolveWorkspaceDefaults, complexityFromExperience, defaultDensity, type StarterSymbol, } from "../starter.ts"; const FORBIDDEN = [ /buy\b/i, /sell\b/i, /you should\b/i, /add to your\b/i, /rotate into\b/i, /action needed\b/i, ]; function assertNoTradeVerbs(items: StarterSymbol[], label: string) { for (const item of items) { const hit = FORBIDDEN.find((rx) => rx.test(item.reason)); assert.equal(hit, undefined, `${label}: forbidden verb in reason for ${item.symbol}`); assert.ok(item.reason.length > 0, `${label}: empty reason for ${item.symbol}`); } } describe("onboarding/starter", () => { describe("STARTER_PACK_FOCUSED / STARTER_WATCHLIST", () => { it("focused pack has liquid exemplars (no thin microcaps as default)", () => { const symbols = STARTER_PACK_FOCUSED.map((s) => s.symbol); assert.ok(symbols.includes("VTI")); assert.ok(symbols.includes("AAPL")); assert.ok(!symbols.includes("IREN")); assert.ok(!symbols.includes("CIFR")); assert.equal(STARTER_WATCHLIST.length, STARTER_PACK_FOCUSED.length); }); it("every focused entry has tickerKind and reason without trade verbs", () => { assertNoTradeVerbs(STARTER_PACK_FOCUSED, "focused"); }); }); describe("starterPackFor", () => { it("standard includes focused symbols plus benchmarks", () => { const pack = starterPackFor("standard"); const symbols = pack.map((s) => s.symbol); assert.ok(symbols.includes("VTI")); assert.ok(symbols.includes("SPY")); assert.ok(symbols.includes("NVDA")); assertNoTradeVerbs(pack, "standard"); }); it("full adds optional higher-vol research examples", () => { const pack = starterPackFor("full"); const symbols = pack.map((s) => s.symbol); assert.ok(symbols.includes("SPY")); assert.ok(symbols.includes("IREN") || symbols.includes("BTC")); assertNoTradeVerbs(pack, "full"); }); }); describe("resolveWorkspaceDefaults", () => { it("maps never_invested → beginner + focused + focused pack", () => { const d = resolveWorkspaceDefaults({ experienceStage: "never_invested" }); assert.equal(d.complexity, "beginner"); assert.equal(d.density, "focused"); assert.equal(d.drawdownTolerancePct, -20); assert.deepEqual( d.starterPack.map((s) => s.symbol), STARTER_PACK_FOCUSED.map((s) => s.symbol), ); }); it("maps active_self_directed → advanced + full", () => { const d = resolveWorkspaceDefaults({ experienceStage: "active_self_directed" }); assert.equal(d.complexity, "advanced"); assert.equal(d.density, "full"); assert.equal(d.riskTolerance, "aggressive"); }); it("respects explicit density override", () => { const d = resolveWorkspaceDefaults({ experienceStage: "never_invested", density: "standard", goal: "protect", horizon: "short", }); assert.equal(d.density, "standard"); assert.equal(d.goal, "protect"); assert.equal(d.horizon, "short"); assert.ok(d.starterPack.length >= STARTER_PACK_STANDARD.length - 1); }); }); describe("complexityFromExperience / defaultDensity", () => { it("maps stages", () => { assert.equal(complexityFromExperience("never_invested"), "beginner"); assert.equal(complexityFromExperience("some_stocks"), "intermediate"); assert.equal(complexityFromExperience("active_self_directed"), "advanced"); assert.equal(defaultDensity("never_invested"), "focused"); assert.equal(defaultDensity("some_stocks"), "standard"); assert.equal(defaultDensity("active_self_directed"), "full"); }); }); describe("defaultDrawdownTolerancePct", () => { it("returns expected levels", () => { assert.equal(defaultDrawdownTolerancePct("beginner"), -20); assert.equal(defaultDrawdownTolerancePct("intermediate"), -30); assert.equal(defaultDrawdownTolerancePct("advanced"), -40); }); }); describe("defaultRiskTolerance", () => { it("returns expected levels", () => { assert.equal(defaultRiskTolerance("beginner"), "moderate"); assert.equal(defaultRiskTolerance("intermediate"), "moderate"); assert.equal(defaultRiskTolerance("advanced"), "aggressive"); }); }); describe("ONBOARDING_DISCLAIMER", () => { it("is short operational setup copy", () => { assert.ok(ONBOARDING_DISCLAIMER.length > 0); assert.ok(ONBOARDING_DISCLAIMER.toLowerCase().includes("watchlist")); assert.ok(!ONBOARDING_DISCLAIMER.toLowerCase().includes("educational")); }); }); });