feat: refine branded operator experience
Add the MBN-branded startup intro, semantic sidebar icons, and refined card-based operator visuals. Strengthen packaged UI and input smoke coverage for the updated experience.
This commit is contained in:
@@ -65,6 +65,7 @@ function pendingMenuFocusHarness() {
|
||||
return tabId === "theme" ? pendingButton : null;
|
||||
}
|
||||
function refreshCurrentMenu() {}
|
||||
function syncHeadingIcon() {}
|
||||
function marketLabel() { return "테마"; }
|
||||
${source}
|
||||
return { syncMarket, hasPendingKeyboardMarketFocus };
|
||||
@@ -262,6 +263,46 @@ test("package input physically resizes the splitter, verifies persistence, and r
|
||||
/ArrowRight: \{ key: "ArrowRight", code: "ArrowRight", virtualKey: 39 \}/);
|
||||
});
|
||||
|
||||
test("read-only package smoke seals the real visual-card screen and nested title selection", () => {
|
||||
const start = packageInputHarness.indexOf(
|
||||
"async function verifyVisualCardPresentation()");
|
||||
const end = packageInputHarness.indexOf(
|
||||
"async function playlistDropGeometry", start);
|
||||
assert.ok(start >= 0 && end > start,
|
||||
"visual-card package verification must be a bounded helper");
|
||||
const visual = packageInputHarness.slice(start, end);
|
||||
for (const contract of [
|
||||
/selectMainTab\(\s*"overseas"/,
|
||||
/button\[data-action-id="fixed-001"\]\.visual-card/,
|
||||
/getComputedStyle\(list\)/,
|
||||
/columnCount:\s*tracks\.length/,
|
||||
/cardHeight:\s*button\.getBoundingClientRect\(\)\.height/,
|
||||
/ariaLabel:\s*button\.getAttribute\("aria-label"\)/,
|
||||
/hasPreview:\s*Boolean\(preview\)/,
|
||||
/wordmarkSource:\s*wordmark\.getAttribute\("src"\)/,
|
||||
/wordmarkNaturalWidth:\s*wordmark\.naturalWidth/,
|
||||
/metrics\.wordmarkNaturalWidth !== 176/,
|
||||
/metrics\.brandProduct !== "V-STOCK"/,
|
||||
/metrics\.hasPreview \|\|\s*metrics\.hasState \|\| metrics\.hasKicker/,
|
||||
/select fixed graphic from its visual-card title/,
|
||||
/outboundMessageCount !== outboundBaseline/,
|
||||
/checkpoint\("visual-card-presentation"/
|
||||
]) assert.match(visual, contract);
|
||||
assert.match(packageInputHarness,
|
||||
/if \(readOnlyProfile\) \{\s*current = await verifyVisualCardPresentation\(\);/);
|
||||
});
|
||||
|
||||
test("UI-only package smoke verifies the local customer wordmark is loaded and visible", () => {
|
||||
assert.match(uiOnlySmokeHarness,
|
||||
/const brandWordmark = document\.querySelector\("img\.brand-wordmark"\)/);
|
||||
assert.match(uiOnlySmokeHarness,
|
||||
/source:\s*brandWordmark\?\.getAttribute\("src"\)/);
|
||||
assert.match(uiOnlySmokeHarness, /naturalWidth:\s*brandWordmark\?\.naturalWidth/);
|
||||
assert.match(uiOnlySmokeHarness, /shell\.brand\.naturalWidth !== 176/);
|
||||
assert.match(uiOnlySmokeHarness, /shell\.brand\.naturalHeight !== 43/);
|
||||
assert.match(uiOnlySmokeHarness, /shell\.brand\.product !== "V-STOCK"/);
|
||||
});
|
||||
|
||||
test("unified operator theme keeps clear semantic states and concise feedback", () => {
|
||||
assert.match(styles,
|
||||
/Unified operator theme: visual-only rules; geometry and native contracts stay intact/);
|
||||
@@ -327,6 +368,116 @@ test("the complete sidebar is one labelled and collapsible navigation region", (
|
||||
assert.match(markup, /id="workspace-nav-footer" class="workspace-nav-footer"/);
|
||||
});
|
||||
|
||||
test("sidebar icons form one semantic SVG family and the heading follows the selected menu", () => {
|
||||
const mappings = [
|
||||
["id=\"workspace-stock-tab\"", "stock-cut"],
|
||||
["data-tab-id=\"overseas\"", "overseas"],
|
||||
["data-tab-id=\"exchange\"", "exchange"],
|
||||
["data-tab-id=\"index\"", "index"],
|
||||
["data-tab-id=\"kospiIndustry\"", "kospi"],
|
||||
["data-tab-id=\"kosdaqIndustry\"", "kosdaq"],
|
||||
["data-tab-id=\"comparison\"", "comparison"],
|
||||
["data-tab-id=\"theme\"", "theme"],
|
||||
["data-tab-id=\"overseasStocks\"", "overseas-stocks"],
|
||||
["data-tab-id=\"expert\"", "expert"],
|
||||
["data-tab-id=\"tradingHalt\"", "trading-halt"],
|
||||
["id=\"workspace-settings-tab\"", "settings"]
|
||||
];
|
||||
for (const [anchor, icon] of mappings) {
|
||||
const start = markup.indexOf(anchor);
|
||||
const end = markup.indexOf("</button>", start);
|
||||
assert.ok(start >= 0 && end > start, `missing menu button: ${anchor}`);
|
||||
const button = markup.slice(start, end);
|
||||
assert.match(button, new RegExp(`data-icon="${icon}"`));
|
||||
assert.match(button, new RegExp(`href="#workspace-icon-${icon}"`));
|
||||
}
|
||||
|
||||
const symbolIds = Array.from(
|
||||
markup.matchAll(/<symbol id="workspace-icon-([^"]+)" viewBox="0 0 24 24">/g),
|
||||
match => match[1]);
|
||||
assert.deepEqual(symbolIds, [
|
||||
"menu",
|
||||
"stock-cut",
|
||||
"overseas",
|
||||
"exchange",
|
||||
"index",
|
||||
"kospi",
|
||||
"kosdaq",
|
||||
"comparison",
|
||||
"theme",
|
||||
"overseas-stocks",
|
||||
"expert",
|
||||
"trading-halt",
|
||||
"settings"
|
||||
]);
|
||||
|
||||
const kospi = markup.slice(
|
||||
markup.indexOf("<symbol id=\"workspace-icon-kospi\""),
|
||||
markup.indexOf("</symbol>", markup.indexOf("<symbol id=\"workspace-icon-kospi\"")));
|
||||
const kosdaq = markup.slice(
|
||||
markup.indexOf("<symbol id=\"workspace-icon-kosdaq\""),
|
||||
markup.indexOf("</symbol>", markup.indexOf("<symbol id=\"workspace-icon-kosdaq\"")));
|
||||
assert.match(kospi, /M4\.5 18V6h4a4 4 0 0 1 0 8h-4/);
|
||||
assert.match(kospi, /M15 6h4M17 6v12M15 18h4/);
|
||||
assert.match(kosdaq, /M2\.3 18V6h1\.6c2\.8 0 4\.3 2\.2 4\.3 6/);
|
||||
assert.match(kosdaq, /M9 18l2\.5-12H13l2\.5 12M10 14h4\.5/);
|
||||
assert.match(kosdaq, /<ellipse cx="19\.2" cy="12" rx="3\.1" ry="6" stroke-width="1\.65">/);
|
||||
assert.match(kosdaq, /m19\.5 14\.5 3\.2 4/);
|
||||
assert.doesNotMatch(kospi + kosdaq, /<text\b/);
|
||||
assert.notEqual(kospi, kosdaq);
|
||||
|
||||
assert.match(styles, /\.workspace-icon-sprite\s*\{[\s\S]*?width:\s*0;[\s\S]*?height:\s*0;/);
|
||||
assert.match(styles, /\.workspace-nav-icon\s*\{\s*shape-rendering:\s*geometricPrecision;\s*\}/);
|
||||
assert.match(navigation,
|
||||
/const titleIcon = document\.querySelector\("\.workspace-heading-icon"\)/);
|
||||
assert.match(navigation,
|
||||
/function syncHeadingIcon\(button\)[\s\S]*?source\.cloneNode\(true\)[\s\S]*?titleIcon\.replaceChildren\(next\)/);
|
||||
assert.match(navigation,
|
||||
/activeWorkspace = name;[\s\S]*?refreshCurrentMenu\(\);[\s\S]*?syncHeadingIcon\(selectedButton\);[\s\S]*?title\.textContent/);
|
||||
assert.match(navigation,
|
||||
/if \(activeWorkspace === "catalog"\)[\s\S]*?syncHeadingIcon\(button\);[\s\S]*?title\.textContent = marketLabel\(button\)/);
|
||||
});
|
||||
|
||||
test("package smokes verify semantic icon painting, heading identity, and collapsed centering", () => {
|
||||
for (const harness of [packageInputHarness, uiOnlySmokeHarness]) {
|
||||
for (const [tabId, icon] of [
|
||||
["overseas", "overseas"],
|
||||
["exchange", "exchange"],
|
||||
["index", "index"],
|
||||
["kospiIndustry", "kospi"],
|
||||
["kosdaqIndustry", "kosdaq"],
|
||||
["comparison", "comparison"],
|
||||
["theme", "theme"],
|
||||
["overseasStocks", "overseas-stocks"],
|
||||
["expert", "expert"],
|
||||
["tradingHalt", "trading-halt"]
|
||||
]) {
|
||||
assert.match(harness, new RegExp(`${tabId}: "${icon}"`));
|
||||
}
|
||||
assert.match(harness, /svg\?\.getBBox\?\.\(\)/);
|
||||
assert.match(harness, /reference:\s*svg\?\.querySelector\("use"\)\?\.getAttribute\("href"\)/);
|
||||
assert.match(harness, /Number\.parseFloat\(style\.opacity\) > 0/);
|
||||
assert.match(harness, /style\.stroke !== "none" \|\| style\.fill !== "none"/);
|
||||
assert.match(harness, /rendered:\s*Boolean\(/);
|
||||
assert.match(harness, /expectedHeadingIcon/);
|
||||
assert.match(harness, /#workspace-icon-\$\{expectedHeadingIcon\}/);
|
||||
}
|
||||
|
||||
assert.match(packageInputHarness, /navigationItems:\s*\[/);
|
||||
assert.match(packageInputHarness, /iconCenterOffset:/);
|
||||
assert.match(packageInputHarness, /iconCenterOffsetY:/);
|
||||
assert.match(packageInputHarness, /item\.iconCenterOffset > 1\.5/);
|
||||
assert.match(packageInputHarness, /item\.iconCenterOffsetY > 1\.5/);
|
||||
assert.match(packageInputHarness, /item\.labelVisible !== false/);
|
||||
assert.match(packageInputHarness, /collapsedIconCenterOffsets:/);
|
||||
assert.match(packageInputHarness,
|
||||
/tab\.icon\.key === baseline\.marketTabs\[index\]\.icon\.key/);
|
||||
|
||||
assert.match(uiOnlySmokeHarness, /layout\.stockTabIcon\.rendered === true/);
|
||||
assert.match(uiOnlySmokeHarness, /layout\.settingsTabIcon\.rendered === true/);
|
||||
assert.match(uiOnlySmokeHarness, /tab\.icon\.rendered === true/);
|
||||
});
|
||||
|
||||
test("workspace navigation stores only the approved local presentation preferences", () => {
|
||||
assert.match(navigation, /entry\.view\.hidden = !isCurrent/);
|
||||
assert.match(navigation, /entry\.view\.toggleAttribute\("inert", !isCurrent\)/);
|
||||
|
||||
Reference in New Issue
Block a user