mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 11:31:18 +00:00
fix(ui): use clipboard fallback in setup wizards (#110139)
* fix(ui): use clipboard fallback in setup wizards * test(ui): verify wizard clipboard payloads Co-authored-by: WhatsSkiLL <284122573+IWhatsskill@users.noreply.github.com> --------- Co-authored-by: Codex <codex@openai.com> Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: WhatsSkiLL <284122573+IWhatsskill@users.noreply.github.com>
This commit is contained in:
69
ui/src/pages/channels/wizard-view.test.ts
Normal file
69
ui/src/pages/channels/wizard-view.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { nothing, render } from "lit";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { i18n } from "../../i18n/index.ts";
|
||||
import { renderChannelWizard } from "./wizard-view.ts";
|
||||
|
||||
describe("renderChannelWizard", () => {
|
||||
beforeEach(async () => {
|
||||
await i18n.setLocale("en");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const container of document.body.querySelectorAll("div")) {
|
||||
render(nothing, container);
|
||||
}
|
||||
document.body.replaceChildren();
|
||||
vi.unstubAllGlobals();
|
||||
delete (document as unknown as { execCommand?: unknown }).execCommand;
|
||||
});
|
||||
|
||||
it("copies setup text through the plain-HTTP clipboard fallback", async () => {
|
||||
vi.stubGlobal("navigator", {});
|
||||
let copiedText: string | undefined;
|
||||
const execCommand = vi.fn().mockImplementation(() => {
|
||||
copiedText = document.querySelector<HTMLTextAreaElement>("textarea")?.value;
|
||||
return true;
|
||||
});
|
||||
(document as unknown as { execCommand: typeof execCommand }).execCommand = execCommand;
|
||||
const container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
render(
|
||||
renderChannelWizard({
|
||||
wizard: {
|
||||
phase: "step",
|
||||
channel: null,
|
||||
step: {
|
||||
id: "copy-command",
|
||||
type: "note",
|
||||
message: "openclaw channels add",
|
||||
},
|
||||
stepIndex: 1,
|
||||
busy: false,
|
||||
validationError: null,
|
||||
},
|
||||
channelLabel: (channelId) => channelId,
|
||||
multiselectValues: [],
|
||||
onToggleMultiselect: vi.fn(),
|
||||
onAnswer: vi.fn(),
|
||||
onClose: vi.fn(),
|
||||
whatsappQrDataUrl: null,
|
||||
whatsappMessage: null,
|
||||
whatsappConnected: null,
|
||||
whatsappBusy: false,
|
||||
onWhatsAppStart: vi.fn(),
|
||||
onWhatsAppWait: vi.fn(),
|
||||
}),
|
||||
container,
|
||||
);
|
||||
|
||||
const copy = container.querySelector<HTMLButtonElement>(".channels-wizard__links button");
|
||||
expect(copy).not.toBeNull();
|
||||
copy?.click();
|
||||
|
||||
await vi.waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
|
||||
expect(copiedText).toBe("openclaw channels add");
|
||||
expect(document.querySelector("textarea")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import "@awesome.me/webawesome/dist/components/radio-group/radio-group.js";
|
||||
import { html, nothing, type TemplateResult } from "lit";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import "../../components/modal-dialog.ts";
|
||||
import { copyToClipboard } from "../../lib/clipboard.ts";
|
||||
import { channelDocsUrl, channelHubMeta, renderChannelArt } from "./hub-meta.ts";
|
||||
import type {
|
||||
ChannelWizardState,
|
||||
@@ -52,11 +53,7 @@ function renderNoteStep(step: ChannelWizardStep, props: ChannelWizardViewProps)
|
||||
${message
|
||||
? html`
|
||||
<div class="channels-wizard__links">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--sm"
|
||||
@click=${() => void navigator.clipboard?.writeText(message)}
|
||||
>
|
||||
<button type="button" class="btn btn--sm" @click=${() => void copyToClipboard(message)}>
|
||||
${t("channels.setup.copyText")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -139,6 +139,8 @@ describe("renderModelSetup", () => {
|
||||
render(nothing, container);
|
||||
}
|
||||
document.body.replaceChildren();
|
||||
vi.unstubAllGlobals();
|
||||
delete (document as unknown as { execCommand?: unknown }).execCommand;
|
||||
});
|
||||
|
||||
it("renders candidate, unavailable, sign-in, and manual sections", () => {
|
||||
@@ -336,6 +338,31 @@ describe("renderModelSetup", () => {
|
||||
expect(text(container)).toContain("Expires in 10 minutes");
|
||||
});
|
||||
|
||||
it("copies device codes through the plain-HTTP clipboard fallback", async () => {
|
||||
vi.stubGlobal("navigator", {});
|
||||
let copiedText: string | undefined;
|
||||
const execCommand = vi.fn().mockImplementation(() => {
|
||||
copiedText = document.querySelector<HTMLTextAreaElement>("textarea")?.value;
|
||||
return true;
|
||||
});
|
||||
(document as unknown as { execCommand: typeof execCommand }).execCommand = execCommand;
|
||||
const container = wizardStep({
|
||||
id: "device",
|
||||
type: "note",
|
||||
deviceCode: { code: "ABCD-EFGH" },
|
||||
});
|
||||
|
||||
const copy = Array.from(container.querySelectorAll<HTMLButtonElement>("button")).find(
|
||||
(button) => button.textContent?.trim() === "Copy",
|
||||
);
|
||||
expect(copy).toBeDefined();
|
||||
copy?.click();
|
||||
|
||||
await vi.waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
|
||||
expect(copiedText).toBe("ABCD-EFGH");
|
||||
expect(document.querySelector("textarea")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders sensitive text, select, and confirm steps", () => {
|
||||
const sensitive = wizardStep(
|
||||
{ id: "token", type: "text", sensitive: true, placeholder: "Paste token" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html, nothing, type TemplateResult } from "lit";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import "../../components/modal-dialog.ts";
|
||||
import { copyToClipboard } from "../../lib/clipboard.ts";
|
||||
import type { ModelSetupWizardState } from "./state.ts";
|
||||
|
||||
type WizardViewProps = {
|
||||
@@ -25,7 +26,7 @@ function renderDeviceCode(step: Extract<ModelSetupWizardState, { phase: "step" }
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--sm"
|
||||
@click=${() => void navigator.clipboard?.writeText(step.deviceCode!.code)}
|
||||
@click=${() => void copyToClipboard(step.deviceCode!.code)}
|
||||
>
|
||||
${t("modelSetup.wizard.copy")}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user