mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-17 10:06:18 +00:00
* feat(scheduler): add interval schedule type Allow scheduled tasks to fire every N seconds from last dispatch, not only wall-clock cron or a single run_at. Cadence is UTC now+N with no missed-beat catch-up, bounded by min_once_delay_seconds and 30 days. * fix(scheduler): let interval tasks create, edit, and keep next run Create/edit now keep every_seconds. Unchanged interval spec no longer resets next_run_at, including timezone-only PATCH. * fix(scheduler): keep non-minute intervals on edit Stop rounding every_seconds to whole minutes in the form. Values that are not whole minutes or hours now use a seconds unit so edit/duplicate round-trips the stored cadence instead of rewriting it and resetting next_run_at. Document that min_once_delay_seconds is also the interval floor. * fix(scheduler): clamp interval seconds to the default 60s floor The new seconds unit allowed 1–59, which the API rejects under the default min_once_delay_seconds. Clamp the form to >= 60 and show the floor next to the preview. Also mention interval in the scheduler field_doc, matching config.example.yaml. * fix(scheduler): do not clamp interval amount while typing Keystroke clamp made 90 become 9 -> 60, then 600, and backspace could not leave 60. Keep the raw field text and apply the 60s floor on blur and emit only. * test(scheduler): cover interval input editing * fix(frontend): preserve saved interval cadence until edited * style(tests): format scheduled task router tests --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { afterEach, describe, expect, test } from "@rstest/core";
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
|
|
import {
|
|
ScheduledTaskScheduleInput,
|
|
type ScheduleValue,
|
|
} from "@/components/workspace/scheduled-task-schedule-input";
|
|
import { I18nProvider } from "@/core/i18n/context";
|
|
|
|
afterEach(cleanup);
|
|
|
|
describe("ScheduledTaskScheduleInput", () => {
|
|
test.each([1, 30, 59])(
|
|
"preserves an existing %s-second interval until explicitly edited",
|
|
(everySeconds) => {
|
|
const emitted: ScheduleValue[] = [];
|
|
render(
|
|
<I18nProvider initialLocale="en-US">
|
|
<ScheduledTaskScheduleInput
|
|
initial={{
|
|
schedule_type: "interval",
|
|
schedule_spec: { every_seconds: everySeconds },
|
|
timezone: "UTC",
|
|
}}
|
|
onChange={(value) => emitted.push(value)}
|
|
/>
|
|
</I18nProvider>,
|
|
);
|
|
|
|
const amountInput = screen.getByRole<HTMLInputElement>("spinbutton");
|
|
expect(amountInput.value).toBe(String(everySeconds));
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(everySeconds);
|
|
|
|
fireEvent.focus(amountInput);
|
|
fireEvent.blur(amountInput);
|
|
expect(amountInput.value).toBe(String(everySeconds));
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(everySeconds);
|
|
|
|
fireEvent.change(amountInput, { target: { value: "9" } });
|
|
expect(amountInput.value).toBe("9");
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(60);
|
|
fireEvent.blur(amountInput);
|
|
expect(amountInput.value).toBe("60");
|
|
},
|
|
);
|
|
|
|
test("keeps interval text editable until blur applies the floor", () => {
|
|
const emitted: ScheduleValue[] = [];
|
|
|
|
render(
|
|
<I18nProvider initialLocale="en-US">
|
|
<ScheduledTaskScheduleInput
|
|
initial={{
|
|
schedule_type: "interval",
|
|
schedule_spec: { every_seconds: 90 },
|
|
timezone: "UTC",
|
|
}}
|
|
onChange={(value) => emitted.push(value)}
|
|
/>
|
|
</I18nProvider>,
|
|
);
|
|
|
|
const amountInput = screen.getByRole("spinbutton");
|
|
expect((amountInput as HTMLInputElement).value).toBe("90");
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(90);
|
|
|
|
fireEvent.change(amountInput, { target: { value: "9" } });
|
|
expect((amountInput as HTMLInputElement).value).toBe("9");
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(60);
|
|
|
|
fireEvent.change(amountInput, { target: { value: "" } });
|
|
expect((amountInput as HTMLInputElement).value).toBe("");
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(60);
|
|
|
|
fireEvent.blur(amountInput);
|
|
expect((amountInput as HTMLInputElement).value).toBe("60");
|
|
expect(emitted.at(-1)?.schedule_spec.every_seconds).toBe(60);
|
|
});
|
|
});
|