feat: add task-manager workspace layout
This commit is contained in:
72
tests/LegacyParityWeb/workspace-navigation.test.cjs
Normal file
72
tests/LegacyParityWeb/workspace-navigation.test.cjs
Normal file
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const test = require("node:test");
|
||||
|
||||
const repositoryRoot = path.resolve(__dirname, "..", "..");
|
||||
const webRoot = path.join(repositoryRoot, "src", "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web");
|
||||
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", () => {
|
||||
for (const id of [
|
||||
"workspace-region",
|
||||
"workspace-navigation",
|
||||
"workspace-nav-toggle",
|
||||
"workspace-stock-tab",
|
||||
"workspace-catalog-tab",
|
||||
"workspace-title",
|
||||
"stock-workspace",
|
||||
"catalog-workspace",
|
||||
"playlist-drop-zone"
|
||||
]) {
|
||||
assert.match(markup, new RegExp(`id="${id}"`));
|
||||
}
|
||||
|
||||
const workspaceStart = markup.indexOf('id="workspace-region"');
|
||||
const stockStart = markup.indexOf('id="stock-workspace"');
|
||||
const catalogStart = markup.indexOf('id="catalog-workspace"');
|
||||
const scheduleStart = markup.indexOf('class="playlist-panel"');
|
||||
assert.ok(workspaceStart >= 0 && stockStart > workspaceStart);
|
||||
assert.ok(catalogStart > stockStart);
|
||||
assert.ok(scheduleStart > catalogStart);
|
||||
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, /<small>항상 표시<\/small><h2>송출 스케줄<\/h2>/);
|
||||
});
|
||||
|
||||
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\)/);
|
||||
assert.match(styles,
|
||||
/\.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-nav-item:focus-visible\s*\{[\s\S]*?outline:\s*2px solid var\(--workspace-accent\)/);
|
||||
assert.match(styles, /\.playlist-toolbar\s*\{[^}]*flex-wrap:\s*wrap/);
|
||||
assert.match(styles, /\.playlist-row\s*\{[^}]*min-width:\s*600px/);
|
||||
});
|
||||
|
||||
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, /region\.classList\.toggle\("nav-collapsed", !isExpanded\)/);
|
||||
assert.match(navigation, /event\.key !== "ArrowUp" && event\.key !== "ArrowDown"/);
|
||||
assert.match(navigation, /event\.key === "Enter" \|\| event\.key === " "/);
|
||||
assert.match(navigation, /setWorkspace\(menuButton === stockTab \? "stock" : "catalog"\)/);
|
||||
assert.match(navigation, /marketTabs\.addEventListener\("click"/);
|
||||
assert.doesNotMatch(navigation, /postMessage|localStorage|sessionStorage|send\s*\(/);
|
||||
});
|
||||
|
||||
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>');
|
||||
assert.ok(navigationScript >= 0 && applicationScript > navigationScript);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
||||
|
||||
public sealed class LegacyWorkspaceLayoutContractTests
|
||||
{
|
||||
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
||||
private static readonly string WebRoot = Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"Web");
|
||||
private static readonly string Markup = File.ReadAllText(Path.Combine(WebRoot, "index.html"));
|
||||
private static readonly string Styles = File.ReadAllText(Path.Combine(WebRoot, "styles.css"));
|
||||
private static readonly string Navigation = File.ReadAllText(
|
||||
Path.Combine(WebRoot, "workspace-navigation.js"));
|
||||
|
||||
[Fact]
|
||||
public void ShellUsesSwitchableWorkspaceBesidePersistentSchedule()
|
||||
{
|
||||
var workspaceStart = Markup.IndexOf("id=\"workspace-region\"", StringComparison.Ordinal);
|
||||
var stockStart = Markup.IndexOf("id=\"stock-workspace\"", StringComparison.Ordinal);
|
||||
var catalogStart = Markup.IndexOf("id=\"catalog-workspace\"", StringComparison.Ordinal);
|
||||
var scheduleStart = Markup.IndexOf("class=\"playlist-panel\"", StringComparison.Ordinal);
|
||||
|
||||
Assert.True(workspaceStart >= 0);
|
||||
Assert.True(stockStart > workspaceStart);
|
||||
Assert.True(catalogStart > stockStart);
|
||||
Assert.True(scheduleStart > catalogStart);
|
||||
Assert.Matches(
|
||||
@"<\/section>\s*<\/div>\s*<\/section>\s*<\/section>\s*<section class=""playlist-panel""",
|
||||
Markup);
|
||||
Assert.Matches("id=\"catalog-workspace\"[^>]*hidden inert", Markup);
|
||||
Assert.Contains("<small>항상 표시</small><h2>송출 스케줄</h2>", Markup,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkAndScheduleUseResponsiveTwoToOneColumns()
|
||||
{
|
||||
Assert.Contains(
|
||||
"grid-template-columns: minmax(900px, 2fr) minmax(600px, 1fr);",
|
||||
Styles,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Matches(
|
||||
new Regex(
|
||||
@"\.playlist-toolbar\s*\{[^}]*flex-wrap:\s*wrap",
|
||||
RegexOptions.CultureInvariant),
|
||||
Styles);
|
||||
Assert.Matches(
|
||||
new Regex(
|
||||
@"\.playlist-row\s*\{[^}]*min-width:\s*600px",
|
||||
RegexOptions.CultureInvariant),
|
||||
Styles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavigationChangesPresentationWithoutNativeOrStoredState()
|
||||
{
|
||||
Assert.Contains("entry.view.hidden = !isCurrent", Navigation, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"entry.view.toggleAttribute(\"inert\", !isCurrent)",
|
||||
Navigation,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("nav-collapsed", Navigation, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("postMessage", Navigation, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("localStorage", Navigation, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("sessionStorage", Navigation, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "MBN_STOCK_WEBVIEW.sln")))
|
||||
{
|
||||
return directory.FullName;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Repository root could not be located.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user