main
mjs 83 lines 5.73 KB
Raw
1 import assert from "node:assert/strict";
2 import { readFileSync } from "node:fs";
3 import test from "node:test";
4 import ts from "typescript";
5
6 const workspace = readFileSync(new URL("../app/components/investment-workspace.tsx", import.meta.url), "utf8");
7 const apiSource = readFileSync(new URL("../app/lib/portfolio-api.ts", import.meta.url), "utf8");
8 const helperSource = readFileSync(new URL("../app/lib/market-intelligence.ts", import.meta.url), "utf8");
9 const styles = readFileSync(new URL("../app/styles.css", import.meta.url), "utf8");
10 const helperJavaScript = ts.transpileModule(helperSource, {
11 compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 }
12 }).outputText;
13 const helpers = await import(
14 `data:text/javascript;base64,${Buffer.from(helperJavaScript).toString("base64")}`
15 );
16
17 test("positive, negative, zero, and unavailable returns retain explicit accessible direction", () => {
18 assert.equal(helpers.performanceRowTone(4.25), "positive");
19 assert.equal(helpers.performanceRowTone(-6.8), "negative");
20 assert.equal(helpers.performanceRowTone(0), "neutral");
21 assert.equal(helpers.performanceRowTone(null), "neutral");
22 assert.equal(helpers.performanceRowTone(Number.NaN), "neutral");
23 assert.equal(helpers.formatSignedPerformancePct(4.25), "+4.25%");
24 assert.equal(helpers.formatSignedPerformancePct(-6.8), "-6.80%");
25 assert.equal(helpers.formatSignedPerformancePct(0), "0.00%");
26 assert.equal(helpers.formatSignedPerformancePct(undefined), "--");
27 });
28
29 test("Sector Performance commits independently without an automatic research acquisition effect", () => {
30 const performanceCall = workspace.indexOf("portfolioApi.getSectorPerformance(");
31 const performanceEffectStart = workspace.lastIndexOf(" useEffect(() => {", performanceCall);
32 const performanceEffectEnd = workspace.indexOf("\n }, [", performanceCall);
33 const performanceEffect = workspace.slice(performanceEffectStart, performanceEffectEnd);
34 assert.match(performanceEffect, /\.then\(\(value\) => \{ if \(!cancelled\) setSectorPerformance\(value\); \}\)/);
35 assert.doesNotMatch(workspace, /researchApi\.prefetchVisible\(/);
36 assert.doesNotMatch(performanceEffect, /getReadiness|ensureReadiness|researchApi\.refresh/);
37 });
38
39 test("the frontend uses provider-neutral readiness and targeted ensure routes", () => {
40 assert.match(apiSource, /getReadiness: \(globalInstrumentId: string\) =>[\s\S]*?`\/api\/v1\/research\/readiness\/\$\{globalInstrumentId\}`/);
41 assert.match(apiSource, /ensureReadiness: \(globalInstrumentId: string, requirements\?: string\[\]\) =>[\s\S]*?`\/api\/v1\/research\/readiness\/\$\{globalInstrumentId\}\/ensure`[\s\S]*?JSON\.stringify\(requirements\?\.length \? \{ requirements \} : \{\}\)/);
42 const readinessStart = workspace.indexOf("async function openResearchReadiness");
43 const readinessEnd = workspace.indexOf("function selectResearchContext", readinessStart);
44 const readinessPath = `${helperSource}\n${workspace.slice(readinessStart, readinessEnd)}`;
45 assert.doesNotMatch(readinessPath, /NSE|SEC_EDGAR|EODHD|Yahoo|country\s*===|region\s*===/);
46 });
47
48 test("clicking a visible stock opens read-only readiness without creating a holding or watchlist membership", () => {
49 assert.match(workspace, /onClick=\{\(\) => onOpenResearch\(stock\)\}/);
50 assert.match(workspace, /onOpenResearch=\{\(selection\) => \{ void openMarketIntelligenceResearch\(selection\); \}\}/);
51 const clickStart = workspace.indexOf("function openMarketIntelligenceResearch");
52 const clickEnd = workspace.indexOf(" async function openRegionalWatchlist", clickStart);
53 const click = workspace.slice(clickStart, clickEnd);
54 assert.match(click, /setSelectedResearchInstrumentId\(stock\.globalInstrumentId\)/);
55 assert.match(click, /openResearchReadiness\(stock\.globalInstrumentId, stock\.companyName\)/);
56 assert.doesNotMatch(click, /ensureDefaultWatchlist|addWatchlistInstrument|prefetchVisible|ensureReadiness|createPortfolio|createPosition|addPosition|updateHolding/);
57 assert.match(workspace, /held: false,[\s\S]*?quantity: 0/);
58 assert.match(workspace, /Qty 0 · Not held/);
59 const rowStart = workspace.indexOf("function MarketPerformanceRow");
60 const rowEnd = workspace.indexOf("function PortfolioCreatePanel", rowStart);
61 const row = workspace.slice(rowStart, rowEnd);
62 assert.doesNotMatch(row, /averageCost|average buy|invested|profitLoss|createPortfolio|createHolding/);
63 });
64
65 test("legacy visible-stock prefetch state and polling are absent", () => {
66 assert.doesNotMatch(apiSource, /prefetchVisible|getPrefetchState|\/api\/v1\/research\/prefetch/);
67 assert.doesNotMatch(workspace, /MarketIntelligenceResearchState|marketIntelligenceResearchStates|getPrefetchState|selectedMarketIntelligenceResearchState/);
68 assert.doesNotMatch(helperSource, /visibleSectorPerformanceInstrumentIds|researchStateLabel/);
69 });
70
71 test("the whole row has positive, negative, and neutral styling with usable hover, selection, and focus", () => {
72 assert.match(workspace, /className=\{`market-performance-row market-performance-row-\$\{tone\}`\}/);
73 assert.match(workspace, /data-performance-direction=\{tone\}/);
74 assert.match(workspace, /aria-label=\{`\$\{stock\.companyName\}, \$\{signedPerformance\}, \$\{performanceDirectionLabel\(stock\)\}`\}/);
75 for (const tone of ["positive", "negative", "neutral"]) {
76 assert.match(styles, new RegExp(`\\.market-performance-row-${tone}\\b`));
77 }
78 assert.match(styles, /\.market-performance-row:hover/);
79 assert.match(styles, /\.market-performance-row\[aria-current="true"\]/);
80 assert.match(styles, /\.market-performance-row:focus-visible/);
81 assert.match(styles, /\.market-performance-row-positive \.market-performance-return \{ color: var\(--positive\); \}/);
82 assert.match(styles, /\.market-performance-row-negative \.market-performance-return \{ color: var\(--negative\); \}/);
83 });