refactor: centralize legacy class aliases and node image helpers

This commit is contained in:
rookiestar28
2026-03-20 05:15:51 +08:00
parent bd318eb0ac
commit fdef19eb0d
10 changed files with 192 additions and 114 deletions
+6 -8
View File
@@ -1,4 +1,5 @@
import logging
from functools import partial
from typing import Any, Tuple
try:
@@ -64,14 +65,11 @@ class OpenClawImageToPrompt:
FUNCTION = "generate_prompt"
CATEGORY = "moltbot"
def _tensor_to_base64_png(self, tensor_image: Any, max_side: int) -> str:
"""
Convert ComfyUI tensor (Batch, H, W, C) to base64 PNG.
Uses the first image in batch.
"""
return tensor_to_base64_png(
tensor_image=tensor_image, max_side=max_side, context="ImageToPrompt"
)
# R154: keep the compatibility method name, but bind the shared helper
# directly so node wrappers do not duplicate image conversion logic.
_tensor_to_base64_png = staticmethod(
partial(tensor_to_base64_png, context="ImageToPrompt")
)
def generate_prompt(
self, image: Any, goal: str, detail_level: str, max_image_side: int
+6 -7
View File
@@ -1,5 +1,6 @@
import json
import logging
from functools import partial
from typing import Any, Tuple
try:
@@ -76,13 +77,11 @@ class OpenClawPromptRefiner:
FUNCTION = "refine_prompt"
CATEGORY = "moltbot"
def _tensor_to_base64_png(self, tensor_image: Any, max_side: int) -> str:
"""
Convert ComfyUI tensor (Batch, H, W, C) to base64 PNG.
"""
return tensor_to_base64_png(
tensor_image=tensor_image, max_side=max_side, context="PromptRefiner"
)
# R154: keep the compatibility method name, but bind the shared helper
# directly so node wrappers do not duplicate image conversion logic.
_tensor_to_base64_png = staticmethod(
partial(tensor_to_base64_png, context="PromptRefiner")
)
def refine_prompt(
self,
+7 -1
View File
@@ -4,7 +4,10 @@
*/
import { ErrorBoundary } from "./ErrorBoundary.js";
import { STORAGE_KEYS, getMirroredStorageValue, setMirroredStorageValue } from "./openclaw_compat.js";
import { normalizeLegacyClassNames } from "./openclaw_utils.js";
import {
applyLegacyClassAliases,
normalizeLegacyClassNames,
} from "./openclaw_utils.js";
export class TabManager {
constructor() {
@@ -79,6 +82,8 @@ export class TabManager {
normalizeLegacyClassNames(this.tabsEl);
normalizeLegacyClassNames(this.contentEl);
applyLegacyClassAliases(this.tabsEl);
applyLegacyClassAliases(this.contentEl);
}
activateTab(id) {
@@ -119,6 +124,7 @@ export class TabManager {
if (pane) {
normalizeLegacyClassNames(pane);
applyLegacyClassAliases(pane);
}
}
+6 -1
View File
@@ -5,7 +5,10 @@
import { tabManager } from "./openclaw_tabs.js";
import { ErrorBoundary } from "./ErrorBoundary.js";
import { openclawApi } from "./openclaw_api.js";
import { normalizeLegacyClassNames } from "./openclaw_utils.js";
import {
applyLegacyClassAliases,
normalizeLegacyClassNames,
} from "./openclaw_utils.js";
import { OpenClawActions } from "./openclaw_actions.js";
import { QueueMonitor } from "./openclaw_queue_monitor.js";
import { OpenClawNotificationCenter } from "./openclaw_notification_center.js";
@@ -204,6 +207,7 @@ export class OpenClawUI {
tabManager.init(tabBar, contentArea);
normalizeLegacyClassNames(container);
applyLegacyClassAliases(container);
this.bannerManager.bind(container, (action) => this.handleAction(action));
this.notificationCenter.render();
}
@@ -301,6 +305,7 @@ export class OpenClawUI {
this.container.appendChild(overlay);
normalizeLegacyClassNames(overlay);
applyLegacyClassAliases(overlay);
}
}
+48
View File
@@ -72,6 +72,54 @@ export function normalizeLegacyClassNames(root) {
return root;
}
/**
* R154: derive runtime legacy aliases from canonical class tokens so templates
* do not need to hand-author duplicate `openclaw-*` + `moltbot-*` markup.
*/
export function buildLegacyAliasClassTokens(className = "") {
const canonicalTokens = normalizeLegacyClassTokens(className)
.split(/\s+/)
.map((token) => token.trim())
.filter(Boolean);
const seen = new Set(canonicalTokens);
const combined = [...canonicalTokens];
canonicalTokens.forEach((token) => {
if (!token.startsWith("openclaw-")) {
return;
}
const legacy = `moltbot-${token.slice("openclaw-".length)}`;
if (seen.has(legacy)) {
return;
}
seen.add(legacy);
combined.push(legacy);
});
return combined.join(" ");
}
export function applyLegacyClassAliases(root) {
if (!root) return root;
const nodes = [];
if (typeof root.className === "string") {
nodes.push(root);
}
if (typeof root.querySelectorAll === "function") {
nodes.push(...root.querySelectorAll("[class]"));
}
nodes.forEach((node) => {
if (typeof node.className !== "string") return;
const withAliases = buildLegacyAliasClassTokens(node.className);
if (withAliases !== node.className) {
node.className = withAliases;
}
});
return root;
}
/**
* Lightweight toast helper for UI feedback.
* @param {string} message
+13 -13
View File
@@ -20,21 +20,21 @@ export const PacksTab = {
render(container) {
// --- 1. Static Layout ---
container.innerHTML = `
<div class="openclaw-panel openclaw-panel moltbot-panel">
<div class="openclaw-card openclaw-card moltbot-card" style="border-radius:0; border:none; border-bottom:1px solid var(--moltbot-color-border);">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Asset Packs</div>
<div class="openclaw-error-box openclaw-error-box moltbot-error-box" style="display:none"></div>
<div class="openclaw-toolbar openclaw-toolbar moltbot-toolbar" style="margin-top:5px; display:flex; gap:5px; align-items:center;" id="pack-toolbar">
<div class="openclaw-panel">
<div class="openclaw-card" style="border-radius:0; border:none; border-bottom:1px solid var(--moltbot-color-border);">
<div class="openclaw-section-header">Asset Packs</div>
<div class="openclaw-error-box" style="display:none"></div>
<div class="openclaw-toolbar" style="margin-top:5px; display:flex; gap:5px; align-items:center;" id="pack-toolbar">
<input type="file" id="pack-import-input" accept=".zip" style="display:none">
<button class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-primary openclaw-btn-primary moltbot-btn-primary" id="pack-import-btn">Import Pack</button>
<button class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-sm openclaw-btn-sm moltbot-btn-sm" id="pack-refresh-btn" style="margin-left: auto;">
<button class="openclaw-btn openclaw-btn-primary" id="pack-import-btn">Import Pack</button>
<button class="openclaw-btn openclaw-btn-sm" id="pack-refresh-btn" style="margin-left: auto;">
Refresh
</button>
</div>
</div>
<div id="pack-list" class="openclaw-scroll-area openclaw-scroll-area moltbot-scroll-area" style="padding:10px;">
<div class="openclaw-empty-state openclaw-empty-state moltbot-empty-state">Loading...</div>
<div id="pack-list" class="openclaw-scroll-area" style="padding:10px;">
<div class="openclaw-empty-state">Loading...</div>
</div>
</div>
`;
@@ -51,7 +51,7 @@ export const PacksTab = {
const renderListItem = (pack) => {
return `
<div class="openclaw-card openclaw-card moltbot-card" style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: start;">
<div class="openclaw-card" style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: start;">
<div>
<div style="font-weight: bold; font-size: var(--moltbot-font-md); color: var(--moltbot-color-fg);">
${escapeHtml(pack.name)} <span style="font-weight:normal; color:var(--moltbot-color-fg-muted);">v${escapeHtml(pack.version)}</span>
@@ -64,8 +64,8 @@ export const PacksTab = {
</div>
</div>
<div style="display: flex; gap: 5px; flex-direction: column; align-items: flex-end;">
<button class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-sm openclaw-btn-sm moltbot-btn-sm" data-action="export" data-name="${escapeHtml(pack.name)}" data-version="${escapeHtml(pack.version)}">Export</button>
<button class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-sm openclaw-btn-sm moltbot-btn-sm openclaw-btn-danger openclaw-btn-danger moltbot-btn-danger" data-action="delete" data-name="${escapeHtml(pack.name)}" data-version="${escapeHtml(pack.version)}">Uninstall</button>
<button class="openclaw-btn openclaw-btn-sm" data-action="export" data-name="${escapeHtml(pack.name)}" data-version="${escapeHtml(pack.version)}">Export</button>
<button class="openclaw-btn openclaw-btn-sm openclaw-btn-danger" data-action="delete" data-name="${escapeHtml(pack.name)}" data-version="${escapeHtml(pack.version)}">Uninstall</button>
</div>
</div>
`;
@@ -73,7 +73,7 @@ export const PacksTab = {
const renderList = (packs) => {
if (!packs || packs.length === 0) {
ui.list.innerHTML = '<div class="openclaw-empty-state openclaw-empty-state moltbot-empty-state">No packs installed.</div>';
ui.list.innerHTML = '<div class="openclaw-empty-state">No packs installed.</div>';
return;
}
ui.list.innerHTML = packs.map(renderListItem).join("");
+30 -30
View File
@@ -8,30 +8,30 @@ export const PlannerTab = {
render(container) {
container.innerHTML = `
<div class="openclaw-panel openclaw-panel moltbot-panel">
<div class="openclaw-scroll-area openclaw-scroll-area moltbot-scroll-area">
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Generation Goal</div>
<div class="openclaw-panel">
<div class="openclaw-scroll-area">
<div class="openclaw-card">
<div class="openclaw-section-header">Generation Goal</div>
<div class="openclaw-error-box openclaw-error-box moltbot-error-box" style="display:none" id="planner-error"></div>
<div class="openclaw-error-box" style="display:none" id="planner-error"></div>
<div class="openclaw-grid-2 openclaw-grid-2 moltbot-grid-2">
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Profile</label>
<select id="planner-profile" class="openclaw-select openclaw-select moltbot-select">
<div class="openclaw-grid-2">
<div class="openclaw-input-group">
<label class="openclaw-label">Profile</label>
<select id="planner-profile" class="openclaw-select">
<option value="SDXL-v1">SDXL v1</option>
<option value="Flux-Dev">Flux Dev</option>
</select>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Style / Directives</label>
<input type="text" id="planner-style" class="openclaw-input openclaw-input moltbot-input" placeholder="e.g. Cyberpunk, 8k...">
<div class="openclaw-input-group">
<label class="openclaw-label">Style / Directives</label>
<input type="text" id="planner-style" class="openclaw-input" placeholder="e.g. Cyberpunk, 8k...">
</div>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Requirements</label>
<textarea id="planner-reqs" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-sm openclaw-textarea-sm moltbot-textarea-sm" placeholder="Describe the image..."></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Requirements</label>
<textarea id="planner-reqs" class="openclaw-textarea openclaw-textarea-sm" placeholder="Describe the image..."></textarea>
</div>
<!-- R38-Lite: Loading state container -->
@@ -45,28 +45,28 @@ export const PlannerTab = {
</div>
<div style="margin-top:8px;">
<div style="font-size:0.85em; opacity:0.8; margin-bottom:4px;">Live Preview (best effort)</div>
<textarea id="planner-stream-preview" class="openclaw-textarea openclaw-textarea moltbot-textarea" style="min-height:70px;" readonly></textarea>
<textarea id="planner-stream-preview" class="openclaw-textarea" style="min-height:70px;" readonly></textarea>
</div>
<button id="planner-cancel-btn" class="openclaw-btn openclaw-btn moltbot-btn" style="margin-top: 8px; width: 100%; background: var(--input-background); border: 1px solid var(--border-color);">Cancel</button>
<button id="planner-cancel-btn" class="openclaw-btn" style="margin-top: 8px; width: 100%; background: var(--input-background); border: 1px solid var(--border-color);">Cancel</button>
</div>
<button id="planner-run-btn" class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-primary openclaw-btn-primary moltbot-btn-primary">Plan Generation</button>
<button id="planner-run-btn" class="openclaw-btn openclaw-btn-primary">Plan Generation</button>
</div>
<div id="planner-results" style="display:none;" class="openclaw-split-v openclaw-split-v moltbot-split-v">
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Plan Output</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Positive</label>
<textarea id="planner-out-pos" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-md openclaw-textarea-md moltbot-textarea-md" readonly></textarea>
<div id="planner-results" style="display:none;" class="openclaw-split-v">
<div class="openclaw-card">
<div class="openclaw-section-header">Plan Output</div>
<div class="openclaw-input-group">
<label class="openclaw-label">Positive</label>
<textarea id="planner-out-pos" class="openclaw-textarea openclaw-textarea-md" readonly></textarea>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Negative</label>
<textarea id="planner-out-neg" class="openclaw-textarea openclaw-textarea moltbot-textarea" rows="2" readonly></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Negative</label>
<textarea id="planner-out-neg" class="openclaw-textarea" rows="2" readonly></textarea>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Params (JSON)</label>
<textarea id="planner-out-params" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-md openclaw-textarea-md moltbot-textarea-md" readonly></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Params (JSON)</label>
<textarea id="planner-out-params" class="openclaw-textarea openclaw-textarea-md" readonly></textarea>
</div>
</div>
</div>
+33 -33
View File
@@ -8,36 +8,36 @@ export const RefinerTab = {
render(container) {
container.innerHTML = `
<div class="openclaw-panel openclaw-panel moltbot-panel">
<div class="openclaw-scroll-area openclaw-scroll-area moltbot-scroll-area">
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Source Context</div>
<div class="openclaw-panel">
<div class="openclaw-scroll-area">
<div class="openclaw-card">
<div class="openclaw-section-header">Source Context</div>
<div class="openclaw-error-box openclaw-error-box moltbot-error-box" style="display:none"></div>
<div class="openclaw-error-box" style="display:none"></div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Source Image</label>
<div class="openclaw-input-group">
<label class="openclaw-label">Source Image</label>
<div style="display:flex; gap:10px; align-items:center;">
<input type="file" id="refiner-img-upload" class="openclaw-input openclaw-input moltbot-input" accept="image/png, image/jpeg">
<input type="file" id="refiner-img-upload" class="openclaw-input" accept="image/png, image/jpeg">
<img id="refiner-img-preview" style="height:40px; border-radius:4px; display:none; border:1px solid #444;">
</div>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Original Positive</label>
<textarea id="refiner-orig-pos" class="openclaw-textarea openclaw-textarea moltbot-textarea"></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Original Positive</label>
<textarea id="refiner-orig-pos" class="openclaw-textarea"></textarea>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Original Negative</label>
<textarea id="refiner-orig-neg" class="openclaw-textarea openclaw-textarea moltbot-textarea" rows="2"></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Original Negative</label>
<textarea id="refiner-orig-neg" class="openclaw-textarea" rows="2"></textarea>
</div>
</div>
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Goal / Issue</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<textarea id="refiner-issue" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-sm openclaw-textarea-sm moltbot-textarea-sm" placeholder="What's wrong? or What to change?"></textarea>
<div class="openclaw-card">
<div class="openclaw-section-header">Goal / Issue</div>
<div class="openclaw-input-group">
<textarea id="refiner-issue" class="openclaw-textarea openclaw-textarea-sm" placeholder="What's wrong? or What to change?"></textarea>
</div>
<!-- R38-Lite: Loading state container -->
@@ -51,29 +51,29 @@ export const RefinerTab = {
</div>
<div style="margin-top:8px;">
<div style="font-size:0.85em; opacity:0.8; margin-bottom:4px;">Live Preview (best effort)</div>
<textarea id="refiner-stream-preview" class="openclaw-textarea openclaw-textarea moltbot-textarea" style="min-height:70px;" readonly></textarea>
<textarea id="refiner-stream-preview" class="openclaw-textarea" style="min-height:70px;" readonly></textarea>
</div>
<button id="refiner-cancel-btn" class="openclaw-btn openclaw-btn moltbot-btn" style="margin-top: 8px; width: 100%; background: var(--input-background); border: 1px solid var(--border-color);">Cancel</button>
<button id="refiner-cancel-btn" class="openclaw-btn" style="margin-top: 8px; width: 100%; background: var(--input-background); border: 1px solid var(--border-color);">Cancel</button>
</div>
<button id="refiner-run-btn" class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-primary openclaw-btn-primary moltbot-btn-primary">Refine Prompts</button>
<button id="refiner-run-btn" class="openclaw-btn openclaw-btn-primary">Refine Prompts</button>
</div>
<div id="refiner-results" style="display:none;" class="openclaw-split-v openclaw-split-v moltbot-split-v">
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Refinement</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Rationale</label>
<div id="refiner-rationale" class="openclaw-markdown-box openclaw-markdown-box moltbot-markdown-box"></div>
<div id="refiner-results" style="display:none;" class="openclaw-split-v">
<div class="openclaw-card">
<div class="openclaw-section-header">Refinement</div>
<div class="openclaw-input-group">
<label class="openclaw-label">Rationale</label>
<div id="refiner-rationale" class="openclaw-markdown-box"></div>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">New Positive</label>
<textarea id="refiner-new-pos" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-md openclaw-textarea-md moltbot-textarea-md"></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">New Positive</label>
<textarea id="refiner-new-pos" class="openclaw-textarea openclaw-textarea-md"></textarea>
</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">New Negative</label>
<textarea id="refiner-new-neg" class="openclaw-textarea openclaw-textarea moltbot-textarea" rows="2"></textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">New Negative</label>
<textarea id="refiner-new-neg" class="openclaw-textarea" rows="2"></textarea>
</div>
</div>
</div>
+21 -21
View File
@@ -7,42 +7,42 @@ export const VariantsTab = {
render(container) {
container.innerHTML = `
<div class="openclaw-panel openclaw-panel moltbot-panel">
<div class="openclaw-scroll-area openclaw-scroll-area moltbot-scroll-area">
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Variants Configuration</div>
<div class="openclaw-panel">
<div class="openclaw-scroll-area">
<div class="openclaw-card">
<div class="openclaw-section-header">Variants Configuration</div>
<div class="openclaw-error-box openclaw-error-box moltbot-error-box" style="display:none"></div>
<div class="openclaw-error-box" style="display:none"></div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Base Parameters (JSON)</label>
<textarea id="var-base-params" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-md openclaw-textarea-md moltbot-textarea-md">{"width": 1024, "height": 1024, "seed": 0}</textarea>
<div class="openclaw-input-group">
<label class="openclaw-label">Base Parameters (JSON)</label>
<textarea id="var-base-params" class="openclaw-textarea openclaw-textarea-md">{"width": 1024, "height": 1024, "seed": 0}</textarea>
</div>
<div class="openclaw-grid-2 openclaw-grid-2 moltbot-grid-2">
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Strategy</label>
<select id="var-strategy" class="openclaw-select openclaw-select moltbot-select">
<div class="openclaw-grid-2">
<div class="openclaw-input-group">
<label class="openclaw-label">Strategy</label>
<select id="var-strategy" class="openclaw-select">
<option value="seeds">Seed Sweep (Count)</option>
<option value="cfg">CFG Scale (Range)</option>
</select>
</div>
<!-- Dynamic inputs based on strategy -->
<div id="var-opts-seeds" class="var-opts openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Count</label>
<input type="number" id="var-seed-count" class="openclaw-input openclaw-input moltbot-input" value="4" min="1" max="100">
<div id="var-opts-seeds" class="var-opts openclaw-input-group">
<label class="openclaw-label">Count</label>
<input type="number" id="var-seed-count" class="openclaw-input" value="4" min="1" max="100">
</div>
</div>
<button id="var-run-btn" class="openclaw-btn openclaw-btn moltbot-btn openclaw-btn-primary openclaw-btn-primary moltbot-btn-primary">Generate Variants JSON</button>
<button id="var-run-btn" class="openclaw-btn openclaw-btn-primary">Generate Variants JSON</button>
</div>
<div class="openclaw-card openclaw-card moltbot-card">
<div class="openclaw-section-header openclaw-section-header moltbot-section-header">Resulting List</div>
<div class="openclaw-input-group openclaw-input-group moltbot-input-group">
<label class="openclaw-label openclaw-label moltbot-label">Output (List of Params)</label>
<textarea id="var-output" class="openclaw-textarea openclaw-textarea moltbot-textarea openclaw-textarea-lg openclaw-textarea-lg moltbot-textarea-lg" readonly></textarea>
<div class="openclaw-card">
<div class="openclaw-section-header">Resulting List</div>
<div class="openclaw-input-group">
<label class="openclaw-label">Output (List of Params)</label>
<textarea id="var-output" class="openclaw-textarea openclaw-textarea-lg" readonly></textarea>
</div>
</div>
</div>
+22
View File
@@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest";
import {
applyLegacyClassAliases,
buildLegacyAliasClassTokens,
makeEl,
normalizeLegacyClassTokens,
normalizeLegacyClassNames,
@@ -34,6 +36,26 @@ describe("openclaw_utils", () => {
expect(root.querySelector("button").className).toBe("openclaw-btn openclaw-btn-primary");
});
it("derives runtime legacy aliases from canonical tokens", () => {
expect(buildLegacyAliasClassTokens("openclaw-panel openclaw-btn")).toBe(
"openclaw-panel openclaw-btn moltbot-panel moltbot-btn"
);
});
it("applies runtime legacy aliases across a subtree", () => {
document.body.innerHTML = `
<section class="openclaw-panel">
<button class="openclaw-btn openclaw-btn-primary">Run</button>
</section>
`;
const root = document.body.firstElementChild;
applyLegacyClassAliases(root);
expect(root.className).toBe("openclaw-panel moltbot-panel");
expect(root.querySelector("button").className).toBe(
"openclaw-btn openclaw-btn-primary moltbot-btn moltbot-btn-primary"
);
});
it("returns fallback data for invalid JSON", () => {
const parsed = parseJsonSafe("{bad", { safe: true });
expect(parsed.ok).toBe(false);