| 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 | }); |