feat: expose graphic screens in sidebar
This commit is contained in:
@@ -47,8 +47,10 @@ test("legacy shell exposes the original stock, cut, playlist and playout control
|
||||
assert.match(markup, /TAKE OUT/);
|
||||
assert.match(markup, /NEXT/);
|
||||
assert.match(markup, /class="connection-state"[^>]*>미연결<\/div>/);
|
||||
assert.match(markup, /class="active"[^>]*data-tab-id="overseas">해외<\/button>/);
|
||||
assert.match(markup, /data-tab-id="tradingHalt">정지<\/button>/);
|
||||
assert.match(markup,
|
||||
/class="[^"]*\bactive\b[^"]*"[^>]*data-tab-id="overseas"[\s\S]*?<span class="workspace-nav-label">해외<\/span>[\s\S]*?<\/button>/);
|
||||
assert.match(markup,
|
||||
/data-tab-id="tradingHalt"[\s\S]*?<span class="workspace-nav-label">정지<\/span>[\s\S]*?<\/button>/);
|
||||
});
|
||||
|
||||
test("web layer sends intent and does not own scene, DB identity or playlist state", () => {
|
||||
@@ -840,6 +842,8 @@ test("market tabs send closed identity intents and reconcile C# order", () => {
|
||||
assert.match(app, /dataset\.tabId/);
|
||||
assert.match(app, /const existingTabs = new Map\(\)/);
|
||||
assert.match(app, /marketTabs\.insertBefore/);
|
||||
assert.match(app, /button\.querySelector\("\.workspace-nav-label"\)/);
|
||||
assert.match(app, /LegacyWorkspaceNavigation\?\.syncMarket/);
|
||||
assert.doesNotMatch(app, /selectedTab\s*=/);
|
||||
});
|
||||
|
||||
|
||||
@@ -11,34 +11,72 @@ const markup = fs.readFileSync(path.join(webRoot, "index.html"), "utf8");
|
||||
const styles = fs.readFileSync(path.join(webRoot, "styles.css"), "utf8");
|
||||
const navigation = fs.readFileSync(path.join(webRoot, "workspace-navigation.js"), "utf8");
|
||||
|
||||
test("operator shell keeps one switchable workspace beside an always-present schedule", () => {
|
||||
const expectedMarketTabs = [
|
||||
"overseas",
|
||||
"exchange",
|
||||
"index",
|
||||
"kospiIndustry",
|
||||
"kosdaqIndustry",
|
||||
"comparison",
|
||||
"theme",
|
||||
"overseasStocks",
|
||||
"expert",
|
||||
"tradingHalt"
|
||||
];
|
||||
|
||||
test("operator shell exposes stock and each graphic screen beside an always-present schedule", () => {
|
||||
for (const id of [
|
||||
"workspace-region",
|
||||
"workspace-navigation",
|
||||
"workspace-nav-toggle",
|
||||
"workspace-stock-tab",
|
||||
"workspace-catalog-tab",
|
||||
"market-tabs",
|
||||
"workspace-settings-tab",
|
||||
"workspace-title",
|
||||
"stock-workspace",
|
||||
"catalog-workspace",
|
||||
"settings-workspace",
|
||||
"playlist-drop-zone"
|
||||
]) {
|
||||
assert.match(markup, new RegExp(`id="${id}"`));
|
||||
}
|
||||
|
||||
const workspaceStart = markup.indexOf('id="workspace-region"');
|
||||
const navigationStart = markup.indexOf('id="workspace-navigation"');
|
||||
const marketStart = markup.indexOf('id="market-tabs"');
|
||||
const settingsTabStart = markup.indexOf('id="workspace-settings-tab"');
|
||||
const stockStart = markup.indexOf('id="stock-workspace"');
|
||||
const catalogStart = markup.indexOf('id="catalog-workspace"');
|
||||
const settingsStart = markup.indexOf('id="settings-workspace"');
|
||||
const scheduleStart = markup.indexOf('class="playlist-panel"');
|
||||
assert.ok(workspaceStart >= 0 && stockStart > workspaceStart);
|
||||
assert.ok(workspaceStart >= 0 && navigationStart > workspaceStart);
|
||||
assert.ok(marketStart > navigationStart && settingsTabStart > marketStart);
|
||||
assert.ok(stockStart > settingsTabStart);
|
||||
assert.ok(catalogStart > stockStart);
|
||||
assert.ok(scheduleStart > catalogStart);
|
||||
assert.ok(settingsStart > catalogStart && scheduleStart > settingsStart);
|
||||
assert.match(markup,
|
||||
/<\/section>\s*<\/div>\s*<\/section>\s*<\/section>\s*<section class="playlist-panel"/);
|
||||
assert.match(markup, /id="catalog-workspace"[^>]*hidden inert/);
|
||||
assert.match(markup, /id="settings-workspace"[^>]*hidden inert/);
|
||||
assert.match(markup, /<small>항상 표시<\/small><h2>송출 스케줄<\/h2>/);
|
||||
});
|
||||
|
||||
test("the sidebar owns the ten native market buttons and keeps settings outside their order", () => {
|
||||
const marketStart = markup.indexOf('<div id="market-tabs"');
|
||||
const marketEnd = markup.indexOf("</div>", marketStart);
|
||||
assert.ok(marketStart >= 0 && marketEnd > marketStart);
|
||||
const marketMarkup = markup.slice(marketStart, marketEnd);
|
||||
const actualTabs = Array.from(
|
||||
marketMarkup.matchAll(/data-tab-id="([^"]+)"/g), match => match[1]);
|
||||
assert.deepEqual(actualTabs, expectedMarketTabs);
|
||||
assert.equal((marketMarkup.match(/<button\b/g) || []).length, expectedMarketTabs.length);
|
||||
assert.doesNotMatch(marketMarkup, /workspace-settings-tab|workspace-nav-section-label/);
|
||||
for (const tabId of expectedMarketTabs) {
|
||||
assert.match(marketMarkup,
|
||||
new RegExp(`data-tab-id="${tabId}"[\\s\\S]*?class="workspace-nav-label"`));
|
||||
}
|
||||
});
|
||||
|
||||
test("layout uses a two-to-one work and schedule split with compact schedule columns", () => {
|
||||
assert.match(styles,
|
||||
/grid-template-columns:\s*minmax\(900px, 2fr\) minmax\(600px, 1fr\)/);
|
||||
@@ -46,6 +84,11 @@ test("layout uses a two-to-one work and schedule split with compact schedule col
|
||||
/\.workspace-region\s*\{[^}]*grid-template-columns:\s*var\(--workspace-nav-width\) minmax\(0, 1fr\)/);
|
||||
assert.match(styles, /\.workspace-region\.nav-collapsed/);
|
||||
assert.match(styles, /--workspace-nav-collapsed:\s*62px/);
|
||||
assert.match(styles,
|
||||
/\.workspace-navigation\s*\{[^}]*grid-template-rows:\s*66px minmax\(0, 1fr\) auto/);
|
||||
assert.match(styles, /\.workspace-nav-footer\s*\{[^}]*border-top/);
|
||||
assert.match(styles,
|
||||
/\.workspace-navigation \.market-tabs button\s*\{[^}]*width:\s*100%[^}]*height:\s*46px/);
|
||||
assert.match(styles,
|
||||
/\.workspace-nav-item:focus-visible\s*\{[\s\S]*?outline:\s*2px solid var\(--workspace-accent\)/);
|
||||
assert.match(styles, /\.playlist-toolbar\s*\{[^}]*flex-wrap:\s*wrap/);
|
||||
@@ -76,19 +119,48 @@ test("modern operator theme keeps clear semantic states and accessible focus", (
|
||||
assert.match(markup, /class="playlist-heading-icon"[\s\S]*?<svg[^>]*viewBox="0 0 24 24"/);
|
||||
});
|
||||
|
||||
test("the complete sidebar is one labelled and collapsible navigation region", () => {
|
||||
assert.match(markup,
|
||||
/id="workspace-navigation"[^>]*role="navigation"[^>]*aria-label=/);
|
||||
assert.match(markup,
|
||||
/id="workspace-nav-toggle"[^>]*aria-expanded="true"[\s\S]*?aria-controls="workspace-nav-items workspace-nav-footer"/);
|
||||
assert.match(markup,
|
||||
/id="workspace-nav-items"[^>]*role="group"[^>]*aria-label=/);
|
||||
assert.match(markup,
|
||||
/id="market-tabs"[^>]*role="group"[^>]*aria-label=/);
|
||||
assert.match(markup, /id="workspace-nav-footer" class="workspace-nav-footer"/);
|
||||
});
|
||||
|
||||
test("workspace navigation is local presentation state and preserves native contracts", () => {
|
||||
assert.match(navigation, /entry\.view\.hidden = !isCurrent/);
|
||||
assert.match(navigation, /entry\.view\.toggleAttribute\("inert", !isCurrent\)/);
|
||||
assert.match(navigation, /if \(focusWillBeHidden\) selected\.tab\.focus\(\)/);
|
||||
assert.match(navigation, /setAttribute\("aria-current", isCurrent \? "page" : "false"\)/);
|
||||
assert.match(navigation, /if \(focusWillBeHidden && selectedButton\) selectedButton\.focus\(\)/);
|
||||
assert.match(navigation, /button\.setAttribute\("aria-current", isCurrent \? "page" : "false"\)/);
|
||||
assert.match(navigation, /region\.classList\.toggle\("nav-collapsed", !isExpanded\)/);
|
||||
assert.match(navigation, /event\.key !== "ArrowUp" && event\.key !== "ArrowDown"/);
|
||||
assert.match(navigation, /navigation\.querySelectorAll\("\.workspace-nav-item"\)/);
|
||||
assert.match(navigation, /event\.key === "Enter" \|\| event\.key === " "/);
|
||||
assert.match(navigation, /setWorkspace\(menuButton === stockTab \? "stock" : "catalog"\)/);
|
||||
assert.match(navigation, /menuButton\.click\(\)/);
|
||||
assert.match(navigation, /pendingKeyboardMarketTabId = keyboardMarketTabId/);
|
||||
assert.match(navigation, /pendingButton\.focus\(\)/);
|
||||
assert.match(navigation, /document\.activeElement === document\.body/);
|
||||
assert.match(navigation, /marketTabs\.addEventListener\("click"/);
|
||||
assert.match(navigation, /settingsTab\.addEventListener\("click"/);
|
||||
assert.match(navigation, /syncMarket\(button\.dataset\.tabId\)/);
|
||||
assert.match(navigation, /setWorkspace\("catalog"\)/);
|
||||
assert.doesNotMatch(navigation, /postMessage|localStorage|sessionStorage|send\s*\(/);
|
||||
});
|
||||
|
||||
test("native tab rendering preserves sidebar icons and reconciles the active market", () => {
|
||||
const app = fs.readFileSync(path.join(webRoot, "app.js"), "utf8");
|
||||
assert.match(app, /button\.querySelector\("\.workspace-nav-label"\)/);
|
||||
assert.match(app, /if \(label\) label\.textContent = tab\.label/);
|
||||
assert.match(app, /LegacyWorkspaceNavigation\?\.syncMarket/);
|
||||
assert.match(app, /activeTab && state\.isBusy !== true/);
|
||||
assert.doesNotMatch(app,
|
||||
/button\.classList\.toggle\("active"[^\n]*\n\s*button\.setAttribute\("aria-current"/);
|
||||
});
|
||||
|
||||
test("workspace navigation loads before the native-authority application bridge", () => {
|
||||
const navigationScript = markup.indexOf('<script src="workspace-navigation.js"></script>');
|
||||
const applicationScript = markup.indexOf('<script src="app.js"></script>');
|
||||
|
||||
Reference in New Issue
Block a user