поправлены способ передачи таймеров в vMix
This commit is contained in:
@@ -688,7 +688,7 @@ def test_runtime_vmix_sequence_preserves_command_order(tmp_path: Path) -> None:
|
||||
"device_secret": "s" * 40,
|
||||
"device_name": "Sequence GFX",
|
||||
"hostname": "SEQUENCE-PC",
|
||||
"agent_version": "1.4.0",
|
||||
"agent_version": "1.3.0",
|
||||
"vmix": {"connected": True, "url": "http://127.0.0.1:8088/api/"},
|
||||
},
|
||||
)
|
||||
@@ -744,6 +744,65 @@ def test_runtime_vmix_sequence_preserves_command_order(tmp_path: Path) -> None:
|
||||
result = await task
|
||||
assert result["ok"] is True
|
||||
assert result["applied"] == 2
|
||||
assert result["transport"] == "legacy"
|
||||
assert [item["function"] for item in result["results"]] == ["SetCountdown", "StartCountdown"]
|
||||
|
||||
asyncio.run(scenario())
|
||||
|
||||
|
||||
def test_build90_runtime_shortcut_sequence_uses_one_batch_on_modern_agent(tmp_path: Path) -> None:
|
||||
database = LocalTestDatabase(tmp_path / "agent-runtime-batch.sqlite3")
|
||||
database.create_all()
|
||||
hub = VmixAgentHub(database) # type: ignore[arg-type]
|
||||
ws = FakeWebSocket()
|
||||
user = HockeyUser(id="92", login="operator92", display_name="operator92")
|
||||
|
||||
async def scenario() -> None:
|
||||
await hub.register(
|
||||
ws, # type: ignore[arg-type]
|
||||
{
|
||||
"device_id": "GFX-PC-RUNTIME-BATCH",
|
||||
"device_secret": "r" * 40,
|
||||
"device_name": "Runtime Batch GFX",
|
||||
"hostname": "RUNTIME-BATCH-PC",
|
||||
"agent_version": "1.4.0",
|
||||
"vmix": {"connected": True, "url": "http://127.0.0.1:8088/api/"},
|
||||
},
|
||||
)
|
||||
await hub.pair_device("GFX-PC-RUNTIME-BATCH", user)
|
||||
assigned = await hub.assign_match(
|
||||
wfl_user_id=user.id, tournament_external_id="1437", game_external_id="902919",
|
||||
)
|
||||
assert assigned is not None
|
||||
|
||||
task = asyncio.create_task(hub.run_vmix_sequence_for_user(
|
||||
user,
|
||||
[
|
||||
{"Function": "SetCountdown", "Input": "53", "SelectedName": "Clock.Text", "Value": "00:18:42"},
|
||||
{"Function": "StartCountdown", "Input": "53", "SelectedName": "Clock.Text"},
|
||||
{"Function": "OverlayInput2In", "Input": "53"},
|
||||
],
|
||||
))
|
||||
batch = None
|
||||
for _ in range(50):
|
||||
await asyncio.sleep(0)
|
||||
batch = next((item for item in reversed(ws.sent) if item.get("type") == "vmix.batch"), None)
|
||||
if batch is not None:
|
||||
break
|
||||
assert batch is not None
|
||||
assert [item["Function"] for item in batch["commands"]] == ["SetCountdown", "StartCountdown", "OverlayInput2In"]
|
||||
assert not [item for item in ws.sent if item.get("type") == "vmix.command"]
|
||||
await hub.receive_command_ack(
|
||||
"GFX-PC-RUNTIME-BATCH",
|
||||
{
|
||||
"type": "command.batch.ack", "request_id": batch["request_id"], "ok": True,
|
||||
"results": [{"ok": True}, {"ok": True}, {"ok": True}],
|
||||
},
|
||||
)
|
||||
result = await task
|
||||
assert result["ok"] is True
|
||||
assert result["transport"] == "batch"
|
||||
assert result["applied"] == 3
|
||||
assert result["overlay_state"]["overlays"]["2"]["input"] == "53"
|
||||
|
||||
asyncio.run(scenario())
|
||||
|
||||
@@ -88,4 +88,6 @@ def test_rebalance_sends_old_plate_out_before_new_plate_in():
|
||||
assert 'const outCommands = [];' in block
|
||||
assert 'const setCommands = [];' in block
|
||||
assert 'const inCommands = [];' in block
|
||||
assert 'const commands = [...outCommands, ...setCommands, ...inCommands];' in block
|
||||
assert 'const stopCommands = [];' in block
|
||||
assert 'const runCommands = [];' in block
|
||||
assert 'const commands = [...outCommands, ...stopCommands, ...setCommands, ...runCommands, ...inCommands];' in block
|
||||
|
||||
@@ -104,9 +104,10 @@ def test_shortcut_editor_layout_fixes_add_step_buttons_and_fullscreen_modal() ->
|
||||
assert "height: calc(100vh - 20px);" in css
|
||||
|
||||
|
||||
def test_hockey_timer_shortcut_supports_space_toggle_pause_and_text_mirror() -> None:
|
||||
def test_hockey_timer_shortcut_supports_native_countdown_and_legacy_text_mirror() -> None:
|
||||
app_js = Path("ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||
assert 'Function: "SuspendCountdown"' in app_js
|
||||
assert 'Function: "PauseCountdown"' in app_js
|
||||
assert 'Function: "StopCountdown"' in app_js
|
||||
assert 'hockey_timer_command' in app_js
|
||||
assert 'game_vmix_mode' in app_js
|
||||
assert 'game_vmix_selected_name' in app_js
|
||||
@@ -196,3 +197,61 @@ def test_overlay_group_repeat_uses_transition_out_not_all_off():
|
||||
assert 'Function: "OverlayInputAllOff"' not in js
|
||||
for layer in range(1, 5):
|
||||
assert f'Function: "OverlayInput{layer}Out"' in js
|
||||
|
||||
|
||||
def test_build90_migrates_old_text_timer_transport_to_native_countdown(tmp_path: Path) -> None:
|
||||
manager = UIBuilderManager(tmp_path, filename="ui.json")
|
||||
saved = manager.save({
|
||||
"version": 21,
|
||||
"project_name": "Hockey",
|
||||
"data_source": "hockey",
|
||||
"canvas": {"width": 1440, "height": 900},
|
||||
"tabs": [{"id": "main", "label": "Main"}],
|
||||
"components": [],
|
||||
"triggers": [],
|
||||
"shortcut_sequences": [{
|
||||
"id": "timers", "name": "Timers", "combo": "Space",
|
||||
"steps": [{
|
||||
"id": "sync", "type": "hockey_vmix_timers_start",
|
||||
"game_vmix_mode": "text", "penalty_vmix_mode": "text",
|
||||
}],
|
||||
}],
|
||||
})
|
||||
step = saved["shortcut_sequences"][0]["steps"][0]
|
||||
assert saved["version"] == 22
|
||||
assert step["game_vmix_mode"] == "countdown"
|
||||
assert step["penalty_vmix_mode"] == "countdown"
|
||||
|
||||
|
||||
def test_build90_keeps_explicit_legacy_text_mode_after_v22(tmp_path: Path) -> None:
|
||||
manager = UIBuilderManager(tmp_path, filename="ui.json")
|
||||
saved = manager.save({
|
||||
"version": 22,
|
||||
"project_name": "Hockey",
|
||||
"data_source": "hockey",
|
||||
"canvas": {"width": 1440, "height": 900},
|
||||
"tabs": [{"id": "main", "label": "Main"}],
|
||||
"components": [], "triggers": [],
|
||||
"shortcut_sequences": [{
|
||||
"id": "timers", "name": "Timers", "combo": "Space",
|
||||
"steps": [{
|
||||
"id": "sync", "type": "hockey_vmix_timers_start",
|
||||
"game_vmix_mode": "text", "penalty_vmix_mode": "text",
|
||||
}],
|
||||
}],
|
||||
})
|
||||
step = saved["shortcut_sequences"][0]["steps"][0]
|
||||
assert step["game_vmix_mode"] == "text"
|
||||
assert step["penalty_vmix_mode"] == "text"
|
||||
|
||||
|
||||
def test_build90_native_countdown_avoids_per_second_transport() -> None:
|
||||
app_js = Path("ui_builder/static/app.js").read_text(encoding="utf-8")
|
||||
assert 'game_vmix_mode !== "countdown"' in app_js
|
||||
assert 'const countdownMode = step.penalty_vmix_mode === "countdown"' in app_js
|
||||
assert 'Function: "PauseCountdown"' in app_js
|
||||
assert 'Function: "StopCountdown"' in app_js
|
||||
assert 'signal: controller.signal' in app_js
|
||||
assert 'vmixCommandQueue: Promise.resolve()' in app_js
|
||||
# Per-second pushes are restricted to the legacy Text mirror map.
|
||||
assert 'eventName === "timer_tick" && state.vmixTimerMirrors.has(component.action_id)' in app_js
|
||||
|
||||
Reference in New Issue
Block a user