- Добавлен асинхронный процесс аппаратной выдачи физических призов (хоппер и витрина) в GameplayHudController. - Реализована блокировка выдачи по 10-секундному таймауту датчика хоппера с гарантированной отправкой команды отключения двигателя '300'. - Реализован автоматический таймаут неактивности на экране QR-кода с последующим переходом в главное меню. - Расширен сервис статистики PrizeSessionStateService новыми долгосрочными счетчиками (сыгранные игры, очки, монеты, сбои связи COM, физ. выдачи и ошибки). - Обновлен построитель сводки статистики в меню настроек для отображения новых счетчиков и списка последних 5 сессий. - Написаны EditMode тесты для проверки инкремента счетчиков и запуска выдачи призов.
222 lines
9.6 KiB
C#
222 lines
9.6 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using DanroJump.Prizes.Qr;
|
||
using DanroJump.Settings;
|
||
using DanroJump.UI.MainMenu;
|
||
using DanroJump.UI.Modals;
|
||
using NUnit.Framework;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
|
||
namespace DanroJump.Tests.EditMode
|
||
{
|
||
public sealed class MainMenuControllerServiceActionTests
|
||
{
|
||
private GameObject gameObject;
|
||
|
||
[TearDown]
|
||
public void TearDown()
|
||
{
|
||
if (gameObject != null)
|
||
{
|
||
UnityEngine.Object.DestroyImmediate(gameObject);
|
||
}
|
||
}
|
||
|
||
[Test]
|
||
public void QrSettingsAction_OpensQrTierSettingsModal()
|
||
{
|
||
var controller = CreateController();
|
||
var settings = new StubSettingsService();
|
||
var qrSettingsRoot = CreateQrSettingsRoot();
|
||
var qrSettingsModal = new QrCodeSettingsModalWindow(qrSettingsRoot, settings);
|
||
|
||
TestObjectFactory.SetPrivateField(controller, "serviceSettingsService", settings);
|
||
TestObjectFactory.SetPrivateField(controller, "qrCodeSettingsModal", qrSettingsModal);
|
||
TestObjectFactory.SetPrivateField(controller, "modalStack", new ModalStackController(new VisualElement(), null));
|
||
|
||
InvokeServiceAction(
|
||
controller,
|
||
ServiceSettingsActionResult.Success(
|
||
"Настройки QR",
|
||
"Открыты настройки QR.",
|
||
ServiceSettingsKeys.QrPrizeTextSettings));
|
||
|
||
Assert.That(qrSettingsModal.IsOpen, Is.True);
|
||
Assert.That(qrSettingsRoot.Q<Label>("QrSettingsTierValue").text, Is.EqualTo("Бронзовый"));
|
||
Assert.That(qrSettingsRoot.Q<Label>("QrSettingsPrizeTextValue").text, Is.EqualTo("Бронза"));
|
||
|
||
qrSettingsModal.Dispose();
|
||
}
|
||
|
||
[Test]
|
||
public void OpenQrPrizeTextInput_SavesSelectedTierPrizeTextWithoutTechnicalTemplate()
|
||
{
|
||
var controller = CreateController();
|
||
var settings = new StubSettingsService();
|
||
var qrSettingsModal = new QrCodeSettingsModalWindow(CreateQrSettingsRoot(), settings);
|
||
var textInputRoot = CreateTextInputRoot();
|
||
var textInputModal = new ServiceTextInputModalWindow(textInputRoot);
|
||
|
||
TestObjectFactory.SetPrivateField(controller, "serviceSettingsService", settings);
|
||
TestObjectFactory.SetPrivateField(controller, "serviceTextInputModal", textInputModal);
|
||
TestObjectFactory.SetPrivateField(controller, "qrCodeSettingsModal", qrSettingsModal);
|
||
TestObjectFactory.SetPrivateField(controller, "modalStack", new ModalStackController(new VisualElement(), null));
|
||
|
||
InvokeOpenQrPrizeTextInput(controller, QrPrizeTier.Silver, ServiceSettingsKeys.QrPrizeTextSilver);
|
||
|
||
Assert.That(textInputModal.IsOpen, Is.True);
|
||
Assert.That(textInputRoot.Q<Label>("ServiceTextInputSetting").text, Is.EqualTo("Серебряный QR"));
|
||
Assert.That(textInputRoot.Q<Label>("ServiceTextInputPreview").text, Is.EqualTo("Серебро"));
|
||
Assert.That(textInputRoot.Q<Label>("ServiceTextInputPreview").text, Does.Not.Contain("{score}"));
|
||
|
||
Assert.That(textInputModal.Confirm(), Is.True);
|
||
|
||
Assert.That(settings.LastSetStringKey, Is.EqualTo(ServiceSettingsKeys.QrPrizeTextSilver));
|
||
Assert.That(settings.LastSetStringValue, Is.EqualTo("Серебро"));
|
||
Assert.That(settings.SaveCalls, Is.EqualTo(1));
|
||
|
||
qrSettingsModal.Dispose();
|
||
textInputModal.Dispose();
|
||
}
|
||
|
||
private MainMenuController CreateController()
|
||
{
|
||
gameObject = new GameObject("MainMenuControllerServiceActionTest");
|
||
return gameObject.AddComponent<MainMenuController>();
|
||
}
|
||
|
||
private static void InvokeServiceAction(
|
||
MainMenuController controller,
|
||
ServiceSettingsActionResult result)
|
||
{
|
||
var method = typeof(MainMenuController).GetMethod(
|
||
"HandleServiceActionActivated",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
Assert.That(method, Is.Not.Null);
|
||
method.Invoke(controller, new object[] { null, result });
|
||
}
|
||
|
||
private static void InvokeOpenQrPrizeTextInput(
|
||
MainMenuController controller,
|
||
QrPrizeTier tier,
|
||
string settingKey)
|
||
{
|
||
var method = typeof(MainMenuController).GetMethod(
|
||
"OpenQrPrizeTextInput",
|
||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||
Assert.That(method, Is.Not.Null);
|
||
method.Invoke(controller, new object[] { tier, settingKey });
|
||
}
|
||
|
||
private static VisualElement CreateQrSettingsRoot()
|
||
{
|
||
var root = new VisualElement { name = "QrSettingsModal" };
|
||
root.Add(new Label { name = "QrSettingsTierValue" });
|
||
root.Add(new Label { name = "QrSettingsPrizeTextValue" });
|
||
root.Add(new Label { name = "QrSettingsStatus" });
|
||
root.Add(new Button { name = "QrSettingsTierPreviousButton" });
|
||
root.Add(new Button { name = "QrSettingsTierNextButton" });
|
||
root.Add(new Button { name = "QrSettingsEditTextButton" });
|
||
root.Add(new Button { name = "QrSettingsCloseButton" });
|
||
return root;
|
||
}
|
||
|
||
private static VisualElement CreateTextInputRoot()
|
||
{
|
||
var root = new VisualElement { name = "ServiceTextInputModal" };
|
||
root.Add(new Label { name = "ServiceTextInputTitle" });
|
||
root.Add(new Label { name = "ServiceTextInputSetting" });
|
||
root.Add(new Label { name = "ServiceTextInputPreview" });
|
||
root.Add(new VisualElement { name = "ServiceTextInputSlots" });
|
||
root.Add(new Label { name = "ServiceTextInputSymbol" });
|
||
root.Add(new Label { name = "ServiceTextInputStatus" });
|
||
root.Add(new Button { name = "ServiceTextInputConfirmButton" });
|
||
root.Add(new Button { name = "ServiceTextInputDeleteButton" });
|
||
root.Add(new Button { name = "ServiceTextInputCancelButton" });
|
||
return root;
|
||
}
|
||
|
||
private sealed class StubSettingsService : IServiceSettingsService
|
||
{
|
||
private readonly Dictionary<string, ServiceSettingDefinition> definitions = new();
|
||
private readonly Dictionary<string, string> values = new();
|
||
|
||
public StubSettingsService()
|
||
{
|
||
AddStringDefinition(ServiceSettingsKeys.QrPrizeTextBronze, "Бронзовый QR", "Бронза");
|
||
AddStringDefinition(ServiceSettingsKeys.QrPrizeTextSilver, "Серебряный QR", "Серебро");
|
||
AddStringDefinition(ServiceSettingsKeys.QrPrizeTextGold, "Золотой QR", "Золото");
|
||
}
|
||
|
||
public string LastSetStringKey { get; private set; }
|
||
public string LastSetStringValue { get; private set; }
|
||
public int SaveCalls { get; private set; }
|
||
|
||
public ServiceSettingsDatabase Database => null;
|
||
public event Action SettingsLoaded { add { } remove { } }
|
||
public event Action SettingsSaved { add { } remove { } }
|
||
public event Action<string, ServiceSettingValue> SettingChanged { add { } remove { } }
|
||
|
||
public bool TryGetDefinition(string key, out ServiceSettingDefinition definition)
|
||
{
|
||
return definitions.TryGetValue(key, out definition);
|
||
}
|
||
|
||
public IReadOnlyDictionary<string, ServiceSettingValue> Snapshot()
|
||
{
|
||
return new Dictionary<string, ServiceSettingValue>();
|
||
}
|
||
|
||
public ServiceSettingValue Get(string key)
|
||
{
|
||
return ServiceSettingValue.String(GetString(key));
|
||
}
|
||
|
||
public bool GetBool(string key) => false;
|
||
public int GetInt(string key) => 0;
|
||
public float GetFloat(string key) => 0f;
|
||
|
||
public string GetString(string key)
|
||
{
|
||
return values.TryGetValue(key, out var value) ? value : string.Empty;
|
||
}
|
||
|
||
public int GetOptionIndex(string key) => 0;
|
||
public void Load() { }
|
||
public void Save() => SaveCalls++;
|
||
public void ReloadExternalChanges() { }
|
||
public void ApplyAll() { }
|
||
public void ResetAllToDefaults() { }
|
||
public void ResetGroupToDefaults(string groupName) { }
|
||
public bool Set(string key, ServiceSettingValue value) => true;
|
||
public bool SetExternal(string key, ServiceSettingValue value) => true;
|
||
public bool SetBool(string key, bool value) => true;
|
||
public bool SetInt(string key, int value) => true;
|
||
public bool SetFloat(string key, float value) => true;
|
||
|
||
public bool SetString(string key, string value)
|
||
{
|
||
LastSetStringKey = key;
|
||
LastSetStringValue = value;
|
||
values[key] = value;
|
||
return true;
|
||
}
|
||
|
||
public bool SetOptionIndex(string key, int optionIndex) => true;
|
||
|
||
private void AddStringDefinition(string key, string displayName, string value)
|
||
{
|
||
values[key] = value;
|
||
var definition = TestObjectFactory.CreateSettingDefinition(
|
||
key,
|
||
ServiceSettingValueType.String,
|
||
ServiceSettingValue.String(value));
|
||
TestObjectFactory.SetPrivateField(definition, "displayName", displayName);
|
||
definitions[key] = definition;
|
||
}
|
||
}
|
||
}
|
||
}
|