惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
J
Java Code Geeks
GbyAI
GbyAI
aimingoo的专栏
aimingoo的专栏
L
LangChain Blog
I
InfoQ
D
Docker
F
Fortinet All Blogs
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
月光博客
月光博客
B
Blog
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
罗磊的独立博客
博客园_首页
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog RSS Feed
IT之家
IT之家
V
V2EX

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
test: merge cron view UI cases · openclaw/openclaw@81d6cf9
steipete · 2026-04-18 · via Recent Commits to openclaw:main

@@ -77,6 +77,12 @@ function createProps(overrides: Partial<CronProps> = {}): CronProps {

7777

};

7878

}

797980+

function getButtonByText(container: Element, text: string) {

81+

return Array.from(container.querySelectorAll("button")).find(

82+

(btn) => btn.textContent?.trim() === text,

83+

);

84+

}

85+8086

describe("cron view", () => {

8187

it("shows all-job history mode by default", () => {

8288

const container = document.createElement("div");

@@ -250,41 +256,36 @@ describe("cron view", () => {

250256

expect(container.textContent).not.toContain("Next 13");

251257

});

252258253-

it("calls onJobsFiltersChange when schedule filter changes", () => {

259+

it("wires jobs filter changes and reset", () => {

254260

const container = document.createElement("div");

255261

const onJobsFiltersChange = vi.fn();

262+

const onJobsFiltersReset = vi.fn();

256263

render(renderCron(createProps({ onJobsFiltersChange })), container);

257264258-

const select = container.querySelector('select[data-test-id="cron-jobs-schedule-filter"]');

259-

expect(select).not.toBeNull();

260-

if (!(select instanceof HTMLSelectElement)) {

265+

const scheduleSelect = container.querySelector(

266+

'select[data-test-id="cron-jobs-schedule-filter"]',

267+

);

268+

expect(scheduleSelect).not.toBeNull();

269+

if (!(scheduleSelect instanceof HTMLSelectElement)) {

261270

return;

262271

}

263-

select.value = "cron";

264-

select.dispatchEvent(new Event("change", { bubbles: true }));

272+

scheduleSelect.value = "cron";

273+

scheduleSelect.dispatchEvent(new Event("change", { bubbles: true }));

265274266275

expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsScheduleKindFilter: "cron" });

267-

});

268-269-

it("calls onJobsFiltersChange when last-run filter changes", () => {

270-

const container = document.createElement("div");

271-

const onJobsFiltersChange = vi.fn();

272-

render(renderCron(createProps({ onJobsFiltersChange })), container);

273276274-

const select = container.querySelector('select[data-test-id="cron-jobs-last-status-filter"]');

275-

expect(select).not.toBeNull();

276-

if (!(select instanceof HTMLSelectElement)) {

277+

const lastRunSelect = container.querySelector(

278+

'select[data-test-id="cron-jobs-last-status-filter"]',

279+

);

280+

expect(lastRunSelect).not.toBeNull();

281+

if (!(lastRunSelect instanceof HTMLSelectElement)) {

277282

return;

278283

}

279-

select.value = "error";

280-

select.dispatchEvent(new Event("change", { bubbles: true }));

284+

lastRunSelect.value = "error";

285+

lastRunSelect.dispatchEvent(new Event("change", { bubbles: true }));

281286282287

expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsLastStatusFilter: "error" });

283-

});

284288285-

it("calls onJobsFiltersReset when reset button is clicked", () => {

286-

const container = document.createElement("div");

287-

const onJobsFiltersReset = vi.fn();

288289

render(

289290

renderCron(

290291

createProps({

@@ -302,7 +303,7 @@ describe("cron view", () => {

302303

expect(onJobsFiltersReset).toHaveBeenCalledTimes(1);

303304

});

304305305-

it("shows webhook delivery option in the form", () => {

306+

it("renders supported delivery options and normalizes stale announce selection", () => {

306307

const container = document.createElement("div");

307308

render(

308309

renderCron(

@@ -317,10 +318,7 @@ describe("cron view", () => {

317318

(opt.textContent ?? "").trim(),

318319

);

319320

expect(options).toContain("Webhook POST");

320-

});

321321322-

it("normalizes stale announce selection in the form when unsupported", () => {

323-

const container = document.createElement("div");

324322

render(

325323

renderCron(

326324

createProps({

@@ -335,12 +333,12 @@ describe("cron view", () => {

335333

container,

336334

);

337335338-

const options = Array.from(container.querySelectorAll("option")).map((opt) =>

336+

const normalizedOptions = Array.from(container.querySelectorAll("option")).map((opt) =>

339337

(opt.textContent ?? "").trim(),

340338

);

341-

expect(options).not.toContain("Announce summary (default)");

342-

expect(options).toContain("Webhook POST");

343-

expect(options).toContain("None (internal)");

339+

expect(normalizedOptions).not.toContain("Announce summary (default)");

340+

expect(normalizedOptions).toContain("Webhook POST");

341+

expect(normalizedOptions).toContain("None (internal)");

344342

expect(container.querySelector('input[placeholder="https://example.com/cron"]')).toBeNull();

345343

});

346344

@@ -386,9 +384,7 @@ describe("cron view", () => {

386384

container,

387385

);

388386389-

const editButton = Array.from(container.querySelectorAll("button")).find(

390-

(btn) => btn.textContent?.trim() === "Edit",

391-

);

387+

const editButton = getButtonByText(container, "Edit");

392388

expect(editButton).not.toBeUndefined();

393389

editButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

394390

expect(onEdit).toHaveBeenCalledWith(job);

@@ -397,15 +393,13 @@ describe("cron view", () => {

397393

expect(container.textContent).toContain("Edit Job");

398394

expect(container.textContent).toContain("Save changes");

399395400-

const cancelButton = Array.from(container.querySelectorAll("button")).find(

401-

(btn) => btn.textContent?.trim() === "Cancel",

402-

);

396+

const cancelButton = getButtonByText(container, "Cancel");

403397

expect(cancelButton).not.toBeUndefined();

404398

cancelButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

405399

expect(onCancelEdit).toHaveBeenCalledTimes(1);

406400

});

407401408-

it("renders advanced controls for cron + agent payload + delivery", () => {

402+

it("renders cron form sections and toggles advanced controls by schedule", () => {

409403

const container = document.createElement("div");

410404

render(

411405

renderCron(

@@ -428,52 +422,29 @@ describe("cron view", () => {

428422

expect(container.textContent).toContain("Model");

429423

expect(container.textContent).toContain("Thinking");

430424

expect(container.textContent).toContain("Best effort delivery");

431-

});

432-433-

it("groups stagger window and unit inside the same stagger row", () => {

434-

const container = document.createElement("div");

435-

render(

436-

renderCron(

437-

createProps({

438-

form: {

439-

...DEFAULT_CRON_FORM,

440-

scheduleKind: "cron",

441-

payloadKind: "agentTurn",

442-

},

443-

}),

444-

),

445-

container,

446-

);

447425448426

const staggerGroup = container.querySelector(".cron-stagger-group");

449427

expect(staggerGroup).not.toBeNull();

450428

expect(staggerGroup?.textContent).toContain("Stagger window");

451429

expect(staggerGroup?.textContent).toContain("Stagger unit");

452-

});

453-454-

it("explains timeout blank behavior and shows cron jitter hint", () => {

455-

const container = document.createElement("div");

456-

render(

457-

renderCron(

458-

createProps({

459-

form: {

460-

...DEFAULT_CRON_FORM,

461-

scheduleKind: "cron",

462-

payloadKind: "agentTurn",

463-

},

464-

}),

465-

),

466-

container,

467-

);

468-469430

expect(container.textContent).toContain(

470431

"Optional. Leave blank to use the gateway default timeout behavior for this run.",

471432

);

472433

expect(container.textContent).toContain("Need jitter? Use Advanced");

473-

});

474434475-

it("disables Agent ID when clear-agent is enabled", () => {

476-

const container = document.createElement("div");

435+

expect(container.textContent).toContain("Enabled");

436+

expect(container.textContent).toContain("Jobs");

437+

expect(container.textContent).toContain("Next wake");

438+

expect(container.textContent).toContain("Basics");

439+

expect(container.textContent).toContain("Schedule");

440+

expect(container.textContent).toContain("Execution");

441+

expect(container.textContent).toContain("Delivery");

442+443+

const checkboxLabel = container.querySelector(".cron-checkbox");

444+

expect(checkboxLabel).not.toBeNull();

445+

const firstElement = checkboxLabel?.firstElementChild;

446+

expect(firstElement?.tagName.toLowerCase()).toBe("input");

447+477448

render(

478449

renderCron(

479450

createProps({

@@ -490,32 +461,7 @@ describe("cron view", () => {

490461

expect(agentInput).not.toBeNull();

491462

expect(agentInput instanceof HTMLInputElement).toBe(true);

492463

expect(agentInput instanceof HTMLInputElement ? agentInput.disabled : false).toBe(true);

493-

});

494464495-

it("renders sectioned cron form layout", () => {

496-

const container = document.createElement("div");

497-

render(renderCron(createProps()), container);

498-

expect(container.textContent).toContain("Enabled");

499-

expect(container.textContent).toContain("Jobs");

500-

expect(container.textContent).toContain("Next wake");

501-

expect(container.textContent).toContain("Basics");

502-

expect(container.textContent).toContain("Schedule");

503-

expect(container.textContent).toContain("Execution");

504-

expect(container.textContent).toContain("Delivery");

505-

expect(container.textContent).toContain("Advanced");

506-

});

507-508-

it("renders checkbox fields with input first for alignment", () => {

509-

const container = document.createElement("div");

510-

render(renderCron(createProps()), container);

511-

const checkboxLabel = container.querySelector(".cron-checkbox");

512-

expect(checkboxLabel).not.toBeNull();

513-

const firstElement = checkboxLabel?.firstElementChild;

514-

expect(firstElement?.tagName.toLowerCase()).toBe("input");

515-

});

516-517-

it("hides cron-only advanced controls for non-cron schedules", () => {

518-

const container = document.createElement("div");

519465

render(

520466

renderCron(

521467

createProps({

@@ -535,7 +481,7 @@ describe("cron view", () => {

535481

expect(container.textContent).not.toContain("Best effort delivery");

536482

});

537483538-

it("renders inline validation errors and disables submit when invalid", () => {

484+

it("renders inline validation errors, disabled submit, and required aria bindings", () => {

539485

const container = document.createElement("div");

540486

render(

541487

renderCron(

@@ -569,10 +515,7 @@ describe("cron view", () => {

569515

);

570516

expect(saveButton).not.toBeUndefined();

571517

expect(saveButton?.disabled).toBe(true);

572-

});

573518574-

it("shows required legend and aria bindings for invalid required fields", () => {

575-

const container = document.createElement("div");

576519

render(

577520

renderCron(

578521

createProps({

@@ -609,7 +552,7 @@ describe("cron view", () => {

609552

);

610553

});

611554612-

it("wires the Clone action from job rows", () => {

555+

it("wires job row actions and selects the row before acting", () => {

613556

const container = document.createElement("div");

614557

const onClone = vi.fn();

615558

const onLoadRuns = vi.fn();

@@ -625,85 +568,67 @@ describe("cron view", () => {

625568

container,

626569

);

627570628-

const cloneButton = Array.from(container.querySelectorAll("button")).find(

629-

(btn) => btn.textContent?.trim() === "Clone",

630-

);

571+

const cloneButton = getButtonByText(container, "Clone");

631572

expect(cloneButton).not.toBeUndefined();

632573

cloneButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

633574

expect(onClone).toHaveBeenCalledWith(job);

634575

expect(onLoadRuns).toHaveBeenCalledWith("job-clone");

635-

});

636576637-

it("selects row when clicking Enable/Disable, Run, and Remove actions", () => {

638-

const container = document.createElement("div");

639577

const onToggle = vi.fn();

640578

const onRun = vi.fn();

641579

const onRemove = vi.fn();

642-

const onLoadRuns = vi.fn();

643-

const job = createJob("job-actions");

580+

const actionLoadRuns = vi.fn();

581+

const actionJob = createJob("job-actions");

644582

render(

645583

renderCron(

646584

createProps({

647-

jobs: [job],

585+

jobs: [actionJob],

648586

onToggle,

649587

onRun,

650588

onRemove,

651-

onLoadRuns,

589+

onLoadRuns: actionLoadRuns,

652590

}),

653591

),

654592

container,

655593

);

656594657-

const enableButton = Array.from(container.querySelectorAll("button")).find(

658-

(btn) => btn.textContent?.trim() === "Disable",

659-

);

595+

const enableButton = getButtonByText(container, "Disable");

660596

expect(enableButton).not.toBeUndefined();

661597

enableButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

662598663-

const runButton = Array.from(container.querySelectorAll("button")).find(

664-

(btn) => btn.textContent?.trim() === "Run",

665-

);

599+

const runButton = getButtonByText(container, "Run");

666600

expect(runButton).not.toBeUndefined();

667601

runButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

668602669-

const removeButton = Array.from(container.querySelectorAll("button")).find(

670-

(btn) => btn.textContent?.trim() === "Remove",

671-

);

603+

const removeButton = getButtonByText(container, "Remove");

672604

expect(removeButton).not.toBeUndefined();

673605

removeButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

674606675-

expect(onToggle).toHaveBeenCalledWith(job, false);

676-

expect(onRun).toHaveBeenCalledWith(job, "force");

677-

expect(onRemove).toHaveBeenCalledWith(job);

678-

expect(onLoadRuns).toHaveBeenCalledTimes(3);

679-

expect(onLoadRuns).toHaveBeenNthCalledWith(1, "job-actions");

680-

expect(onLoadRuns).toHaveBeenNthCalledWith(2, "job-actions");

681-

expect(onLoadRuns).toHaveBeenNthCalledWith(3, "job-actions");

682-

});

607+

expect(onToggle).toHaveBeenCalledWith(actionJob, false);

608+

expect(onRun).toHaveBeenCalledWith(actionJob, "force");

609+

expect(onRemove).toHaveBeenCalledWith(actionJob);

610+

expect(actionLoadRuns).toHaveBeenCalledTimes(3);

611+

expect(actionLoadRuns).toHaveBeenNthCalledWith(1, "job-actions");

612+

expect(actionLoadRuns).toHaveBeenNthCalledWith(2, "job-actions");

613+

expect(actionLoadRuns).toHaveBeenNthCalledWith(3, "job-actions");

683614684-

it("wires Run if due action with due mode", () => {

685-

const container = document.createElement("div");

686-

const onRun = vi.fn();

687-

const onLoadRuns = vi.fn();

688-

const job = createJob("job-due");

615+

const onRunDue = vi.fn();

616+

const dueJob = createJob("job-due");

689617

render(

690618

renderCron(

691619

createProps({

692-

jobs: [job],

693-

onRun,

694-

onLoadRuns,

620+

jobs: [dueJob],

621+

onRun: onRunDue,

695622

}),

696623

),

697624

container,

698625

);

699626700-

const runDueButton = Array.from(container.querySelectorAll("button")).find(

701-

(btn) => btn.textContent?.trim() === "Run if due",

702-

);

627+

const runDueButton = getButtonByText(container, "Run if due");

703628

expect(runDueButton).not.toBeUndefined();

704629

runDueButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));

705630706-

expect(onRun).toHaveBeenCalledWith(job, "due");

631+

expect(onRunDue).toHaveBeenCalledWith(dueJob, "due");

707632

});

708633709634

it("renders suggestion datalists for agent/model/thinking/timezone", () => {