Embed umbrella graph in workspace panel
This commit is contained in:
parent
28ffa92f6f
commit
c0ff59097c
@ -2,7 +2,7 @@
|
||||
|
||||
**Upstream version:** v0.51.118
|
||||
**Plugin version:** 0.5.0
|
||||
**Total dependencies:** 32 (23 public API · 0 forced internal · 9 frontend)
|
||||
**Total dependencies:** 34 (25 public API · 0 forced internal · 9 frontend)
|
||||
|
||||
> **Auto-generated by `scripts/ast-connection-map.py`. Do not hand-edit.**
|
||||
> To change a justification, edit the `# CONNECTION:` comment above the
|
||||
@ -23,6 +23,8 @@
|
||||
| `plugin.py:43` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/adwright.js")` |
|
||||
| `plugin.py:45` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")` |
|
||||
| `plugin.py:46` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")` |
|
||||
| `plugin.py:48` | `api.inject_stylesheet` | `api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css")` |
|
||||
| `plugin.py:49` | `api.inject_script` | `api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js")` |
|
||||
| `routes/adwright.py:68` | `api.logger` | `log = api.logger("svrnty.routes.adwright")` |
|
||||
| `routes/adwright.py:69` | `api.register_route` | `api.register_route(` |
|
||||
| `routes/adwright.py:71` | `api.register_route` | `api.register_route(` |
|
||||
@ -52,13 +54,13 @@ _None. Plugin uses only the public API._ ✓
|
||||
|
||||
| File | Line | URL |
|
||||
|---|---|---|
|
||||
| `static/bte.js` | 329 | `/api/command/requestPhotoshoot` |
|
||||
| `static/bte.js` | 360 | `/api/query/assetDtos` |
|
||||
| `static/bte.js` | 372 | `/api/assets/` |
|
||||
| `static/bte.js` | 481 | `/api/command/rateAsset` |
|
||||
| `static/bte.js` | 365 | `/api/command/requestPhotoshoot` |
|
||||
| `static/bte.js` | 396 | `/api/query/assetDtos` |
|
||||
| `static/bte.js` | 408 | `/api/assets/` |
|
||||
| `static/bte.js` | 517 | `/api/command/rateAsset` |
|
||||
| `static/adwright.js` | 176 | `/api/profile/switch` |
|
||||
| `static/adwright.js` | 197 | `/api/profile/active` |
|
||||
| `static/adwright.js` | 606 | `/api/adwright/provision-creds` |
|
||||
| `static/umbrella.js` | 41 | `/api/umbrella` |
|
||||
| `static/umbrella.js` | 57 | `/api/umbrella` |
|
||||
| `static/app.js` | 165 | `/api/vault/status` |
|
||||
|
||||
|
||||
@ -33,10 +33,12 @@ assets:
|
||||
- /plugins/svrnty/svrnty_nav.js
|
||||
- /plugins/svrnty/adwright.js
|
||||
- /plugins/svrnty/bte.js
|
||||
- /plugins/svrnty/umbrella_inline.js
|
||||
stylesheets:
|
||||
- /plugins/svrnty/app.css
|
||||
- /plugins/svrnty/adwright.css
|
||||
- /plugins/svrnty/bte.css
|
||||
- /plugins/svrnty/umbrella_inline.css
|
||||
|
||||
# Routes this plugin registers at load time (declarative cross-check vs runtime).
|
||||
# Each row maps to a routes/<file>.py.
|
||||
|
||||
@ -44,6 +44,9 @@ def register(api):
|
||||
# BTE Command Center panel — same pattern (main.svrnty-showing-bte).
|
||||
api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/bte.css")
|
||||
api.inject_script(f"/plugins/{STATIC_PREFIX}/bte.js")
|
||||
# Inline Umbrella graph for the Hermes Workspace right panel.
|
||||
api.inject_stylesheet(f"/plugins/{STATIC_PREFIX}/umbrella_inline.css")
|
||||
api.inject_script(f"/plugins/{STATIC_PREFIX}/umbrella_inline.js")
|
||||
log.info("static + assets wired at /plugins/%s/", STATIC_PREFIX)
|
||||
|
||||
# Routes — each feature lives in its own module under routes/.
|
||||
|
||||
@ -22,19 +22,10 @@
|
||||
'<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
||||
bte:
|
||||
'<path d="M12 2l1.8 5.6L19.4 9.4l-4.5 3.3 1.7 5.7L12 15l-4.6 3.4 1.7-5.7L4.6 9.4l5.6-1.8L12 2z"/>',
|
||||
graph:
|
||||
'<circle cx="5" cy="12" r="2"/><circle cx="12" cy="5" r="2"/><circle cx="19" cy="12" r="2"/><circle cx="12" cy="19" r="2"/><path d="M6.7 10.6l3.9-4"/><path d="M13.4 6.6l3.9 4"/><path d="M17.3 13.4l-3.9 4"/><path d="M10.6 17.4l-3.9-4"/><path d="M7 12h10"/>',
|
||||
};
|
||||
const TABS = [
|
||||
{ id: "adwright", label: "Adwright", tooltip: "Adwright — marketing intelligence" },
|
||||
{ id: "bte", label: "BTE", tooltip: "BTE — brand creative studio" },
|
||||
{
|
||||
id: "project-graph",
|
||||
label: "Project Graph",
|
||||
tooltip: "Project Graph — open workspace graph",
|
||||
href: "/plugins/svrnty/umbrella.html",
|
||||
icon: "graph",
|
||||
},
|
||||
];
|
||||
|
||||
function _svg(iconPath, size, stroke) {
|
||||
@ -84,10 +75,6 @@
|
||||
btn.innerHTML = _svg(ICONS[t.icon || t.id], c.size, c.stroke);
|
||||
btn.addEventListener("click", () => {
|
||||
LOG("clicked:", t.id);
|
||||
if (t.href) {
|
||||
window.open(t.href, "_blank", "noopener,noreferrer");
|
||||
return;
|
||||
}
|
||||
if (typeof window.switchPanel === "function") {
|
||||
window.switchPanel(t.id, { fromRailClick: true });
|
||||
} else {
|
||||
@ -116,7 +103,7 @@
|
||||
if (typeof window.switchPanel !== "function") return false;
|
||||
if (window.switchPanel.__svrntyWrapped) return true;
|
||||
const original = window.switchPanel;
|
||||
const OUR_IDS = TABS.filter((t) => !t.href).map((t) => t.id);
|
||||
const OUR_IDS = TABS.map((t) => t.id);
|
||||
|
||||
async function wrapped(name, opts) {
|
||||
const result = await original(name, opts);
|
||||
|
||||
@ -159,3 +159,56 @@
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
}
|
||||
.umbrella-footer a { color: var(--accent); }
|
||||
|
||||
body.umbrella-inline {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
body.umbrella-inline .umbrella-root {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
body.umbrella-inline .umbrella-header {
|
||||
padding: 8px;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
body.umbrella-inline .umbrella-header h1 {
|
||||
display: none;
|
||||
}
|
||||
body.umbrella-inline .umbrella-stats {
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
body.umbrella-inline .umbrella-controls {
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
body.umbrella-inline .umbrella-controls input[type=search] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
body.umbrella-inline .umbrella-controls button,
|
||||
body.umbrella-inline .chip {
|
||||
padding: 4px 7px;
|
||||
font-size: 10px;
|
||||
}
|
||||
body.umbrella-inline .umbrella-filters,
|
||||
body.umbrella-inline .umbrella-disclosure {
|
||||
max-height: 58px;
|
||||
overflow: auto;
|
||||
}
|
||||
body.umbrella-inline .umbrella-footer {
|
||||
display: none;
|
||||
}
|
||||
body.umbrella-inline .umbrella-side {
|
||||
width: 100%;
|
||||
}
|
||||
body.umbrella-inline .side-body {
|
||||
max-height: 38vh;
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
<link rel="stylesheet" href="/plugins/svrnty/umbrella.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
if (new URLSearchParams(location.search).get("inline") === "1") {
|
||||
document.body.classList.add("umbrella-inline");
|
||||
}
|
||||
</script>
|
||||
<div id="svrnty-umbrella" class="umbrella-root">
|
||||
<header class="umbrella-header">
|
||||
<h1>Cortex-OS Umbrella</h1>
|
||||
|
||||
28
static/umbrella_inline.css
Normal file
28
static/umbrella_inline.css
Normal file
@ -0,0 +1,28 @@
|
||||
/* Inline Umbrella graph mounted into Hermes WebUI's Workspace right panel. */
|
||||
|
||||
.svrnty-graph-btn.active {
|
||||
background: var(--accent-bg);
|
||||
color: var(--accent-text);
|
||||
}
|
||||
|
||||
.svrnty-umbrella-inline-surface {
|
||||
display: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: var(--bg, #0f1115);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svrnty-umbrella-inline-surface[data-open="true"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.svrnty-umbrella-inline-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border: 0;
|
||||
background: #0f1115;
|
||||
}
|
||||
210
static/umbrella_inline.js
Normal file
210
static/umbrella_inline.js
Normal file
@ -0,0 +1,210 @@
|
||||
// umbrella_inline.js — mounts the Cortex-OS graph inside Hermes WebUI's
|
||||
// Workspace right panel. Plugin-only shim; the standalone page remains a
|
||||
// fallback at /plugins/svrnty/umbrella.html.
|
||||
(function () {
|
||||
"use strict";
|
||||
if (window.__svrntyUmbrellaInlineLoaded) return;
|
||||
window.__svrntyUmbrellaInlineLoaded = true;
|
||||
|
||||
const LOG = (...a) => console.log("[svrnty-umbrella-inline]", ...a);
|
||||
const ICON =
|
||||
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" ' +
|
||||
'stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
|
||||
'stroke-linejoin="round" aria-hidden="true">' +
|
||||
'<circle cx="5" cy="12" r="2"/><circle cx="12" cy="5" r="2"/>' +
|
||||
'<circle cx="19" cy="12" r="2"/><circle cx="12" cy="19" r="2"/>' +
|
||||
'<path d="M6.7 10.6l3.9-4"/><path d="M13.4 6.6l3.9 4"/>' +
|
||||
'<path d="M17.3 13.4l-3.9 4"/><path d="M10.6 17.4l-3.9-4"/>' +
|
||||
'<path d="M7 12h10"/></svg>';
|
||||
|
||||
let surface = null;
|
||||
let graphOpen = false;
|
||||
let previousHeading = "Workspace";
|
||||
let previousPanelWidth = "";
|
||||
let openedPanelForGraph = false;
|
||||
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function isWorkspacePanelOpen() {
|
||||
const htmlState = document.documentElement.dataset.workspacePanel;
|
||||
const panel = document.querySelector(".rightpanel");
|
||||
return htmlState === "open" && !!panel && getComputedStyle(panel).pointerEvents !== "none";
|
||||
}
|
||||
|
||||
function ensureSurface() {
|
||||
if (surface && document.body.contains(surface)) return surface;
|
||||
const panel = document.querySelector(".rightpanel");
|
||||
const preview = $("previewArea");
|
||||
if (!panel || !preview) return null;
|
||||
surface = document.createElement("div");
|
||||
surface.id = "svrntyUmbrellaInlineSurface";
|
||||
surface.className = "svrnty-umbrella-inline-surface";
|
||||
surface.dataset.open = "false";
|
||||
surface.innerHTML =
|
||||
'<iframe class="svrnty-umbrella-inline-frame" ' +
|
||||
'title="Project graph" src="/plugins/svrnty/umbrella.html?inline=1"></iframe>';
|
||||
preview.insertAdjacentElement("afterend", surface);
|
||||
return surface;
|
||||
}
|
||||
|
||||
function setWorkspaceSurfaces(mode) {
|
||||
const fileTree = $("fileTree");
|
||||
const empty = $("wsEmptyState");
|
||||
const preview = $("previewArea");
|
||||
const breadcrumb = $("breadcrumbBar");
|
||||
const graph = ensureSurface();
|
||||
if (!graph) return;
|
||||
const showGraph = mode === "graph";
|
||||
if (fileTree) fileTree.style.display = showGraph ? "none" : "";
|
||||
if (empty) empty.style.display = showGraph ? "none" : empty.style.display;
|
||||
if (preview) preview.style.display = showGraph ? "none" : "";
|
||||
if (breadcrumb) breadcrumb.style.display = showGraph ? "none" : breadcrumb.style.display;
|
||||
graph.dataset.open = showGraph ? "true" : "false";
|
||||
}
|
||||
|
||||
function syncHeader(open) {
|
||||
const heading = $("workspacePanelHeading");
|
||||
const button = $("btnSvrntyWorkspaceGraph");
|
||||
if (heading) {
|
||||
if (open) {
|
||||
previousHeading = heading.textContent || "Workspace";
|
||||
heading.textContent = "Project Graph";
|
||||
heading.title = "Project Graph";
|
||||
} else {
|
||||
heading.textContent = previousHeading || "Workspace";
|
||||
heading.title = "Workspace root";
|
||||
}
|
||||
}
|
||||
if (button) {
|
||||
button.classList.toggle("active", open);
|
||||
button.setAttribute("aria-pressed", open ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
function resizeGraphSoon() {
|
||||
const frame = surface && surface.querySelector("iframe");
|
||||
if (!frame) return;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const cy = frame.contentWindow && frame.contentWindow.cy;
|
||||
if (cy) {
|
||||
cy.resize();
|
||||
cy.fit(null, 24);
|
||||
}
|
||||
} catch (_) {}
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function expandPanelForGraph() {
|
||||
const panel = document.querySelector(".rightpanel");
|
||||
if (!panel) return;
|
||||
previousPanelWidth = panel.style.width || "";
|
||||
const current = panel.getBoundingClientRect().width || 0;
|
||||
if (current < 480) {
|
||||
panel.style.width = "500px";
|
||||
}
|
||||
}
|
||||
|
||||
function restorePanelWidth() {
|
||||
const panel = document.querySelector(".rightpanel");
|
||||
if (!panel) return;
|
||||
panel.style.width = previousPanelWidth;
|
||||
previousPanelWidth = "";
|
||||
}
|
||||
|
||||
function openWorkspaceGraph() {
|
||||
if (graphOpen) {
|
||||
resizeGraphSoon();
|
||||
return;
|
||||
}
|
||||
openedPanelForGraph = !isWorkspacePanelOpen();
|
||||
if (typeof window.openWorkspacePanel === "function") {
|
||||
window.openWorkspacePanel("browse");
|
||||
} else if (typeof window.toggleWorkspacePanel === "function") {
|
||||
window.toggleWorkspacePanel(true);
|
||||
}
|
||||
expandPanelForGraph();
|
||||
graphOpen = true;
|
||||
setWorkspaceSurfaces("graph");
|
||||
syncHeader(true);
|
||||
resizeGraphSoon();
|
||||
LOG("opened inline graph");
|
||||
}
|
||||
|
||||
function closeWorkspaceGraph() {
|
||||
if (!graphOpen) return false;
|
||||
graphOpen = false;
|
||||
setWorkspaceSurfaces("workspace");
|
||||
syncHeader(false);
|
||||
restorePanelWidth();
|
||||
if (openedPanelForGraph && typeof window.closeWorkspacePanel === "function") {
|
||||
window.closeWorkspacePanel();
|
||||
} else if (typeof window.renderBreadcrumb === "function") {
|
||||
window.renderBreadcrumb();
|
||||
}
|
||||
openedPanelForGraph = false;
|
||||
LOG("closed inline graph");
|
||||
return true;
|
||||
}
|
||||
|
||||
function injectButton() {
|
||||
const actions = document.querySelector(".rightpanel .panel-actions");
|
||||
if (!actions || $("btnSvrntyWorkspaceGraph")) return !!actions;
|
||||
const refresh = $("btnRefreshPanel");
|
||||
const btn = document.createElement("button");
|
||||
btn.className = "panel-icon-btn has-tooltip has-tooltip--bottom svrnty-graph-btn";
|
||||
btn.id = "btnSvrntyWorkspaceGraph";
|
||||
btn.type = "button";
|
||||
btn.dataset.tooltip = "Project graph";
|
||||
btn.setAttribute("aria-label", "Open project graph");
|
||||
btn.setAttribute("aria-pressed", "false");
|
||||
btn.innerHTML = ICON;
|
||||
btn.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openWorkspaceGraph();
|
||||
});
|
||||
if (refresh && refresh.parentElement === actions) {
|
||||
refresh.insertAdjacentElement("afterend", btn);
|
||||
} else {
|
||||
actions.appendChild(btn);
|
||||
}
|
||||
ensureSurface();
|
||||
return true;
|
||||
}
|
||||
|
||||
function interceptCloseButton() {
|
||||
const close = $("btnClearPreview");
|
||||
if (!close || close.dataset.svrntyGraphCloseBound === "1") return !!close;
|
||||
close.dataset.svrntyGraphCloseBound = "1";
|
||||
close.addEventListener("click", (event) => {
|
||||
if (!graphOpen) return;
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
closeWorkspaceGraph();
|
||||
}, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function init() {
|
||||
const ok = injectButton() && interceptCloseButton();
|
||||
if (!ok) {
|
||||
requestAnimationFrame(init);
|
||||
return;
|
||||
}
|
||||
window.SvrntyUmbrellaInline = {
|
||||
open: openWorkspaceGraph,
|
||||
close: closeWorkspaceGraph,
|
||||
isOpen: () => graphOpen,
|
||||
};
|
||||
LOG("ready");
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@ -66,6 +66,8 @@ def test_loader_register_wires_our_plugin(loader, monkeypatch):
|
||||
assert "svrnty" in loader._STATIC
|
||||
assert "/plugins/svrnty/app.css" in loader._STYLESHEETS
|
||||
assert "/plugins/svrnty/app.js" in loader._SCRIPTS
|
||||
assert "/plugins/svrnty/umbrella_inline.css" in loader._STYLESHEETS
|
||||
assert "/plugins/svrnty/umbrella_inline.js" in loader._SCRIPTS
|
||||
# Audio processor for voice-message transcription
|
||||
assert len(loader._AUDIO_PROCESSORS) == 1
|
||||
|
||||
|
||||
@ -5,13 +5,14 @@ from pathlib import Path
|
||||
NAV_JS = Path(__file__).resolve().parents[2] / "static" / "svrnty_nav.js"
|
||||
|
||||
|
||||
def test_project_graph_nav_opens_umbrella_page_in_new_tab():
|
||||
def test_project_graph_is_not_in_left_nav():
|
||||
src = NAV_JS.read_text()
|
||||
assert "Project Graph" in src
|
||||
assert "/plugins/svrnty/umbrella.html" in src
|
||||
assert 'window.open(t.href, "_blank", "noopener,noreferrer")' in src
|
||||
assert "Project Graph" not in src
|
||||
assert "/plugins/svrnty/umbrella.html" not in src
|
||||
assert "window.open" not in src
|
||||
|
||||
|
||||
def test_project_graph_does_not_participate_in_panel_switching():
|
||||
def test_svrnty_tabs_participate_in_panel_switching():
|
||||
src = NAV_JS.read_text()
|
||||
assert "TABS.filter((t) => !t.href).map((t) => t.id)" in src
|
||||
assert "const TABS = [" in src
|
||||
assert "const OUR_IDS = TABS.map((t) => t.id)" in src
|
||||
|
||||
54
tests/unit/test_umbrella_inline_static.py
Normal file
54
tests/unit/test_umbrella_inline_static.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""Static checks for the inline Workspace-panel umbrella graph integration."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
INLINE_JS = ROOT / "static" / "umbrella_inline.js"
|
||||
INLINE_CSS = ROOT / "static" / "umbrella_inline.css"
|
||||
UMBRELLA_HTML = ROOT / "static" / "umbrella.html"
|
||||
UMBRELLA_CSS = ROOT / "static" / "umbrella.css"
|
||||
PLUGIN = ROOT / "plugin.py"
|
||||
|
||||
|
||||
def test_plugin_injects_inline_umbrella_assets():
|
||||
src = PLUGIN.read_text()
|
||||
assert "/plugins/{STATIC_PREFIX}/umbrella_inline.css" in src
|
||||
assert "/plugins/{STATIC_PREFIX}/umbrella_inline.js" in src
|
||||
|
||||
|
||||
def test_inline_graph_targets_workspace_right_panel():
|
||||
src = INLINE_JS.read_text()
|
||||
assert "btnSvrntyWorkspaceGraph" in src
|
||||
assert '.rightpanel .panel-actions' in src
|
||||
assert "Project graph" in src
|
||||
assert "svrntyUmbrellaInlineSurface" in src
|
||||
assert "/plugins/svrnty/umbrella.html?inline=1" in src
|
||||
assert "window.open(" not in src
|
||||
|
||||
|
||||
def test_inline_graph_uses_right_panel_mode_switching():
|
||||
src = INLINE_JS.read_text()
|
||||
assert "openWorkspaceGraph" in src
|
||||
assert "closeWorkspaceGraph" in src
|
||||
assert "openWorkspacePanel" in src
|
||||
assert "expandPanelForGraph" in src
|
||||
assert "restorePanelWidth" in src
|
||||
assert 'panel.style.width = "500px"' in src
|
||||
assert "btnClearPreview" in src
|
||||
assert "stopImmediatePropagation" in src
|
||||
|
||||
|
||||
def test_inline_graph_has_panel_surface_styles():
|
||||
src = INLINE_CSS.read_text()
|
||||
assert ".svrnty-umbrella-inline-surface" in src
|
||||
assert '.svrnty-umbrella-inline-surface[data-open="true"]' in src
|
||||
assert ".svrnty-umbrella-inline-frame" in src
|
||||
|
||||
|
||||
def test_standalone_umbrella_supports_inline_mode():
|
||||
html = UMBRELLA_HTML.read_text()
|
||||
css = UMBRELLA_CSS.read_text()
|
||||
assert 'get("inline") === "1"' in html
|
||||
assert "umbrella-inline" in html
|
||||
assert "body.umbrella-inline .umbrella-header" in css
|
||||
assert "body.umbrella-inline .umbrella-footer" in css
|
||||
Loading…
Reference in New Issue
Block a user