P16-01-DISMISS: 一般従業員の通常操作での解雇(退職金を含む。P16-01 の補助の作業。原単位への加算 0)
This commit is contained in:
@@ -48,6 +48,7 @@ func _run() -> void:
|
||||
_test_defense_refusal()
|
||||
_test_commit_failure_rolls_back()
|
||||
_test_rescue_cleanup_failure_rolls_back()
|
||||
_test_rollback_branches()
|
||||
_test_old_save_gains_the_severance_account()
|
||||
await _test_dismiss_from_main()
|
||||
await process_frame
|
||||
@@ -853,13 +854,74 @@ func _test_rescue_cleanup_failure_rolls_back() -> void:
|
||||
_scenes.free_fixture(fixture)
|
||||
|
||||
|
||||
## F9: the game's own Runtime and Main. The staff panel shows the severance, the
|
||||
## reopened Task and the held wages before the press, dismisses through its buttons,
|
||||
## shows the result and a refusal that arrives after the press, and for every refusal
|
||||
## row (R1, R2, R3, R5, R7, R8, R9) keeps the button disabled with the reason and sends
|
||||
## nothing on a forced press (5.a, whole save bytes). Input route: the Buttons' pressed
|
||||
## signals and Main's key handler, as the other Main suites drive them; no OS or
|
||||
## viewport input is claimed.
|
||||
## SPEC 4.1 d, the guarded branches the other scenes do not fail. Each changes nothing
|
||||
## but the command record (5.c).
|
||||
## (1) The removal contract cannot release the held Task's Reservation although the
|
||||
## pre-check passes. Disclosed fixture: the release operation id is already bound
|
||||
## to another inventory reservation, so InventoryService refuses the release.
|
||||
## (2) The seat of a boarded rescue pair cannot be released. Disclosed fixture: the
|
||||
## medic's Path request was completed beforehand through the public PathService
|
||||
## call, a state the save check refuses.
|
||||
## (3) The severance cannot be posted after the dismissed carrier's seat was released,
|
||||
## so the Path request, the cabin seat and the pairing are restored with the rest.
|
||||
func _test_rollback_branches() -> void:
|
||||
var fixture := _scenes.new_fixture(16_150)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var minute: int = fixture[&"clock"].absolute_minute
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": &"tool_worker", &"role_id": &"worker", &"options": {&"state": StaffRuntime.State.AVAILABLE,
|
||||
&"current_cell": Vector3i(0, 0, WorldService.LAYER_FRONT), &"role_tags": [&"worker"], &"permitted_task_tags": [&"work"], &"daily_wage": 100}}).is_success()
|
||||
and _scenes.command(fixture, &"add_inventory_stack", {&"owner_inventory_id": &"staff.supplies", &"item_id": &"tool/snapshot_kit", &"quantity": 2}).is_success()
|
||||
and _scenes.command(fixture, &"create_staff_task", {&"task_id": &"tool_task", &"task_type": &"inspect", &"priority": 90, &"options": {
|
||||
&"target_cell": Vector3i(0, 0, WorldService.LAYER_FRONT), &"required_role_tags": [&"worker"], &"required_items": {&"tool/snapshot_kit": 1},
|
||||
&"task_tags": [&"work"], &"estimated_minutes": 10}}).is_success(), "a worker, two tools and a Task that needs one")
|
||||
fixture[&"clock"].advance_one_minute()
|
||||
minute = fixture[&"clock"].absolute_minute
|
||||
var task: StaffTask = runtime.staff_service.get_task(&"tool_task")
|
||||
_expect(task.state == StaffTask.State.WORKING and task.reservation_ids.size() == 1, "the worker holds the Task and its Reservation")
|
||||
var held: StringName = task.reservation_ids[0] if task.reservation_ids.size() == 1 else &""
|
||||
var bound: Dictionary = runtime.inventory_service.reserve_item(&"staff.supplies", &"tool/snapshot_kit", 1, &"p16.fixture", &"staff_task", minute, minute + 60,
|
||||
StringName("staff.task.release.%s.%s" % [task.task_id, held]))
|
||||
_expect(bool(bound.get(&"ok", false)) and bool(runtime.get_staff_dismissal_preview(&"tool_worker")[&"can_dismiss"]),
|
||||
"disclosed fixture: the release operation id is bound to another reservation; the pre-check still passes")
|
||||
var refused := _expect_executor_refusal(fixture, &"tool_worker", &"RESERVATION_RELEASE_FAILED", "(1) the removal contract's release fails")
|
||||
_expect(refused.message == "Staff removal could not release Task Reservations." and runtime.staff_service.has_staff(&"tool_worker")
|
||||
and runtime.inventory_service.get_reservation(held).is_active() and runtime.staff_service.get_task(&"tool_task").assigned_staff_id == &"tool_worker",
|
||||
"(1) the existing contract's text; the employee, the Task and its Reservation are as before")
|
||||
_scenes.free_fixture(fixture)
|
||||
for scene: Array in [["(2) the seat release fails", &"RESCUE_SEAT_RELEASE_FAILED"], ["(3) the severance fails after the seat release", &"SEVERANCE_POST_FAILED"]]:
|
||||
var rescue := _scenes.new_rescue_fixture()
|
||||
var rescue_runtime: Variant = rescue[&"runtime"]
|
||||
_expect(_scenes.advance_to_rescue_phase(rescue, "rescue_transport/MOVING/BOARDED/pair"), "%s: the pair rides the elevator" % scene[0])
|
||||
var trip: Dictionary = rescue_runtime.elevators.get_actor_trip(&"staff:medic")
|
||||
if scene[1] == &"RESCUE_SEAT_RELEASE_FAILED":
|
||||
_expect(rescue_runtime.path_service.complete_elevator_request(StringName(trip.get(&"connector_id", &"")), StringName(trip.get(&"request_id", &""))),
|
||||
"%s: disclosed fixture: the medic's Path request is completed beforehand" % scene[0])
|
||||
else:
|
||||
var next_id := int(rescue_runtime.create_snapshot()[&"systems"][&"runtime"][&"next_command_id"])
|
||||
_expect(rescue_runtime.economy_service.transfer(Scenes.PAYER, Scenes.FRONT_SALES, 1, rescue[&"clock"].absolute_minute, &"TRANSFER", &"p16.fixture",
|
||||
StringName("staff.severance.%d" % next_id)).is_success(), "%s: disclosed fixture: the dismissal's posting key is already used" % scene[0])
|
||||
_expect_executor_refusal(rescue, &"medic", scene[1], scene[0])
|
||||
var medic_trip: Dictionary = rescue_runtime.elevators.get_actor_trip(&"staff:medic")
|
||||
var patient_trip: Dictionary = rescue_runtime.elevators.get_actor_trip(&"staff:patient")
|
||||
_expect(rescue_runtime.staff_service.has_staff(&"medic") and medic_trip == trip and StringName(medic_trip.get(&"companion_id", &"")) == &"staff:patient"
|
||||
and StringName(patient_trip.get(&"companion_id", &"")) == &"staff:medic", "%s: the medic is still aboard with the patient" % scene[0])
|
||||
if scene[1] == &"SEVERANCE_POST_FAILED":
|
||||
var saved: Dictionary = rescue_runtime.create_snapshot()
|
||||
_expect(rescue_runtime.restore_snapshot(saved) and rescue_runtime.create_snapshot() == saved, "%s: the restored game saves and restores exactly" % scene[0])
|
||||
_scenes.free_fixture(rescue)
|
||||
|
||||
|
||||
## F9: the game's own Runtime and Main, driven by viewport input. Main's key and every
|
||||
## button of the dismissal flow (the hiring buttons, 雇用済み, the roster rows, 解雇する…,
|
||||
## やめる, 解雇を確定) are pressed through root.push_input in the viewport's own
|
||||
## coordinates after the control is scrolled into view (_click, the form of
|
||||
## tests/ui/test_business_management.gd _click_principal_button). The panel shows the
|
||||
## severance, the hidden-fund top-up, the reopened Task, the rescue the command
|
||||
## interrupts and the held wages before the press, dismisses, shows the result and a
|
||||
## refusal that arrives after the press, and for every refusal row (R1, R2, R3, R5, R7,
|
||||
## R8, R9) keeps the button disabled with the reason and sends nothing when it is
|
||||
## clicked (5.a, whole save bytes). This is viewport input, not OS input; the Windows
|
||||
## device, IME, OS scaling and the same-candidate device check are later (SPEC 10).
|
||||
func _test_dismiss_from_main() -> void:
|
||||
var runtime: Variant = root.get_node("SimulationRuntime")
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
@@ -876,15 +938,14 @@ func _test_dismiss_from_main() -> void:
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
clock.set_paused(true)
|
||||
var fixture := {&"runtime": runtime, &"clock": clock, &"state": state}
|
||||
var key := InputEventKey.new()
|
||||
key.keycode = KEY_H
|
||||
key.pressed = true
|
||||
main.call("_unhandled_key_input", key)
|
||||
await _press_key(KEY_H)
|
||||
_expect((main.get("staff_candidate_panel") as Control).visible, "the H key opens the hiring panel")
|
||||
for candidate: StringName in [&"staff_akane", &"staff_eri", &"heroine_anri"]:
|
||||
var hire := (main.get("staff_candidate_buttons") as Dictionary).get(candidate) as Button
|
||||
_expect(hire != null, "the hiring panel lists %s" % candidate)
|
||||
if hire != null:
|
||||
hire.pressed.emit()
|
||||
await _click(hire)
|
||||
_expect(runtime.staff_service.has_staff(candidate), "a click hires %s" % candidate)
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": &"adult_contract", &"role_id": &"attendant",
|
||||
&"options": {&"employment_kind": &"ADULT_CONTRACT", &"daily_wage": 100}}).is_success(), "an ADULT_CONTRACT fixture row")
|
||||
_expect(_scenes.command(fixture, &"create_staff_task", {&"task_id": &"ui_task", &"task_type": &"inspect", &"priority": 90, &"options": {
|
||||
@@ -893,35 +954,65 @@ func _test_dismiss_from_main() -> void:
|
||||
clock.advance_minutes(1)
|
||||
var eri_task: StringName = runtime.staff_service.get_staff(&"staff_eri").current_task_id
|
||||
_expect(eri_task == &"ui_task", "the cleaner holds the Task (%s)" % eri_task)
|
||||
(main.get("staff_management_toggle_button") as Button).pressed.emit()
|
||||
await _click(main.get("staff_management_toggle_button") as Button)
|
||||
var panel: StaffManagementPanel = main.get("staff_management_panel")
|
||||
_expect(panel.visible and panel.roster_buttons.size() == 4, "the hired-staff panel lists four employees")
|
||||
(panel.roster_buttons[&"staff_akane"] as Button).pressed.emit()
|
||||
_expect(not panel.dismiss_button.disabled and panel.dismiss_button.tooltip_text.contains("退職金 G360"), "the receptionist row offers the dismissal with severance G360")
|
||||
_expect(panel.visible and panel.roster_buttons.size() == 4, "a click on 雇用済み shows the four employees")
|
||||
await _click(panel.roster_buttons[&"staff_akane"])
|
||||
_expect(panel.selected_staff_id == &"staff_akane" and not panel.dismiss_button.disabled and panel.dismiss_button.tooltip_text.contains("退職金 G360"),
|
||||
"a click on her row offers the dismissal with severance G360")
|
||||
var untouched := var_to_bytes(runtime.create_snapshot())
|
||||
panel.dismiss_button.pressed.emit()
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_box.visible and panel.dismiss_confirm_label.text.contains("退職金 G360(日給の3日分)") and var_to_bytes(runtime.create_snapshot()) == untouched,
|
||||
"pressing 解雇する… shows the severance and sends nothing (5.a, bytes)")
|
||||
panel.dismiss_cancel_button.pressed.emit()
|
||||
_expect(not panel.dismiss_confirm_box.visible and var_to_bytes(runtime.create_snapshot()) == untouched, "やめる closes the confirmation without a command (bytes)")
|
||||
panel.dismiss_button.pressed.emit()
|
||||
panel.dismiss_confirm_button.pressed.emit()
|
||||
"a click on 解雇する… shows the severance and sends nothing (5.a, bytes)")
|
||||
await _click(panel.dismiss_cancel_button)
|
||||
_expect(not panel.dismiss_confirm_box.visible and var_to_bytes(runtime.create_snapshot()) == untouched, "a click on やめる closes the confirmation without a command (bytes)")
|
||||
await _click(panel.dismiss_button)
|
||||
await _click(panel.dismiss_confirm_button)
|
||||
_expect(not runtime.staff_service.has_staff(&"staff_akane") and not panel.roster_buttons.has(&"staff_akane") and panel.selected_staff_id != &"staff_akane",
|
||||
"解雇を確定 dismisses her; the roster and the selection follow")
|
||||
"a click on 解雇を確定 dismisses her; the roster and the selection follow")
|
||||
_expect(panel.result_label.text.contains("解雇しました") and panel.result_label.text.contains("G360"), "the result shows the paid severance: %s" % panel.result_label.text)
|
||||
_expect(_scenes.count_category(runtime, FinancePolicy.SEVERANCE_CATEGORY, &"staff_akane") == 1, "the panel's command paid the severance once")
|
||||
(panel.roster_buttons[&"staff_eri"] as Button).pressed.emit()
|
||||
panel.dismiss_button.pressed.emit()
|
||||
await _click(panel.roster_buttons[&"staff_eri"])
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_label.text.contains("持っている作業は開き直して") and panel.dismiss_confirm_label.text.contains("G270"), "the cleaner's confirmation names the reopened Task and G270")
|
||||
clock.set_paused(false)
|
||||
panel.dismiss_confirm_button.pressed.emit()
|
||||
await _click(panel.dismiss_confirm_button, true)
|
||||
_expect(runtime.staff_service.has_staff(&"staff_eri") and panel.dismiss_button.disabled and panel.result_label.text.contains("受理しました"), "with the clock running the dismissal waits for the next minute")
|
||||
clock.advance_minutes(1)
|
||||
clock.set_paused(true)
|
||||
_expect(not runtime.staff_service.has_staff(&"staff_eri") and runtime.staff_service.get_task(&"ui_task").state == StaffTask.State.OPEN
|
||||
and panel.result_label.text.contains("解雇しました") and panel.result_label.text.contains("持っていた作業は開き直しました"), "the queued dismissal completes and reopens her Task")
|
||||
# The rescue the command interrupts (SPEC 6.2.1), on one floor: a collapsed medic (the
|
||||
# patient) and a medic who carries her to the infirmary on foot.
|
||||
var infirmary := _scenes.command(fixture, &"build_facility", {&"definition_id": &"infirmary", &"origin": Vector3i(8, 0, 1)})
|
||||
_expect(infirmary.is_success(), "build the infirmary (%s %s)" % [infirmary.message, infirmary.details])
|
||||
clock.advance_minutes(runtime.definition_registry.get_facility(&"infirmary").build_minutes)
|
||||
for id: StringName in [&"patient", &"medic"]:
|
||||
var options := {
|
||||
&"current_cell": Vector3i(-2, 0, 1) if id == &"patient" else Vector3i(-10, 0, 1),
|
||||
&"health": 0 if id == &"patient" else 100, &"state": StaffRuntime.State.AVAILABLE,
|
||||
&"role_tags": [&"medic"], &"permitted_task_tags": [&"rescue"], &"daily_wage": 100,
|
||||
}
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": id, &"role_id": &"medic", &"options": options}).is_success(), "register rescue employee %s" % id)
|
||||
_expect(_scenes.advance_to_rescue_phase(fixture, "rescue_transport/MOVING/-/single"), "the medic carries the patient on foot (%s)" % _scenes.rescue_phase(runtime))
|
||||
panel.refresh()
|
||||
await _click(panel.roster_buttons[&"patient"])
|
||||
var carrying := var_to_bytes(runtime.create_snapshot())
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_label.text.contains("この従業員への救護は取り消し、救護係を外します。") and var_to_bytes(runtime.create_snapshot()) == carrying,
|
||||
"the patient's confirmation says her rescue is cancelled: %s" % panel.dismiss_confirm_label.text)
|
||||
await _click(panel.dismiss_cancel_button)
|
||||
await _click(panel.roster_buttons[&"medic"])
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_label.text.contains("運んでいる救護の搬送は取り消し、患者の救護をやり直します。") and not panel.dismiss_confirm_label.text.contains("開き直して"),
|
||||
"the carrier's confirmation says the transport is cancelled, not reopened: %s" % panel.dismiss_confirm_label.text)
|
||||
await _click(panel.dismiss_confirm_button)
|
||||
_expect(not runtime.staff_service.has_staff(&"medic") and panel.result_label.text.contains("関わっていた救護の作業は取り消しました。")
|
||||
and not panel.result_label.text.contains("開き直しました"), "the carrier's result names the cancelled rescue: %s" % panel.result_label.text)
|
||||
await _click(panel.roster_buttons[&"patient"])
|
||||
await _click(panel.dismiss_button)
|
||||
await _click(panel.dismiss_confirm_button)
|
||||
_expect(not runtime.staff_service.has_staff(&"patient"), "the patient is dismissed too, so the later rows are as before")
|
||||
for row: Array in [[&"heroine_anri", "ユニーク:雇用・維持費無料 / 解雇・売却不可", "R1"], [&"adult_contract", "ギルドで雇った一般従業員だけを解雇できます。", "R7"]]:
|
||||
_expect_refused_row(panel, runtime, row[0], row[1], row[2])
|
||||
await _expect_refused_row(panel, runtime, row[0], row[1], row[2])
|
||||
# R9: the defender of an unfinished raid.
|
||||
var objective_build := _scenes.command(fixture, &"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(-8, 0, 0)})
|
||||
_expect(objective_build.is_success(), "build the raid objective (%s %s)" % [objective_build.message, objective_build.details])
|
||||
@@ -935,7 +1026,7 @@ func _test_dismiss_from_main() -> void:
|
||||
&"waves": [{&"wave_id": &"wave.one", &"threat_strength": 15, &"retreat_threshold": 0, &"max_rounds": 1, &"entry_points": [&"gate.north"]}]}}, &"defense_controller").is_success()
|
||||
and runtime.submit_command(&"register_defense_combatant", {&"raid_id": &"ui.raid", &"actor_id": &"staff_klaus", &"side": &"DEFENDER", &"combat": 90,
|
||||
&"max_health": 100, &"options": {}}, &"defense_controller").is_success(), "the guard defends an unfinished raid")
|
||||
_expect_refused_row(panel, runtime, &"staff_klaus", "襲撃の防衛に参加中は解雇できません。", "R9")
|
||||
await _expect_refused_row(panel, runtime, &"staff_klaus", "襲撃の防衛に参加中は解雇できません。", "R9")
|
||||
# R2: the porter carrying supply logistics.
|
||||
for spec: Array in [[&"warehouse", Vector3i(-8, 0, 1)], [&"kitchen", Vector3i(4, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]:
|
||||
var built := _scenes.command(fixture, &"build_facility", {&"definition_id": spec[0], &"origin": spec[1]})
|
||||
@@ -961,7 +1052,7 @@ func _test_dismiss_from_main() -> void:
|
||||
for minute: int in 40:
|
||||
if runtime.staff_service.is_externally_driven_task(runtime.staff_service.get_staff(&"staff_gina").current_task_id): break
|
||||
clock.advance_minutes(1)
|
||||
_expect_refused_row(panel, runtime, &"staff_gina", "物流の受け取り・配達・返送の途中です。", "R2")
|
||||
await _expect_refused_row(panel, runtime, &"staff_gina", "物流の受け取り・配達・返送の途中です。", "R2")
|
||||
# R3: a Task Reservation that cannot be released (disclosed fixture: the tool is consumed).
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": &"ui_tool_worker", &"role_id": &"worker", &"options": {&"state": StaffRuntime.State.AVAILABLE,
|
||||
&"current_cell": Vector3i(0, 0, WorldService.LAYER_FRONT), &"role_tags": [&"worker"], &"permitted_task_tags": [&"work"], &"daily_wage": 100}}).is_success()
|
||||
@@ -974,34 +1065,52 @@ func _test_dismiss_from_main() -> void:
|
||||
var before_consume: Dictionary = runtime.create_snapshot()
|
||||
_expect(tool_task.assigned_staff_id == &"ui_tool_worker" and tool_task.reservation_ids.size() == 1
|
||||
and bool(runtime.inventory_service.consume_reservation(tool_task.reservation_ids[0], &"p16.ui.consume", clock.absolute_minute).get(&"ok", false)), "fixture consumes the reserved tool")
|
||||
_expect_refused_row(panel, runtime, &"ui_tool_worker", "作業の予約を解けないため解雇できません。", "R3")
|
||||
await _expect_refused_row(panel, runtime, &"ui_tool_worker", "作業の予約を解けないため解雇できません。", "R3")
|
||||
_expect(runtime.restore_snapshot(before_consume), "the fixture's consumption is undone after the check")
|
||||
panel.refresh()
|
||||
# The hidden fund covers the part of the severance the front fund lacks (A14).
|
||||
# Disclosed balance fixtures: the front fund holds G100 and a hidden-fund sale adds
|
||||
# G200, so the funds return to the earlier balances after the dismissal.
|
||||
for account: StringName in [Scenes.FRONT_SALES, Scenes.BACK_SALES, Scenes.PAYER]:
|
||||
runtime.economy_service.register_account(account)
|
||||
_expect(runtime.economy_service.initialize_account(Scenes.PAYER, 100000, clock.absolute_minute, &"p16.ui.payer").is_success(), "disclosed fixture: a payer for the balance fixtures")
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": &"ui_top_up", &"role_id": &"cleaner", &"options": {&"daily_wage": 100}}).is_success(), "an employee whose severance needs the hidden fund")
|
||||
var front_before: int = runtime.economy_service.get_balance(FRONT)
|
||||
var back_before: int = runtime.economy_service.get_balance(BACK)
|
||||
_expect(front_before > 0 and _scenes.drain_front(fixture, "ui.top_up") and _scenes.sale(fixture, FRONT, 100, "ui.top_up.front")
|
||||
and _scenes.sale(fixture, BACK, 200, "ui.top_up.back"), "disclosed fixture: front G100, hidden G%d" % (back_before + 200))
|
||||
panel.refresh()
|
||||
await _click(panel.roster_buttons[&"ui_top_up"])
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_label.text.contains("退職金 G300(日給の3日分)を表金から払います。不足 G200 は裏金から補います。"),
|
||||
"the confirmation shows the hidden-fund top-up: %s" % panel.dismiss_confirm_label.text)
|
||||
await _click(panel.dismiss_confirm_button)
|
||||
_expect(not runtime.staff_service.has_staff(&"ui_top_up") and panel.result_label.text.contains("退職金 G300 を払いました(不足 G200 は裏金から補填)"),
|
||||
"the result shows the top-up: %s" % panel.result_label.text)
|
||||
_expect(runtime.economy_service.get_balance(FRONT) == 0 and runtime.economy_service.get_balance(BACK) == back_before
|
||||
and _scenes.sale(fixture, FRONT, front_before, "ui.top_up.restore") and runtime.economy_service.get_balance(FRONT) == front_before,
|
||||
"the funds are back at G%d and G%d" % [front_before, back_before])
|
||||
# A refusal that arrives after the press: the funds are spent before the queued command runs.
|
||||
_expect(_scenes.command(fixture, &"register_staff", {&"staff_id": &"ui_late", &"role_id": &"cleaner", &"options": {&"daily_wage": 100}}).is_success(), "an employee to dismiss")
|
||||
panel.refresh()
|
||||
(panel.roster_buttons[&"ui_late"] as Button).pressed.emit()
|
||||
panel.dismiss_button.pressed.emit()
|
||||
clock.set_paused(false)
|
||||
panel.dismiss_confirm_button.pressed.emit()
|
||||
await _click(panel.roster_buttons[&"ui_late"])
|
||||
await _click(panel.dismiss_button)
|
||||
await _click(panel.dismiss_confirm_button, true)
|
||||
_expect(runtime.staff_service.has_staff(&"ui_late") and panel.result_label.text.contains("受理しました"), "the dismissal is accepted for the next minute")
|
||||
_expect(_scenes.drain_front(fixture, "ui.late") and runtime.economy_service.get_balance(BACK) < 300, "disclosed fixture: the funds are spent before the command runs")
|
||||
clock.advance_minutes(1)
|
||||
clock.set_paused(true)
|
||||
_expect(runtime.staff_service.has_staff(&"ui_late") and panel.result_label.text.contains("退職金 G300 を払えません"), "the result shows the refusal after the press: %s" % panel.result_label.text)
|
||||
_expect_refused_row(panel, runtime, &"ui_late", "退職金 G300 を払えません", "R8")
|
||||
await _expect_refused_row(panel, runtime, &"ui_late", "退職金 G300 を払えません", "R8")
|
||||
# The held unpaid wages: the first 00:00 close finds no funds, so the day's wage stays unpaid (A09).
|
||||
clock.advance_minutes(1441 - int(clock.absolute_minute))
|
||||
_expect(runtime.staff_service.get_staff(&"ui_late").unpaid_wages == 100, "the first close leaves her daily wage 100 unpaid")
|
||||
for account: StringName in [Scenes.FRONT_SALES, Scenes.PAYER]:
|
||||
runtime.economy_service.register_account(account)
|
||||
_expect(runtime.economy_service.initialize_account(Scenes.PAYER, 100000, clock.absolute_minute, &"p16.ui.payer").is_success()
|
||||
and _scenes.sale(fixture, FRONT, 1000, "ui.sale"), "disclosed fixture: a sale refills the front fund")
|
||||
_expect(_scenes.sale(fixture, FRONT, 1000, "ui.sale"), "disclosed fixture: a sale refills the front fund")
|
||||
await _show_staff_panel(main, panel)
|
||||
panel.refresh()
|
||||
(panel.roster_buttons[&"ui_late"] as Button).pressed.emit()
|
||||
panel.dismiss_button.pressed.emit()
|
||||
await _click(panel.roster_buttons[&"ui_late"])
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(panel.dismiss_confirm_label.text.contains("未払い G100 は退職金と別に次の0時に精算します"), "the confirmation shows the held wages: %s" % panel.dismiss_confirm_label.text)
|
||||
panel.dismiss_confirm_button.pressed.emit()
|
||||
await _click(panel.dismiss_confirm_button)
|
||||
_expect(not runtime.staff_service.has_staff(&"ui_late") and panel.result_label.text.contains("未払い G100 は次の0時に精算します"), "the result shows the held wages: %s" % panel.result_label.text)
|
||||
# R5: after the game ends (three actual operating losses with no funds).
|
||||
if not bool(runtime.get(&"_operations_enabled")):
|
||||
@@ -1012,20 +1121,79 @@ func _test_dismiss_from_main() -> void:
|
||||
_scenes.drain_front(fixture, "ui.bankrupt.%d" % clock.absolute_minute)
|
||||
clock.advance_minutes(1)
|
||||
_expect(runtime.operations_coordinator.is_terminal(), "the game ends (BANKRUPT) after three actual operating losses")
|
||||
_expect_refused_row(panel, runtime, &"ui_tool_worker", "運営終了後は解雇できません。", "R5")
|
||||
await _show_staff_panel(main, panel)
|
||||
await _expect_refused_row(panel, runtime, &"ui_tool_worker", "運営終了後は解雇できません。", "R5")
|
||||
var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main)
|
||||
_expect(bool(cleanup.get(&"passed", false)), "ordinary Main release with audio cleanup")
|
||||
|
||||
|
||||
## A refusal row: a click selects the row, the button is disabled with the reason, and a
|
||||
## click on the disabled button sends nothing (5.a, whole save bytes).
|
||||
func _expect_refused_row(panel: StaffManagementPanel, runtime: Variant, staff_id: StringName, text: String, label: String) -> void:
|
||||
panel.refresh()
|
||||
_expect(panel.roster_buttons.has(staff_id), "%s: the row is listed" % label)
|
||||
if not panel.roster_buttons.has(staff_id):
|
||||
return
|
||||
(panel.roster_buttons[staff_id] as Button).pressed.emit()
|
||||
await _click(panel.roster_buttons[staff_id])
|
||||
var before := var_to_bytes(runtime.create_snapshot())
|
||||
_expect(panel.dismiss_button.disabled and panel.staff_detail_label.text.contains(text) and panel.dismiss_button.tooltip_text.contains(text),
|
||||
_expect(panel.selected_staff_id == staff_id and panel.dismiss_button.disabled and panel.staff_detail_label.text.contains(text) and panel.dismiss_button.tooltip_text.contains(text),
|
||||
"%s: the button is disabled and the reason is shown (%s)" % [label, panel.dismiss_button.tooltip_text])
|
||||
panel.dismiss_button.pressed.emit()
|
||||
await _click(panel.dismiss_button)
|
||||
_expect(var_to_bytes(runtime.create_snapshot()) == before and not panel.dismiss_confirm_box.visible and runtime.staff_service.has_staff(staff_id),
|
||||
"%s: a forced press sends nothing (5.a, whole save bytes)" % label)
|
||||
"%s: a click on the disabled button sends nothing (5.a, whole save bytes)" % label)
|
||||
|
||||
|
||||
## Viewport input (the _click_principal_button form of tests/ui/test_business_management.gd):
|
||||
## the control is scrolled into view, the mouse moves to its centre and presses and
|
||||
## releases there, in the viewport's own coordinates (Main is scaled from 1440x810).
|
||||
## A roster row rebuilt by the panel in this frame scrolls only from the next frame
|
||||
## (ScrollContainer.ensure_control_visible), and the roster's own scroll sits inside
|
||||
## the side panel's scroll, so each is asked again after a frame.
|
||||
## `running`: the clock runs only for the release, so a press while time runs sends
|
||||
## the command for the next minute and no frame advances a minute.
|
||||
func _click(control: Control, running := false) -> void:
|
||||
for pass_index: int in 2:
|
||||
await process_frame
|
||||
var ancestor: Node = control.get_parent()
|
||||
while ancestor != null:
|
||||
if ancestor is ScrollContainer: ancestor.ensure_control_visible(control)
|
||||
ancestor = ancestor.get_parent()
|
||||
await process_frame
|
||||
var clock: Variant = root.get_node("GameClock")
|
||||
var point := control.get_global_rect().get_center()
|
||||
var motion := InputEventMouseMotion.new()
|
||||
motion.position = point
|
||||
motion.global_position = point
|
||||
root.push_input(motion, true)
|
||||
for pressed: bool in [true, false]:
|
||||
var event := InputEventMouseButton.new()
|
||||
event.button_index = MOUSE_BUTTON_LEFT
|
||||
event.pressed = pressed
|
||||
event.position = point
|
||||
event.global_position = point
|
||||
event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0
|
||||
if running and not pressed:
|
||||
clock.set_paused(false)
|
||||
root.push_input(event, true)
|
||||
if running and not pressed:
|
||||
clock.set_paused(true)
|
||||
await process_frame
|
||||
|
||||
|
||||
## After a 00:00 close Main shows the ledger in the side panel (_on_snapshot_published)
|
||||
## and hides the staff controls; the player returns with the 従業員 navigation button.
|
||||
func _show_staff_panel(main: Control, panel: StaffManagementPanel) -> void:
|
||||
_expect(not panel.is_visible_in_tree(), "after the close Main shows the ledger instead of the staff panel")
|
||||
await _click((main.get("navigation_buttons") as Dictionary)[&"staff"] as Button)
|
||||
_expect(panel.is_visible_in_tree(), "a click on 従業員 brings the hired-staff panel back")
|
||||
|
||||
|
||||
## A key press and release through the viewport (Main's _unhandled_key_input).
|
||||
func _press_key(keycode: Key) -> void:
|
||||
for pressed: bool in [true, false]:
|
||||
var event := InputEventKey.new()
|
||||
event.keycode = keycode
|
||||
event.physical_keycode = keycode
|
||||
event.pressed = pressed
|
||||
root.push_input(event, true)
|
||||
await process_frame
|
||||
|
||||
Reference in New Issue
Block a user