"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 webRoot = path.join(root, "src", "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web"); const app = fs.readFileSync(path.join(webRoot, "app.js"), "utf8"); const styles = fs.readFileSync(path.join(webRoot, "styles.css"), "utf8"); function section(startToken, endToken) { const start = app.indexOf(startToken); const end = app.indexOf(endToken, start + startToken.length); assert.ok(start >= 0, `missing start token: ${startToken}`); assert.ok(end > start, `missing end token: ${endToken}`); return app.slice(start, end); } test("UC4 renders original headings and action order in the Web vertical workspace", () => { const render = section("function renderTheme(state)", "function renderExpert(state)"); assert.match(render, /resultsTitle\.textContent = "테마명"/u); assert.match(render, /previewTitle\.textContent = "종목명"/u); assert.match(render, /search\.append\(input, searchButton, addTheme, editTheme, deleteTheme\)/u); assert.match(render, /workspace\.append\(search, resultsPanel, sortControls, actions, previewPanel\)/u); assert.match(render, /\["six-row-current", 0\][\s\S]*\["twelve-row-current", 1\][\s\S]*\["five-row-current", 2\][\s\S]*\["five-row-expected", 3\]/u); assert.match(render, /\["six-row-current", "6종목 현재가"\][\s\S]*\["twelve-row-current", "12종목 현재가"\][\s\S]*\["five-row-current", "현재가"\][\s\S]*\["five-row-expected", "예상체결가"\]/u); assert.match(styles, /\.theme-workspace\s*\{[^}]*grid-template-rows:/u); assert.doesNotMatch(styles, /\.theme-body\s*\{/u); assert.doesNotMatch(app, /theme-nxt-session/u); }); test("UC4 defers a single click for double-click activation and preserves the result DOM", () => { const doubleClick = section( "categoryTree.addEventListener(\"dblclick\"", "categoryTree.addEventListener(\"click\""); const click = section( "categoryTree.addEventListener(\"click\"", "categoryTree.addEventListener(\"change\""); const themeClickStart = click.indexOf( "const themeResult = event.target.closest(\"button[data-theme-result-id]\");"); const themeClickEnd = click.indexOf( "if (event.target.closest(\"button[data-theme-search]\"))", themeClickStart); assert.ok(themeClickStart >= 0 && themeClickEnd > themeClickStart); const themeClick = click.slice(themeClickStart, themeClickEnd); const render = section("function renderTheme(state)", "function renderExpert(state)"); const updater = section("function updateThemeWorkspace(workspace, theme)", "function renderTheme(state)"); assert.match(themeClick, /clearDelayedClick\(themeResultClickTimer\);[\s\S]*const resultId = themeResult\.dataset\.themeResultId;[\s\S]*themeResultClickTimer = window\.setTimeout\(function \(\) \{[\s\S]*themeResultClickTimer = null;[\s\S]*send\("select-theme", \{ resultId: resultId \}\);[\s\S]*\}, 250\);/u); assert.match(doubleClick, /clearDelayedClick\(themeResultClickTimer\);[\s\S]*themeResultClickTimer = null;[\s\S]*send\("activate-theme-result", \{/u); assert.match(render, /existingWorkspace && structureKey === themeRenderStructureKey[\s\S]*updateThemeWorkspace\(existingWorkspace, theme\);[\s\S]*return;/u); assert.doesNotMatch(updater, /categoryTree\.replaceChildren|results\.replaceChildren/u); assert.match(updater, /document\.activeElement !== input/u); }); test("UC4 delegates the NXT expected-price warning to native instead of disabling the button", () => { const clickability = section( "function themeActionIsClickable(action)", "function updateThemeWorkspace(workspace, theme)"); const updater = section("function updateThemeWorkspace(workspace, theme)", "function renderTheme(state)"); assert.match(clickability, /action\.actionId === "five-row-expected"[\s\S]*action\.unavailableReason === "expected-theme-is-krx-only"/u); assert.match(updater, /button\.disabled = !themeActionIsClickable\(action\)/u); assert.match(updater, /"NXT는 데이터가 없습니다\."/u); });