mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
test: merge cron view UI cases
This commit is contained in:
@@ -77,6 +77,12 @@ function createProps(overrides: Partial<CronProps> = {}): CronProps {
|
||||
};
|
||||
}
|
||||
|
||||
function getButtonByText(container: Element, text: string) {
|
||||
return Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === text,
|
||||
);
|
||||
}
|
||||
|
||||
describe("cron view", () => {
|
||||
it("shows all-job history mode by default", () => {
|
||||
const container = document.createElement("div");
|
||||
@@ -250,41 +256,36 @@ describe("cron view", () => {
|
||||
expect(container.textContent).not.toContain("Next 13");
|
||||
});
|
||||
|
||||
it("calls onJobsFiltersChange when schedule filter changes", () => {
|
||||
it("wires jobs filter changes and reset", () => {
|
||||
const container = document.createElement("div");
|
||||
const onJobsFiltersChange = vi.fn();
|
||||
const onJobsFiltersReset = vi.fn();
|
||||
render(renderCron(createProps({ onJobsFiltersChange })), container);
|
||||
|
||||
const select = container.querySelector('select[data-test-id="cron-jobs-schedule-filter"]');
|
||||
expect(select).not.toBeNull();
|
||||
if (!(select instanceof HTMLSelectElement)) {
|
||||
const scheduleSelect = container.querySelector(
|
||||
'select[data-test-id="cron-jobs-schedule-filter"]',
|
||||
);
|
||||
expect(scheduleSelect).not.toBeNull();
|
||||
if (!(scheduleSelect instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
select.value = "cron";
|
||||
select.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
scheduleSelect.value = "cron";
|
||||
scheduleSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
|
||||
expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsScheduleKindFilter: "cron" });
|
||||
});
|
||||
|
||||
it("calls onJobsFiltersChange when last-run filter changes", () => {
|
||||
const container = document.createElement("div");
|
||||
const onJobsFiltersChange = vi.fn();
|
||||
render(renderCron(createProps({ onJobsFiltersChange })), container);
|
||||
|
||||
const select = container.querySelector('select[data-test-id="cron-jobs-last-status-filter"]');
|
||||
expect(select).not.toBeNull();
|
||||
if (!(select instanceof HTMLSelectElement)) {
|
||||
const lastRunSelect = container.querySelector(
|
||||
'select[data-test-id="cron-jobs-last-status-filter"]',
|
||||
);
|
||||
expect(lastRunSelect).not.toBeNull();
|
||||
if (!(lastRunSelect instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
select.value = "error";
|
||||
select.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
lastRunSelect.value = "error";
|
||||
lastRunSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
|
||||
expect(onJobsFiltersChange).toHaveBeenCalledWith({ cronJobsLastStatusFilter: "error" });
|
||||
});
|
||||
|
||||
it("calls onJobsFiltersReset when reset button is clicked", () => {
|
||||
const container = document.createElement("div");
|
||||
const onJobsFiltersReset = vi.fn();
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
@@ -302,7 +303,7 @@ describe("cron view", () => {
|
||||
expect(onJobsFiltersReset).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("shows webhook delivery option in the form", () => {
|
||||
it("renders supported delivery options and normalizes stale announce selection", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
@@ -317,10 +318,7 @@ describe("cron view", () => {
|
||||
(opt.textContent ?? "").trim(),
|
||||
);
|
||||
expect(options).toContain("Webhook POST");
|
||||
});
|
||||
|
||||
it("normalizes stale announce selection in the form when unsupported", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
@@ -335,12 +333,12 @@ describe("cron view", () => {
|
||||
container,
|
||||
);
|
||||
|
||||
const options = Array.from(container.querySelectorAll("option")).map((opt) =>
|
||||
const normalizedOptions = Array.from(container.querySelectorAll("option")).map((opt) =>
|
||||
(opt.textContent ?? "").trim(),
|
||||
);
|
||||
expect(options).not.toContain("Announce summary (default)");
|
||||
expect(options).toContain("Webhook POST");
|
||||
expect(options).toContain("None (internal)");
|
||||
expect(normalizedOptions).not.toContain("Announce summary (default)");
|
||||
expect(normalizedOptions).toContain("Webhook POST");
|
||||
expect(normalizedOptions).toContain("None (internal)");
|
||||
expect(container.querySelector('input[placeholder="https://example.com/cron"]')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -386,9 +384,7 @@ describe("cron view", () => {
|
||||
container,
|
||||
);
|
||||
|
||||
const editButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Edit",
|
||||
);
|
||||
const editButton = getButtonByText(container, "Edit");
|
||||
expect(editButton).not.toBeUndefined();
|
||||
editButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(onEdit).toHaveBeenCalledWith(job);
|
||||
@@ -397,15 +393,13 @@ describe("cron view", () => {
|
||||
expect(container.textContent).toContain("Edit Job");
|
||||
expect(container.textContent).toContain("Save changes");
|
||||
|
||||
const cancelButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Cancel",
|
||||
);
|
||||
const cancelButton = getButtonByText(container, "Cancel");
|
||||
expect(cancelButton).not.toBeUndefined();
|
||||
cancelButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(onCancelEdit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders advanced controls for cron + agent payload + delivery", () => {
|
||||
it("renders cron form sections and toggles advanced controls by schedule", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
@@ -428,52 +422,29 @@ describe("cron view", () => {
|
||||
expect(container.textContent).toContain("Model");
|
||||
expect(container.textContent).toContain("Thinking");
|
||||
expect(container.textContent).toContain("Best effort delivery");
|
||||
});
|
||||
|
||||
it("groups stagger window and unit inside the same stagger row", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
form: {
|
||||
...DEFAULT_CRON_FORM,
|
||||
scheduleKind: "cron",
|
||||
payloadKind: "agentTurn",
|
||||
},
|
||||
}),
|
||||
),
|
||||
container,
|
||||
);
|
||||
|
||||
const staggerGroup = container.querySelector(".cron-stagger-group");
|
||||
expect(staggerGroup).not.toBeNull();
|
||||
expect(staggerGroup?.textContent).toContain("Stagger window");
|
||||
expect(staggerGroup?.textContent).toContain("Stagger unit");
|
||||
});
|
||||
|
||||
it("explains timeout blank behavior and shows cron jitter hint", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
form: {
|
||||
...DEFAULT_CRON_FORM,
|
||||
scheduleKind: "cron",
|
||||
payloadKind: "agentTurn",
|
||||
},
|
||||
}),
|
||||
),
|
||||
container,
|
||||
);
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"Optional. Leave blank to use the gateway default timeout behavior for this run.",
|
||||
);
|
||||
expect(container.textContent).toContain("Need jitter? Use Advanced");
|
||||
});
|
||||
|
||||
it("disables Agent ID when clear-agent is enabled", () => {
|
||||
const container = document.createElement("div");
|
||||
expect(container.textContent).toContain("Enabled");
|
||||
expect(container.textContent).toContain("Jobs");
|
||||
expect(container.textContent).toContain("Next wake");
|
||||
expect(container.textContent).toContain("Basics");
|
||||
expect(container.textContent).toContain("Schedule");
|
||||
expect(container.textContent).toContain("Execution");
|
||||
expect(container.textContent).toContain("Delivery");
|
||||
|
||||
const checkboxLabel = container.querySelector(".cron-checkbox");
|
||||
expect(checkboxLabel).not.toBeNull();
|
||||
const firstElement = checkboxLabel?.firstElementChild;
|
||||
expect(firstElement?.tagName.toLowerCase()).toBe("input");
|
||||
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
@@ -490,32 +461,7 @@ describe("cron view", () => {
|
||||
expect(agentInput).not.toBeNull();
|
||||
expect(agentInput instanceof HTMLInputElement).toBe(true);
|
||||
expect(agentInput instanceof HTMLInputElement ? agentInput.disabled : false).toBe(true);
|
||||
});
|
||||
|
||||
it("renders sectioned cron form layout", () => {
|
||||
const container = document.createElement("div");
|
||||
render(renderCron(createProps()), container);
|
||||
expect(container.textContent).toContain("Enabled");
|
||||
expect(container.textContent).toContain("Jobs");
|
||||
expect(container.textContent).toContain("Next wake");
|
||||
expect(container.textContent).toContain("Basics");
|
||||
expect(container.textContent).toContain("Schedule");
|
||||
expect(container.textContent).toContain("Execution");
|
||||
expect(container.textContent).toContain("Delivery");
|
||||
expect(container.textContent).toContain("Advanced");
|
||||
});
|
||||
|
||||
it("renders checkbox fields with input first for alignment", () => {
|
||||
const container = document.createElement("div");
|
||||
render(renderCron(createProps()), container);
|
||||
const checkboxLabel = container.querySelector(".cron-checkbox");
|
||||
expect(checkboxLabel).not.toBeNull();
|
||||
const firstElement = checkboxLabel?.firstElementChild;
|
||||
expect(firstElement?.tagName.toLowerCase()).toBe("input");
|
||||
});
|
||||
|
||||
it("hides cron-only advanced controls for non-cron schedules", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
@@ -535,7 +481,7 @@ describe("cron view", () => {
|
||||
expect(container.textContent).not.toContain("Best effort delivery");
|
||||
});
|
||||
|
||||
it("renders inline validation errors and disables submit when invalid", () => {
|
||||
it("renders inline validation errors, disabled submit, and required aria bindings", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
@@ -569,10 +515,7 @@ describe("cron view", () => {
|
||||
);
|
||||
expect(saveButton).not.toBeUndefined();
|
||||
expect(saveButton?.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("shows required legend and aria bindings for invalid required fields", () => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
@@ -609,7 +552,7 @@ describe("cron view", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("wires the Clone action from job rows", () => {
|
||||
it("wires job row actions and selects the row before acting", () => {
|
||||
const container = document.createElement("div");
|
||||
const onClone = vi.fn();
|
||||
const onLoadRuns = vi.fn();
|
||||
@@ -625,85 +568,67 @@ describe("cron view", () => {
|
||||
container,
|
||||
);
|
||||
|
||||
const cloneButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Clone",
|
||||
);
|
||||
const cloneButton = getButtonByText(container, "Clone");
|
||||
expect(cloneButton).not.toBeUndefined();
|
||||
cloneButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
expect(onClone).toHaveBeenCalledWith(job);
|
||||
expect(onLoadRuns).toHaveBeenCalledWith("job-clone");
|
||||
});
|
||||
|
||||
it("selects row when clicking Enable/Disable, Run, and Remove actions", () => {
|
||||
const container = document.createElement("div");
|
||||
const onToggle = vi.fn();
|
||||
const onRun = vi.fn();
|
||||
const onRemove = vi.fn();
|
||||
const onLoadRuns = vi.fn();
|
||||
const job = createJob("job-actions");
|
||||
const actionLoadRuns = vi.fn();
|
||||
const actionJob = createJob("job-actions");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
jobs: [job],
|
||||
jobs: [actionJob],
|
||||
onToggle,
|
||||
onRun,
|
||||
onRemove,
|
||||
onLoadRuns,
|
||||
onLoadRuns: actionLoadRuns,
|
||||
}),
|
||||
),
|
||||
container,
|
||||
);
|
||||
|
||||
const enableButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Disable",
|
||||
);
|
||||
const enableButton = getButtonByText(container, "Disable");
|
||||
expect(enableButton).not.toBeUndefined();
|
||||
enableButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
const runButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Run",
|
||||
);
|
||||
const runButton = getButtonByText(container, "Run");
|
||||
expect(runButton).not.toBeUndefined();
|
||||
runButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
const removeButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Remove",
|
||||
);
|
||||
const removeButton = getButtonByText(container, "Remove");
|
||||
expect(removeButton).not.toBeUndefined();
|
||||
removeButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
expect(onToggle).toHaveBeenCalledWith(job, false);
|
||||
expect(onRun).toHaveBeenCalledWith(job, "force");
|
||||
expect(onRemove).toHaveBeenCalledWith(job);
|
||||
expect(onLoadRuns).toHaveBeenCalledTimes(3);
|
||||
expect(onLoadRuns).toHaveBeenNthCalledWith(1, "job-actions");
|
||||
expect(onLoadRuns).toHaveBeenNthCalledWith(2, "job-actions");
|
||||
expect(onLoadRuns).toHaveBeenNthCalledWith(3, "job-actions");
|
||||
});
|
||||
expect(onToggle).toHaveBeenCalledWith(actionJob, false);
|
||||
expect(onRun).toHaveBeenCalledWith(actionJob, "force");
|
||||
expect(onRemove).toHaveBeenCalledWith(actionJob);
|
||||
expect(actionLoadRuns).toHaveBeenCalledTimes(3);
|
||||
expect(actionLoadRuns).toHaveBeenNthCalledWith(1, "job-actions");
|
||||
expect(actionLoadRuns).toHaveBeenNthCalledWith(2, "job-actions");
|
||||
expect(actionLoadRuns).toHaveBeenNthCalledWith(3, "job-actions");
|
||||
|
||||
it("wires Run if due action with due mode", () => {
|
||||
const container = document.createElement("div");
|
||||
const onRun = vi.fn();
|
||||
const onLoadRuns = vi.fn();
|
||||
const job = createJob("job-due");
|
||||
const onRunDue = vi.fn();
|
||||
const dueJob = createJob("job-due");
|
||||
render(
|
||||
renderCron(
|
||||
createProps({
|
||||
jobs: [job],
|
||||
onRun,
|
||||
onLoadRuns,
|
||||
jobs: [dueJob],
|
||||
onRun: onRunDue,
|
||||
}),
|
||||
),
|
||||
container,
|
||||
);
|
||||
|
||||
const runDueButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(btn) => btn.textContent?.trim() === "Run if due",
|
||||
);
|
||||
const runDueButton = getButtonByText(container, "Run if due");
|
||||
expect(runDueButton).not.toBeUndefined();
|
||||
runDueButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
|
||||
expect(onRun).toHaveBeenCalledWith(job, "due");
|
||||
expect(onRunDue).toHaveBeenCalledWith(dueJob, "due");
|
||||
});
|
||||
|
||||
it("renders suggestion datalists for agent/model/thinking/timezone", () => {
|
||||
|
||||
Reference in New Issue
Block a user