feat(#5427): add preset cron schedule builder
This commit is contained in:
1 parent
7ee6a12e44
commit
cc7c3f9586
3 files changed
+318
-12
No files matched your search
@@ -1583,6 +1583,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Name',
|
||||
cron_name_placeholder: 'Optional',
|
||||
cron_schedule_label: 'Schedule',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -3240,6 +3247,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Nome',
|
||||
cron_name_placeholder: 'Opzionale',
|
||||
cron_schedule_label: 'Pianificazione',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: 'Usa \'every 1h\' o un\'espressione cron per job ricorrenti. Durate semplici come \'30m\' vengono eseguite una volta sola.',
|
||||
cron_schedule_once_warning: 'Forme di durata come \'30m\' vengono eseguite una volta e rimosse dopo l\'esecuzione. Usa \'every 30m\' per mantenere un job ricorrente.',
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -4905,6 +4919,13 @@ const LOCALES = {
|
||||
cron_name_label: '名前',
|
||||
cron_name_placeholder: '任意',
|
||||
cron_schedule_label: 'スケジュール',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "繰り返し実行には 'every 1h' または Cron 式を使います。'30m' のような期間だけの指定は 1 回だけ実行されます。",
|
||||
cron_schedule_once_warning: "'30m' のような期間指定は 1 回だけ実行され、実行後に削除されます。繰り返すには 'every 30m' を使ってください。",
|
||||
cron_prompt_label: 'プロンプト',
|
||||
@@ -6212,6 +6233,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Имя',
|
||||
cron_name_placeholder: 'Необязательно',
|
||||
cron_schedule_label: 'Расписание',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Для повторяющихся заданий используйте 'every 1h' или cron-выражение. Простые интервалы вроде '30m' выполняются один раз.",
|
||||
cron_schedule_once_warning: "Интервалы вроде '30m' выполняются один раз и удаляются после запуска. Используйте 'every 30m' для повторяющегося задания.",
|
||||
cron_prompt_label: 'Запрос',
|
||||
@@ -7796,6 +7824,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Nombre',
|
||||
cron_name_placeholder: 'Opcional',
|
||||
cron_schedule_label: 'Programación',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Usa 'every 1h' o una expresión cron para trabajos recurrentes. Duraciones como '30m' se ejecutan una sola vez.",
|
||||
cron_schedule_once_warning: "Las duraciones como '30m' se ejecutan una vez y se eliminan después de correr. Usa 'every 30m' para mantener un trabajo recurrente.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -9062,6 +9097,13 @@ const LOCALES = {
|
||||
cron_duplicated: 'Aufgabe dupliziert (pausiert)',
|
||||
cron_name_placeholder: 'Optional',
|
||||
cron_schedule_label: 'Zeitplan',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Für wiederkehrende Aufgaben 'every 1h' oder einen Cron-Ausdruck verwenden. Reine Dauern wie '30m' laufen einmal.",
|
||||
cron_schedule_once_warning: "Dauerangaben wie '30m' laufen einmal und werden nach der Ausführung entfernt. Verwende 'every 30m' für eine wiederkehrende Aufgabe.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -11000,6 +11042,13 @@ const LOCALES = {
|
||||
cron_name_label: '名称',
|
||||
cron_name_placeholder: '可选',
|
||||
cron_schedule_label: '计划',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "循环任务请用 'every 1h' 或 Cron 表达式。像 '30m' 这样的裸时长只会运行一次。",
|
||||
cron_schedule_once_warning: "像 '30m' 这样的时长写法只会运行一次,并在运行后移除。要保留循环任务,请使用 'every 30m'。",
|
||||
cron_prompt_label: '提示词',
|
||||
@@ -12959,6 +13008,13 @@ const LOCALES = {
|
||||
cron_name_label: '任務名稱',
|
||||
cron_name_placeholder: '選填',
|
||||
cron_schedule_label: '排程',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: '週期性任務請用「every 1h」或 Cron 表示式。像「30m」這樣的裸時長只會執行一次。',
|
||||
cron_schedule_once_warning: '像「30m」這樣的時長寫法只會執行一次,並在執行後移除。要保留週期性任務,請使用「every 30m」。',
|
||||
cron_prompt_label: '提示',
|
||||
@@ -14357,6 +14413,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Nome',
|
||||
cron_name_placeholder: 'Opcional',
|
||||
cron_schedule_label: 'Agendamento',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Use 'every 1h' ou uma expressão Cron para tarefas recorrentes. Durações como '30m' rodam uma vez.",
|
||||
cron_schedule_once_warning: "Durações como '30m' rodam uma vez e são removidas após executar. Use 'every 30m' para manter uma tarefa recorrente.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -15998,6 +16061,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Name',
|
||||
cron_name_placeholder: 'Optional',
|
||||
cron_schedule_label: 'Schedule',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
@@ -17731,6 +17801,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Nom',
|
||||
cron_name_placeholder: 'Facultatif',
|
||||
cron_schedule_label: 'Planification',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Utilisez 'every 1h' ou une expression cron pour les tâches récurrentes. Les durées simples comme '30m' s'exécutent une fois.",
|
||||
cron_schedule_once_warning: "Les formes de durée comme '30m' s'exécutent une fois et sont supprimées après l'exécution. Utilisez 'every 30m' pour conserver une tâche récurrente.",
|
||||
cron_prompt_label: 'Invite',
|
||||
@@ -19296,6 +19373,13 @@ const LOCALES = {
|
||||
cron_name_label: 'İsim',
|
||||
cron_name_placeholder: 'İsteğe bağlı',
|
||||
cron_schedule_label: 'Takvim',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Use 'every 1h' or a cron expression for recurring jobs. Bare durations like '30m' run once.",
|
||||
cron_schedule_once_warning: "Duration forms like '30m' run once and are removed after running. Use 'every 30m' to keep a recurring job.",
|
||||
cron_prompt_label: 'Çabuk',
|
||||
@@ -21041,6 +21125,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Nazwa',
|
||||
cron_name_placeholder: 'Opcjonalnie',
|
||||
cron_schedule_label: 'Harmonogram',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Użyj 'every 1h' lub wyrażenia cron dla zadań cyklicznych. Same formy czasu trwania, jak '30m', zostaną uruchomione tylko raz.",
|
||||
cron_schedule_once_warning: 'Formy czasu trwania, takie jak \'30m\', są uruchamiane raz i usuwane po uruchomieniu. Użyj \'every 30m\', aby utrzymać zadanie cykliczne.',
|
||||
cron_prompt_label: 'Monit',
|
||||
@@ -22540,6 +22631,13 @@ const LOCALES = {
|
||||
cron_name_label: 'Tên',
|
||||
cron_name_placeholder: 'Tùy chọn',
|
||||
cron_schedule_label: 'Lịch chạy',
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
cron_schedule_hint: "Dùng 'every 1h' hoặc biểu thức cron cho job lặp lại. Các khoảng thời gian đơn như '30m' chỉ chạy một lần.",
|
||||
cron_schedule_once_warning: "Các dạng thời lượng như '30m' chỉ chạy một lần và sẽ bị xóa sau khi chạy. Dùng 'every 30m' để giữ job lặp lại.",
|
||||
cron_prompt_label: 'Prompt',
|
||||
|
||||
+61
-6
@@ -466,6 +466,60 @@ function _syncCronScheduleWarning() {
|
||||
warning.style.display = _cronScheduleKindForInput(input.value) === 'once' ? '' : 'none';
|
||||
}
|
||||
|
||||
const CRON_SCHEDULE_PRESETS = [
|
||||
{ id: 'custom', value: '', label: 'cron_schedule_preset_custom', fallback: 'Custom' },
|
||||
{ id: 'hourly', value: 'every 1h', label: 'cron_schedule_preset_hourly', fallback: 'Hourly' },
|
||||
{ id: 'daily', value: '0 9 * * *', label: 'cron_schedule_preset_daily', fallback: 'Daily' },
|
||||
{ id: 'weekdays', value: '0 9 * * 1-5', label: 'cron_schedule_preset_weekdays', fallback: 'Weekdays' },
|
||||
{ id: 'weekly', value: '0 9 * * 1', label: 'cron_schedule_preset_weekly', fallback: 'Weekly' },
|
||||
{ id: 'monthly', value: '0 9 1 * *', label: 'cron_schedule_preset_monthly', fallback: 'Monthly' },
|
||||
];
|
||||
|
||||
function _cronSchedulePresetOptionHtml() {
|
||||
return CRON_SCHEDULE_PRESETS
|
||||
.map((preset) => `<option value="${preset.id}">${esc(t(preset.label) || preset.fallback)}</option>`)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function _cronSchedulePresetIdForValue(value) {
|
||||
const schedule = String(value || '').trim();
|
||||
const preset = CRON_SCHEDULE_PRESETS.find((entry) => entry.value && entry.value === schedule);
|
||||
return preset ? preset.id : 'custom';
|
||||
}
|
||||
|
||||
function _syncCronSchedulePresetFromInput() {
|
||||
const presetEl = $('cronFormSchedulePreset');
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
const presetId = _cronSchedulePresetIdForValue(scheduleEl.value);
|
||||
presetEl.value = presetId;
|
||||
}
|
||||
|
||||
function _syncCronSchedulePresetAndWarning() {
|
||||
_syncCronSchedulePresetFromInput();
|
||||
_syncCronScheduleWarning();
|
||||
}
|
||||
|
||||
function _applyCronSchedulePresetSelection() {
|
||||
const presetEl = $('cronFormSchedulePreset');
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
const presetId = presetEl.value;
|
||||
const preset = CRON_SCHEDULE_PRESETS.find((entry) => entry.id === presetId);
|
||||
if (preset && preset.value) scheduleEl.value = preset.value;
|
||||
_syncCronSchedulePresetAndWarning();
|
||||
}
|
||||
|
||||
function _initCronSchedulePresetControls() {
|
||||
const presetEl = $('cronFormSchedulePreset');
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (!presetEl || !scheduleEl) return;
|
||||
presetEl.addEventListener('change', _applyCronSchedulePresetSelection);
|
||||
scheduleEl.addEventListener('input', _syncCronSchedulePresetAndWarning);
|
||||
scheduleEl.addEventListener('change', _syncCronSchedulePresetAndWarning);
|
||||
_syncCronSchedulePresetAndWarning();
|
||||
}
|
||||
|
||||
function _hasUnlimitedRepeat(job) {
|
||||
return !!(job && job.repeat && job.repeat.times == null);
|
||||
}
|
||||
@@ -1311,6 +1365,12 @@ function _renderCronForm({ name, schedule, prompt, deliver, profile, toast_notif
|
||||
<label for="cronFormName">${esc(t('cron_name_label') || 'Name')}</label>
|
||||
<input type="text" id="cronFormName" value="${esc(name || '')}" placeholder="${esc(t('cron_name_placeholder') || 'Optional')}" autocomplete="off">
|
||||
</div>
|
||||
<div class="detail-form-row">
|
||||
<label for="cronFormSchedulePreset">${esc(t('cron_schedule_preset_label') || 'Preset')}</label>
|
||||
<select id="cronFormSchedulePreset">
|
||||
${_cronSchedulePresetOptionHtml()}
|
||||
</select>
|
||||
</div>
|
||||
<div class="detail-form-row">
|
||||
<label for="cronFormSchedule">${esc(t('cron_schedule_label') || 'Schedule')}</label>
|
||||
<input type="text" id="cronFormSchedule" value="${esc(schedule || '')}" placeholder="0 9 * * * — every 1h — @daily" autocomplete="off" required>
|
||||
@@ -1356,12 +1416,7 @@ function _renderCronForm({ name, schedule, prompt, deliver, profile, toast_notif
|
||||
_populateCronDeliverOptions(deliver, isEdit);
|
||||
_populateCronFormModelSelect(model, provider, isNoAgent);
|
||||
if (!isNoAgent) _renderCronSkillTags();
|
||||
const scheduleEl = $('cronFormSchedule');
|
||||
if (scheduleEl) {
|
||||
scheduleEl.addEventListener('input', _syncCronScheduleWarning);
|
||||
scheduleEl.addEventListener('change', _syncCronScheduleWarning);
|
||||
_syncCronScheduleWarning();
|
||||
}
|
||||
_initCronSchedulePresetControls();
|
||||
const focusEl = $('cronFormName');
|
||||
if (focusEl) focusEl.focus();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,17 @@ def _cron_schedule_source() -> str:
|
||||
return src[start:end]
|
||||
|
||||
|
||||
def _cron_schedule_save_source() -> str:
|
||||
src = PANELS_JS.read_text(encoding="utf-8")
|
||||
start = src.find("async function saveCronForm()")
|
||||
if start < 0:
|
||||
pytest.fail("saveCronForm is missing")
|
||||
end = src.find("// Back-compat aliases for any stale callers", start)
|
||||
if end < 0:
|
||||
end = len(src)
|
||||
return src[start:end]
|
||||
|
||||
|
||||
def _run_node(script: str) -> str:
|
||||
proc = subprocess.run(
|
||||
[NODE, "-e", script],
|
||||
@@ -66,17 +77,159 @@ console.log(JSON.stringify(cases));
|
||||
assert kinds["not_a_schedule"] == ""
|
||||
|
||||
|
||||
def test_cron_form_surfaces_one_shot_warning_copy_and_styles():
|
||||
def test_cron_schedule_preset_matching():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const cases = {
|
||||
hourly: _cronSchedulePresetIdForValue("every 1h"),
|
||||
daily: _cronSchedulePresetIdForValue("0 9 * * *"),
|
||||
weekdays: _cronSchedulePresetIdForValue("0 9 * * 1-5"),
|
||||
weekly: _cronSchedulePresetIdForValue("0 9 * * 1"),
|
||||
monthly: _cronSchedulePresetIdForValue("0 9 1 * *"),
|
||||
empty: _cronSchedulePresetIdForValue(""),
|
||||
trimMatch: _cronSchedulePresetIdForValue(" 0 9 * * * "),
|
||||
custom: _cronSchedulePresetIdForValue("0 9 * * * 0"),
|
||||
};
|
||||
console.log(JSON.stringify(cases));
|
||||
"""
|
||||
cases = json.loads(_run_node(script))
|
||||
|
||||
assert cases["hourly"] == "hourly"
|
||||
assert cases["daily"] == "daily"
|
||||
assert cases["weekdays"] == "weekdays"
|
||||
assert cases["weekly"] == "weekly"
|
||||
assert cases["monthly"] == "monthly"
|
||||
assert cases["empty"] == "custom"
|
||||
assert cases["trimMatch"] == "daily"
|
||||
assert cases["custom"] == "custom"
|
||||
|
||||
|
||||
def test_cron_schedule_preset_controls_sync_raw_and_preset_paths():
|
||||
script = _cron_schedule_source() + r"""
|
||||
const elements = {};
|
||||
function $(id) { return elements[id]; }
|
||||
function makeElement() {
|
||||
return {
|
||||
value: '',
|
||||
style: {},
|
||||
listeners: {},
|
||||
addEventListener(type, handler) {
|
||||
(this.listeners[type] || (this.listeners[type] = [])).push(handler);
|
||||
},
|
||||
dispatchEvent(eventType) {
|
||||
const handlers = this.listeners[eventType] || [];
|
||||
for (const handler of handlers) handler({ type: eventType, target: this });
|
||||
},
|
||||
};
|
||||
}
|
||||
function t(key) {
|
||||
const dict = {
|
||||
cron_schedule_preset_label: 'Preset',
|
||||
cron_schedule_preset_hourly: 'Hourly',
|
||||
cron_schedule_preset_daily: 'Daily',
|
||||
cron_schedule_preset_weekdays: 'Weekdays',
|
||||
cron_schedule_preset_weekly: 'Weekly',
|
||||
cron_schedule_preset_monthly: 'Monthly',
|
||||
cron_schedule_preset_custom: 'Custom',
|
||||
};
|
||||
return dict[key];
|
||||
}
|
||||
function esc(value) { return value == null ? '' : String(value); }
|
||||
|
||||
elements.cronFormSchedule = makeElement();
|
||||
elements.cronFormSchedulePreset = makeElement();
|
||||
elements.cronFormScheduleOnceWarning = { style: {} };
|
||||
|
||||
_initCronSchedulePresetControls();
|
||||
|
||||
elements.cronFormSchedule.value = "0 9 * * *";
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const selectAfterDaily = elements.cronFormSchedulePreset.value;
|
||||
const warningAfterDaily = elements.cronFormScheduleOnceWarning.style.display;
|
||||
|
||||
elements.cronFormSchedule.value = "30m";
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
const selectAfterOnce = elements.cronFormSchedulePreset.value;
|
||||
const warningAfterOnce = elements.cronFormScheduleOnceWarning.style.display;
|
||||
|
||||
elements.cronFormSchedulePreset.value = 'hourly';
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const presetWriteValue = elements.cronFormSchedule.value;
|
||||
const selectAfterPresetWrite = elements.cronFormSchedulePreset.value;
|
||||
|
||||
elements.cronFormSchedule.value = "advanced: cron expression";
|
||||
elements.cronFormSchedule.dispatchEvent('input');
|
||||
elements.cronFormSchedulePreset.value = "custom";
|
||||
elements.cronFormSchedulePreset.dispatchEvent('change');
|
||||
const preservedCustomValue = elements.cronFormSchedule.value;
|
||||
const selectAfterCustom = elements.cronFormSchedulePreset.value;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
selectAfterDaily,
|
||||
warningAfterDaily,
|
||||
selectAfterOnce,
|
||||
warningAfterOnce,
|
||||
presetWriteValue,
|
||||
selectAfterPresetWrite,
|
||||
preservedCustomValue,
|
||||
selectAfterCustom,
|
||||
}));
|
||||
"""
|
||||
result = json.loads(_run_node(script))
|
||||
|
||||
assert result["selectAfterDaily"] == "daily"
|
||||
assert result["warningAfterDaily"] == "none"
|
||||
assert result["selectAfterOnce"] == "custom"
|
||||
assert result["warningAfterOnce"] == ""
|
||||
assert result["presetWriteValue"] == "every 1h"
|
||||
assert result["selectAfterPresetWrite"] == "hourly"
|
||||
assert result["preservedCustomValue"] == "advanced: cron expression"
|
||||
assert result["selectAfterCustom"] == "custom"
|
||||
|
||||
|
||||
def test_cron_form_surfaces_one_shot_warning_copy_markers_and_preset_markup():
|
||||
panels = PANELS_JS.read_text(encoding="utf-8")
|
||||
style = STYLE_CSS.read_text(encoding="utf-8")
|
||||
i18n = I18N_JS.read_text(encoding="utf-8")
|
||||
|
||||
assert "id=\"cronFormScheduleOnceWarning\"" in panels
|
||||
assert "id=\"cronFormSchedulePreset\"" in panels
|
||||
assert "cron_schedule_once_warning" in panels
|
||||
assert "_syncCronScheduleWarning" in panels
|
||||
assert "addEventListener('input', _syncCronScheduleWarning" in panels
|
||||
assert "addEventListener('change', _syncCronScheduleWarning" in panels
|
||||
|
||||
assert "_cronSchedulePresetIdForValue" in panels
|
||||
assert "_cronSchedulePresetOptionHtml" in panels
|
||||
assert "_initCronSchedulePresetControls" in panels
|
||||
assert "addEventListener('input', _syncCronSchedulePresetAndWarning" in panels
|
||||
assert "addEventListener('change', _syncCronSchedulePresetAndWarning" in panels
|
||||
assert "addEventListener('change', _applyCronSchedulePresetSelection" in panels
|
||||
assert ".cron-once-warning" in style
|
||||
assert i18n.count("cron_schedule_once_warning") >= 9
|
||||
assert "id: 'hourly'" in panels
|
||||
assert "id: 'daily'" in panels
|
||||
assert "id: 'weekdays'" in panels
|
||||
assert "id: 'weekly'" in panels
|
||||
assert "id: 'monthly'" in panels
|
||||
assert "id: 'custom'" in panels
|
||||
assert "Duration forms like '30m' run once" in i18n
|
||||
|
||||
|
||||
def test_cron_form_save_payload_still_uses_visible_raw_schedule_only():
|
||||
save_block = _cron_schedule_save_source()
|
||||
panels = PANELS_JS.read_text(encoding="utf-8")
|
||||
|
||||
assert "cronFormSchedulePreset" not in save_block
|
||||
assert "const schedule=schEl.value.trim();" in save_block
|
||||
assert "const updates = {job_id: _editingCronId, schedule, profile: profile, toast_notifications: toastNotifications}" in panels
|
||||
|
||||
|
||||
def test_cron_form_i18n_has_preset_keys():
|
||||
i18n = I18N_JS.read_text(encoding="utf-8")
|
||||
required_keys = [
|
||||
"cron_schedule_preset_label",
|
||||
"cron_schedule_preset_hourly",
|
||||
"cron_schedule_preset_daily",
|
||||
"cron_schedule_preset_weekdays",
|
||||
"cron_schedule_preset_weekly",
|
||||
"cron_schedule_preset_monthly",
|
||||
"cron_schedule_preset_custom",
|
||||
]
|
||||
|
||||
for key in required_keys:
|
||||
assert i18n.count(key) >= 14
|
||||
Reference in new issue
Block a user