"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const root = path.resolve(__dirname, "../.."); const read = relativePath => fs.readFileSync(path.join(root, relativePath), "utf8"); const app = read("Web/app.js"); const index = read("Web/index.html"); const styles = read("Web/styles.css"); const native = fs.readdirSync(root) .filter(name => /^MainWindow(?:\..+)?\.cs$/u.test(name)) .map(name => read(name)) .join("\n"); function functionBody(source, name, nextName) { const start = source.indexOf(`function ${name}(`); assert.ok(start >= 0, `${name} must exist`); const end = source.indexOf(`function ${nextName}(`, start + 1); assert.ok(end > start, `${name} must precede ${nextName}`); return source.slice(start, end); } test("trading-halt results expose an accessible stock-name sort header", () => { assert.match(index, /class="trading-halt-results-header" role="group" aria-label="거래 정지 검색 결과 정렬"[\s\S]*id="tradingHaltNameSort"[\s\S]*aria-controls="tradingHaltResults"[\s\S]*data-sort-direction="none">종목명 ↕<\/button>/u); assert.match(styles, /\.trading-halt-results-header\s*\{/u); assert.match(styles, /\.trading-halt-results-header button:disabled\s*\{/u); }); test("render sorts only a copied display view and preserves market-code selection identity", () => { const identity = functionBody(app, "tradingHaltIdentity", "toggleTradingHaltNameSort"); const render = functionBody(app, "renderTradingHaltWorkflow", "requestTradingHaltSearch"); assert.match(identity, /tradingHaltWorkflow\.tradingHaltIdentity\(value\)/u); assert.doesNotMatch(identity, /displayName/u); assert.match(render, /sortTradingHaltsForDisplay\(\s*workflow\.results,\s*workflow\.sortDirection\)/u); assert.match(render, /tradingHaltIdentity\(item\) === selectedIdentity/u); assert.doesNotMatch(render, /workflow\.results\.sort\(/u); }); test("sort is blocked while pending, locked, or empty and otherwise toggles locally", () => { const toggle = functionBody(app, "toggleTradingHaltNameSort", "selectTradingHalt"); const render = functionBody(app, "renderTradingHaltWorkflow", "requestTradingHaltSearch"); assert.match(toggle, /isPlaylistSnapshotLocked\(\) \|\| workflow\.status !== "ready" \|\| !workflow\.results\.length/u); assert.match(toggle, /workflow\.sortDirection = tradingHaltWorkflow\.nextTradingHaltNameSort\(workflow\.sortDirection\)/u); assert.match(render, /const canSort = workflow\.status === "ready" && workflow\.results\.length > 0/u); assert.match(render, /tradingHaltNameSort\.disabled = locked \|\| !canSort/u); assert.doesNotMatch(toggle, /postNative|requestTradingHaltSearch|state\.playlist|renderPlaylist|updatePreview/u); }); test("each new search and matching result restores the server-default order", () => { const request = functionBody(app, "requestTradingHaltSearch", "handleTradingHaltResults"); const results = functionBody(app, "handleTradingHaltResults", "handleTradingHaltError"); assert.match(request, /workflow\.status = "loading";[\s\S]*workflow\.results = \[\];[\s\S]*workflow\.sortDirection = null;/u); assert.match(results, /clearTradingHaltTimeout\(\);\s*workflow\.sortDirection = null;[\s\S]*workflow\.results = \[\.\.\.response\.results\]/u); }); test("the display sort adds no native command surface or playlist mutation", () => { const toggle = functionBody(app, "toggleTradingHaltNameSort", "selectTradingHalt"); assert.doesNotMatch(toggle, /postNative|state\.playlist|renderPlaylist|updatePreview|prepare|take-in|take-out/iu); assert.doesNotMatch(native, /sort-trading-halt|trading-halt-sort/iu); assert.doesNotMatch(app, /postNative\("(?:sort-trading-halt|trading-halt-sort)"/iu); });