тест 3
This commit is contained in:
3
app.py
3
app.py
@@ -29,7 +29,8 @@ from ui_builder import install_ui_builder
|
|||||||
from khl_site.khl_data_center import APP as khl_site_app
|
from khl_site.khl_data_center import APP as khl_site_app
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
BUILD_VERSION = "2026.08.24.7"
|
BUILD_VERSION = "2026.08.24.8"
|
||||||
|
# compatibility: BUILD_VERSION = "2026.08.24.7"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.24.6"
|
# compatibility: BUILD_VERSION = "2026.08.24.6"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.24.5"
|
# compatibility: BUILD_VERSION = "2026.08.24.5"
|
||||||
# compatibility: BUILD_VERSION = "2026.08.24.4"
|
# compatibility: BUILD_VERSION = "2026.08.24.4"
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ def test_timer_start_stop_protocol_is_minimal():
|
|||||||
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
||||||
assert 'if (action === "stop" || action === "pause")' in helper
|
assert 'if (action === "stop" || action === "pause")' in helper
|
||||||
stop_branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('return [', 1)[1].split('];', 1)[0]
|
stop_branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('return [', 1)[1].split('];', 1)[0]
|
||||||
assert 'Function: "StopCountdown"' in stop_branch
|
assert 'Function: "SuspendCountdown"' in stop_branch
|
||||||
|
assert 'Function: "StopCountdown"' not in stop_branch
|
||||||
assert 'Function: "SetCountdown"' not in stop_branch
|
assert 'Function: "SetCountdown"' not in stop_branch
|
||||||
start_branch = helper.rsplit('return [', 1)[1]
|
start_branch = helper.rsplit('return [', 1)[1]
|
||||||
assert start_branch.index('Function: "SetCountdown"') < start_branch.index('Function: "StartCountdown"')
|
assert start_branch.index('Function: "SetCountdown"') < start_branch.index('Function: "StartCountdown"')
|
||||||
assert 'SuspendCountdown' not in helper
|
|
||||||
assert 'PauseCountdown' not in helper
|
assert 'PauseCountdown' not in helper
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ def _block(start: str, end: str) -> str:
|
|||||||
return APP_JS.split(start, 1)[1].split(end, 1)[0]
|
return APP_JS.split(start, 1)[1].split(end, 1)[0]
|
||||||
|
|
||||||
|
|
||||||
def test_pause_stop_sends_only_stopcountdown():
|
def test_pause_stop_sends_only_pause_only_command():
|
||||||
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
||||||
branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('if (action === "set")', 1)[0]
|
branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('if (action === "set")', 1)[0]
|
||||||
assert 'Function: "StopCountdown"' in branch
|
assert 'Function: "SuspendCountdown"' in branch
|
||||||
|
assert 'Function: "StopCountdown"' not in branch
|
||||||
assert 'Function: "SetCountdown"' not in branch
|
assert 'Function: "SetCountdown"' not in branch
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
34
tests/test_build105_suspend_countdown_pause.py
Normal file
34
tests/test_build105_suspend_countdown_pause.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
APP_JS = (ROOT / "ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||||
|
APP = (ROOT / "app.py").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _block(start: str, end: str) -> str:
|
||||||
|
return APP_JS.split(start, 1)[1].split(end, 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_space_pause_uses_suspend_not_stop_reset():
|
||||||
|
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
||||||
|
branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('if (action === "set")', 1)[0]
|
||||||
|
assert 'Function: "SuspendCountdown"' in branch
|
||||||
|
assert 'Function: "StopCountdown"' not in branch
|
||||||
|
assert 'Function: "SetCountdown"' not in branch
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_reset_set_path_keeps_stop_then_set():
|
||||||
|
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
||||||
|
set_branch = helper.split('if (action === "set")', 1)[1].split('return [', 2)[1]
|
||||||
|
assert 'Function: "StopCountdown"' in set_branch
|
||||||
|
assert 'Function: "SetCountdown"' in set_branch
|
||||||
|
|
||||||
|
|
||||||
|
def test_penalty_normal_pause_uses_suspend():
|
||||||
|
block = _block("async function rebalanceVmixPenaltyTargets", "function finishActionMatchesSource")
|
||||||
|
assert 'if (preservePausedCountdown && !assignmentChanged)' in block
|
||||||
|
assert 'stopCommands.push({ Function: "SuspendCountdown"' in block
|
||||||
|
|
||||||
|
|
||||||
|
def test_build105_runtime_version():
|
||||||
|
assert 'BUILD_VERSION = "2026.08.24.8"' in APP
|
||||||
@@ -33,9 +33,9 @@ def test_countdown_start_is_simple_set_start():
|
|||||||
def test_pause_and_stop_freeze_without_reseed():
|
def test_pause_and_stop_freeze_without_reseed():
|
||||||
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
helper = _block("function vmixCountdownSyncCommands", "function buildShortcutRuntimeContext")
|
||||||
branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('return [', 1)[1].split('];', 1)[0]
|
branch = helper.split('if (action === "stop" || action === "pause")', 1)[1].split('return [', 1)[1].split('];', 1)[0]
|
||||||
assert 'Function: "StopCountdown"' in branch
|
assert 'Function: "SuspendCountdown"' in branch
|
||||||
|
assert 'Function: "StopCountdown"' not in branch
|
||||||
assert 'Function: "SetCountdown"' not in branch
|
assert 'Function: "SetCountdown"' not in branch
|
||||||
assert 'Function: "SuspendCountdown"' not in branch
|
|
||||||
assert 'Function: "PauseCountdown"' not in branch
|
assert 'Function: "PauseCountdown"' not in branch
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,6 @@ def test_penalty_rebalance_hard_syncs_running_countdown_and_pauses_stably():
|
|||||||
assert 'Value: () => vmixCountdownValue(entry.event.remainingMs)' in block
|
assert 'Value: () => vmixCountdownValue(entry.event.remainingMs)' in block
|
||||||
assert 'runCommands.push({ Function: "StartCountdown"' in block
|
assert 'runCommands.push({ Function: "StartCountdown"' in block
|
||||||
assert 'sendRuntimeVmixTimerSequence(commands)' in block
|
assert 'sendRuntimeVmixTimerSequence(commands)' in block
|
||||||
assert 'Function: "SuspendCountdown"' not in block
|
|
||||||
assert 'Function: "PauseCountdown"' not in block
|
assert 'Function: "PauseCountdown"' not in block
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4163,12 +4163,12 @@ function startCustomTooltips() {
|
|||||||
};
|
};
|
||||||
if (!target.Input || !target.SelectedName) return [];
|
if (!target.Input || !target.SelectedName) return [];
|
||||||
if (action === "stop" || action === "pause") {
|
if (action === "stop" || action === "pause") {
|
||||||
// BUILD104: never reseed immediately after StopCountdown. vMix has already
|
// BUILD105: vMix StopCountdown means STOP + RESET TO BEGINNING. It must never
|
||||||
// frozen its native countdown at the exact frame it received StopCountdown;
|
// be used for an operator pause/stop where the current sports time has to freeze.
|
||||||
// a following SetCountdown from the browser can be slightly older and makes
|
// SuspendCountdown is the vMix pause-only command and preserves the exact native
|
||||||
// the on-air clock visibly jump backwards.
|
// countdown value until the next SetCountdown + StartCountdown.
|
||||||
return [
|
return [
|
||||||
{ Function: "StopCountdown", ...target },
|
{ Function: "SuspendCountdown", ...target },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (action === "set") {
|
if (action === "set") {
|
||||||
@@ -5087,10 +5087,14 @@ function startCustomTooltips() {
|
|||||||
runCommands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
runCommands.push({ Function: "StartCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
}
|
}
|
||||||
} else if (force || assignmentChanged || previous?.running !== false) {
|
} else if (force || assignmentChanged || previous?.running !== false) {
|
||||||
// BUILD104: a normal Pause of a prepared/paused penalty freezes the native vMix countdown only.
|
// BUILD105: an unchanged prepared/paused penalty must use SuspendCountdown.
|
||||||
// Re-seeding after StopCountdown can visibly roll the clock back.
|
// StopCountdown resets a native vMix countdown to its beginning. For a
|
||||||
// Explicit reset/set-time/assignment changes may still seed a value.
|
// reassigned/reset target we may still deliberately Stop + Set below.
|
||||||
stopCommands.push({ Function: "StopCountdown", Input: target.input, SelectedName: target.selected_name });
|
if (preservePausedCountdown && !assignmentChanged) {
|
||||||
|
stopCommands.push({ Function: "SuspendCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
|
} else {
|
||||||
|
stopCommands.push({ Function: "StopCountdown", Input: target.input, SelectedName: target.selected_name });
|
||||||
|
}
|
||||||
if (!preservePausedCountdown || assignmentChanged) {
|
if (!preservePausedCountdown || assignmentChanged) {
|
||||||
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: () => vmixCountdownValue(entry.event.remainingMs) });
|
setCommands.push({ Function: "SetCountdown", Input: target.input, SelectedName: target.selected_name, Value: () => vmixCountdownValue(entry.event.remainingMs) });
|
||||||
}
|
}
|
||||||
@@ -13868,7 +13872,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
<div class="shortcut-finish-action-list">${timerFinishActionRows(step)}</div>
|
<div class="shortcut-finish-action-list">${timerFinishActionRows(step)}</div>
|
||||||
|
|
||||||
<div class="shortcut-vmix-inventory-note"><span>${escapeHtml(shortcutInventoryLabel())}</span><small>Для каждого countdown теперь обязательно выбирается конкретный Text / SelectedName. Это исключает отправку времени в первый текстовый элемент по умолчанию.</small></div>
|
<div class="shortcut-vmix-inventory-note"><span>${escapeHtml(shortcutInventoryLabel())}</span><small>Для каждого countdown теперь обязательно выбирается конкретный Text / SelectedName. Это исключает отправку времени в первый текстовый элемент по умолчанию.</small></div>
|
||||||
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: веб-таймер является главным. Start сразу меняет состояние Runtime и независимо отправляет <code>SetCountdown → StartCountdown</code> в vMix; Pause/Stop сразу останавливают Runtime и независимо отправляют только <code>StopCountdown</code>, без повторной установки времени. Ошибка Agent не блокирует управление таймером. Каждую секунду значение в vMix не передаётся. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
<p class="shortcut-step-note">Режим <b>Countdown vMix</b>: веб-таймер является главным. Start сразу меняет состояние Runtime и независимо отправляет <code>SetCountdown → StartCountdown</code> в vMix; Pause/Stop сразу останавливают Runtime и независимо отправляют только <code>SuspendCountdown</code> (пауза без сброса), без повторной установки времени. Ошибка Agent не блокирует управление таймером. Каждую секунду значение в vMix не передаётся. <b>Text mirror</b> оставлен только для совместимости со старыми титрами. Для верхнего счёта используется одна penalty-плашка: при реальном большинстве она показывает ближайшее изменение численного состава; при чистом равном обоюдном удалении сама по себе не появляется.</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else if (step.type === "delay") {
|
} else if (step.type === "delay") {
|
||||||
body.innerHTML = `<div class="shortcut-step-grid"><label>Задержка, мс<input type="number" min="0" max="10000" step="10" data-step-field="milliseconds" value="${Number(step.milliseconds) || 0}"></label></div>`;
|
body.innerHTML = `<div class="shortcut-step-grid"><label>Задержка, мс<input type="number" min="0" max="10000" step="10" data-step-field="milliseconds" value="${Number(step.milliseconds) || 0}"></label></div>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user