7551 lines
360 KiB
Kotlin
7551 lines
360 KiB
Kotlin
// StateCheck.kt —— 状态层内核自检(Node 下运行,对应 S3 的 Redux 胶水)
|
||
//
|
||
// 覆盖点(都是原 module_10849 的实际语义):
|
||
// ① Store:派发 → 归约 → 通知监听器,监听器看到的是新状态
|
||
// ② 中间件:列表靠后的包在更外层(调用顺序先外后内,返回顺序反过来)
|
||
// ③ 网关:按动作类的监听器在状态更新之前触发
|
||
// ④ 网关:模型订阅可退订;重复订阅直接失败
|
||
// ⑤ 网关:调度器负责把派发排进引擎循环(这里用同步调度器验证)
|
||
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
|
||
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
|
||
// ⑧ 全目录动作类都能按 KClass 路由
|
||
// ⑨~㊿/51-69 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
|
||
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview / Entrance / User / Settings /
|
||
// AccountSettings / ProBattle / Language / Announcement / News / Notifications / Quests /
|
||
// LobbyLoader / PurchaseDialogState / PromoCode / SupportUrl / RulesUpdated / NotificationByCountry / AvailabilityByRank /
|
||
// Icons / QuestCategories / MainQuest / SecuritySettings / ReferralEvent /
|
||
// TipsState / RankUpDialogState / BattleResult / CaptchaState / ModalScreen / DroppableGold / EventGoods /
|
||
// CreatorTagState / RewardedVideosState / PasswordChangeState /
|
||
// System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction /
|
||
// Moderation / MigrationState / Register / UserReports /
|
||
// Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename /
|
||
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers / Challenges / ClansStorage / Clan / ChatState / ItemNotifications / MatchmakingGroup;
|
||
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
||
//
|
||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||
package App
|
||
|
||
import State.Middleware
|
||
import State.ReduxGateway
|
||
import State.ReduxStore
|
||
import State.StoreListener
|
||
import App.State.ActionCatalog
|
||
import App.State.ActionSamples
|
||
import App.State.ApplyInvite
|
||
import App.State.Reducers.ByPasswordStatus
|
||
import App.State.Reducers.HomeStateSlice
|
||
import App.State.Reducers.InviteStateSlice
|
||
import App.State.Reducers.InviteStatus
|
||
import App.State.Reducers.LocalBattleUserStateSlice
|
||
import App.State.Reducers.LoginByPasswordStateSlice
|
||
import App.State.Reducers.LoginOnly
|
||
import App.State.Reducers.ReduceHome
|
||
import App.State.Reducers.ReduceInvite
|
||
import App.State.Reducers.ReduceLocalBattleUser
|
||
import App.State.Reducers.ReduceLoginByPassword
|
||
import App.State.Reducers.ReduceTwitch
|
||
import App.State.Reducers.ResetLoginByPassword
|
||
import App.State.Reducers.ResetLoginByPasswordStatus
|
||
import App.State.Reducers.ShowShopDiscountNotification
|
||
import App.State.Reducers.TwitchStateSlice
|
||
import App.State.ReduceRoot
|
||
import App.State.RootReducer
|
||
import App.State.RootState
|
||
import App.State.RootWiring
|
||
import App.State.IncorrectLogin2FA
|
||
import App.State.AddItemToOrderProcessing
|
||
import App.State.AdditionalDescription
|
||
import App.State.BraintreeLoadingInterrupt
|
||
import App.State.Category
|
||
import App.State.CurrentPayMode
|
||
import App.State.DeleteOneTimePurchaseItem
|
||
import App.State.DiscountItem
|
||
import App.State.InitAdditionalDescription
|
||
import App.State.InitAmountString
|
||
import App.State.InitCategory
|
||
import App.State.InitGroup
|
||
import App.State.InitOneTimePurchaseItem
|
||
import App.State.InitPayModeAction
|
||
import App.State.InitPaymentProviderItems
|
||
import App.State.InitPaymentViewType
|
||
import App.State.InitShopCard
|
||
import App.State.InitShopCardDiscount
|
||
import App.State.InitShopCardView
|
||
import App.State.InitShopItem
|
||
import App.State.InitShopItemName
|
||
import App.State.MobilePaymentStateChanged
|
||
import App.State.OneTimePurchaseItem
|
||
import App.State.PayMode
|
||
import App.State.PaymentProviderItem
|
||
import App.State.RemoveItemFromOrderProcessing
|
||
import App.State.SavePreviousScreen
|
||
import App.State.SaveTargetProductId
|
||
import App.State.SaveTargetVisitedCategory
|
||
import App.State.SelectCategoryId
|
||
import App.State.SelectItem
|
||
import App.State.SetBraintreeToken
|
||
import App.State.SetCoinPaymentProviderUrl
|
||
import App.State.SetInitShopItemId
|
||
import App.State.SetPurchaseCompleted
|
||
import App.State.SetStripeIntentParams
|
||
import App.State.SetupDiscountCategoryByItem
|
||
import App.State.SetupDiscountItem
|
||
import App.State.ShopCard
|
||
import App.State.ShopCardExpired
|
||
import App.State.ShopCardRemoved
|
||
import App.State.ShopCardSold
|
||
import App.State.ShopItem
|
||
import App.State.ToggleShopActivity
|
||
import App.State.TryToBuyOneTimePurchase
|
||
import App.State.AddCoins
|
||
import App.State.AddRubies
|
||
import App.State.SetBatteryLevel
|
||
import App.State.SetClanInfo
|
||
import App.State.SetCoins
|
||
import App.State.SetDisabledWithoutPremiumLocations
|
||
import App.State.SetHasTemporaryCash
|
||
import App.State.SetPremiumExpirationTime
|
||
import App.State.SetRankNames
|
||
import App.State.SetRubies
|
||
import App.State.SetUserPropertiesAction
|
||
import App.State.SetUserId
|
||
import App.State.BattleTeam
|
||
import App.State.ClearInputUid
|
||
import App.State.Fight
|
||
import App.State.Language
|
||
import App.State.PasswordStatus
|
||
import App.State.SetAccountRemovalDays
|
||
import App.State.SetSkipNextPromotedAnnounce
|
||
import App.State.SettingsCategory
|
||
import App.State.SpendCoins
|
||
import App.State.SpendCrystals
|
||
import App.State.SpendRubies
|
||
import App.State.UpdateClanTag
|
||
import App.State.UpdateCrystals
|
||
import App.State.UpdateEventPass
|
||
import App.State.UpdateGearScore
|
||
import App.State.UpdatePremium
|
||
import App.State.UpdateRank
|
||
import App.State.UpdateScore
|
||
import App.State.UpdateUid
|
||
import App.State.UpdateUserRating
|
||
import App.State.UserPropertiesCC
|
||
import App.State.Reducers.AddCategoriesWithBonus
|
||
import App.State.Reducers.BlockBackButton
|
||
import App.State.Reducers.CategoryType
|
||
import App.State.Reducers.ClearPreviousScreen
|
||
import App.State.Reducers.ClearTargetCategory
|
||
import App.State.Reducers.ClearTargetProductId
|
||
import App.State.Reducers.InitPaymentModeAsShopItem
|
||
import App.State.Reducers.MobilePaymentState
|
||
import App.State.Reducers.PaymentProviderFinishFail
|
||
import App.State.Reducers.PaymentProviderFinishSuccess
|
||
import App.State.Reducers.PaymentProviderInProgress
|
||
import App.State.Reducers.PaymentProviderState
|
||
import App.State.Reducers.PaymentUrlReceiveFailed
|
||
import App.State.Reducers.ReduceShop
|
||
import App.State.Reducers.ReduceShopCategories
|
||
import App.State.Reducers.ReduceShopItems
|
||
import App.State.Reducers.RemoveDiscountItemForItem
|
||
import App.State.Reducers.ResetPaymentState
|
||
import App.State.Reducers.ResetShop
|
||
import App.State.Reducers.SelectShopCategory
|
||
import App.State.Reducers.SetGoToHomeAfterBuy
|
||
import App.State.Reducers.SetupDiscountItemForItem
|
||
import App.State.Reducers.ShopCategoriesState
|
||
import App.State.Reducers.ShopItemsState
|
||
import App.State.Reducers.ShopLoaded
|
||
import App.State.Reducers.ShopStateSlice
|
||
import App.State.Reducers.UnblockBackButton
|
||
import App.State.RequestsNumber2FA
|
||
import App.State.SetAccount
|
||
import App.State.SetDaysForNextLinking
|
||
import App.State.SetInviteNeed
|
||
import App.State.SetInviteStatus
|
||
import App.State.SetLocalWeaponResistanceProperty
|
||
import App.State.SetLogin
|
||
import App.State.SetPassword
|
||
import App.State.SetReArmorTemporaryDisabled
|
||
import App.State.SetRememberMe
|
||
import App.State.SetStatus
|
||
import App.State.SetTwitchEnabled
|
||
import App.State.DisplayMainEntranceScreen
|
||
import App.State.DisplayReauthorizationMessage
|
||
import App.State.EntranceScreen
|
||
import App.State.GoTo
|
||
import App.State.ActivatePromoCode
|
||
import App.State.AddNewsItemAction
|
||
import App.State.AddNotification
|
||
import App.State.ChangeEmailStatus
|
||
import App.State.Init as InitNotificationByCountry
|
||
import App.State.InitNotificationSound
|
||
import App.State.NewsItemData
|
||
import App.State.RemoveNewsItemAction
|
||
import App.State.SetCodeValidateStatus
|
||
import App.State.SetCurrentCategory
|
||
import App.State.SetLoadedData
|
||
import App.State.SetNewsItemAction
|
||
import App.State.SetUnreadNewsCounter
|
||
import App.State.SetUrl
|
||
import App.State.SuccessfullyPurchase
|
||
import App.State.UpdateRules
|
||
import App.State.LoaderScreen
|
||
import App.State.NamedScreen
|
||
import App.State.PressedAnyButton
|
||
import App.State.Reducers.ClearScreenStack
|
||
import App.State.Reducers.DelayNextGoTo
|
||
import App.State.Reducers.DisableBack
|
||
import App.State.Reducers.DisableForeground
|
||
import App.State.Reducers.EnableBack
|
||
import App.State.Reducers.EnableForeground
|
||
import App.State.Reducers.FilterScreenStack
|
||
import App.State.Reducers.PopScreen
|
||
import App.State.Reducers.ReduceScreen
|
||
import App.State.Reducers.ResetScreenStackOnNextGoTo
|
||
import App.State.Reducers.ScreenStateSlice
|
||
import App.State.AddKeys
|
||
import App.State.ContainerGivenItem
|
||
import App.State.ContainerType
|
||
import App.State.SelectCategory
|
||
import App.State.SetContainerData
|
||
import App.State.SetCurrentContainer
|
||
import App.State.SetGoToGarage
|
||
import App.State.SetIsAccelerated
|
||
import App.State.SetIsOpening
|
||
import App.State.SetIsShopVisited
|
||
import App.State.SetKeys
|
||
import App.State.SetPossibleRewards
|
||
import App.State.SetRewards
|
||
import App.State.ShownRewards
|
||
import App.State.Reducers.ClearRewards
|
||
import App.State.Reducers.LootBoxOpeningFailed
|
||
import App.State.Reducers.LootBoxesStateSlice
|
||
import App.State.Reducers.ReduceLootBoxes
|
||
import App.State.Reducers.SetIsBlockOpenButtons
|
||
import App.State.Reducers.SetIsReopening
|
||
import App.State.Reducers.SetPeriodOfGiveLootBox
|
||
import App.State.Reducers.SetTimeToGiveLootBox
|
||
import App.State.Devices
|
||
import App.State.AddRelatedBazooka
|
||
import App.State.AddRelatedResistanceModule
|
||
import App.State.AddToFavoriteItem
|
||
import App.State.AddOverdriveDescription
|
||
import App.State.BuyProtectionSlot
|
||
import App.State.GarageAddItemToDepot
|
||
import App.State.GarageApplyResistanceMount
|
||
import App.State.GarageBaseItems
|
||
import App.State.GarageBuyDevice
|
||
import App.State.GarageDevice
|
||
import App.State.GarageDeviceObject
|
||
import App.State.GarageDeviceTrySetInfinityLifetime
|
||
import App.State.GarageDevicesLoadedAction
|
||
import App.State.GarageInsertDeviceClientAndServer
|
||
import App.State.GarageItem
|
||
import App.State.GarageItemModification
|
||
import App.State.GarageItemObject
|
||
import App.State.GarageItemsState
|
||
import App.State.GarageObject
|
||
import App.State.GarageRemoveDevice
|
||
import App.State.GarageRemoveDiscountForDevice
|
||
import App.State.GarageRemoveItemFromDepot
|
||
import App.State.GarageRemoveItemFromStore
|
||
import App.State.GarageResistanceUnMount
|
||
import App.State.GarageSelectAlteration
|
||
import App.State.GarageSelectItem
|
||
import App.State.GarageSelectShotSkin
|
||
import App.State.GarageSelectSkin
|
||
import App.State.GarageUpdateItemCount
|
||
import App.State.GarageUpgradeItemAction
|
||
import App.State.GarageUpgradeItemConfirm
|
||
import App.State.GarageUpgradeItemCoolDownFinished
|
||
import App.State.GarageUpgradeItemCoolDownStarted
|
||
import App.State.OverdriveDescriptionData
|
||
import App.State.RemoveFromFavoriteItem
|
||
import App.State.RemoveFromSale
|
||
import App.State.SetGarageSkinsAction
|
||
import App.State.SetItemRarity
|
||
import App.State.SetRestrictionMount
|
||
import App.State.SwitchingRowsScroll
|
||
import App.State.UpdateDiscount
|
||
import App.State.UpgradableItemParams
|
||
import App.State.AddRequestedItem
|
||
import App.State.Coloring
|
||
import App.State.EmissionColors
|
||
import App.State.GaragePreviewModelCC
|
||
import App.State.HullEngineType
|
||
import App.State.InitGarageResources
|
||
import App.State.ResetItem3dModelActionCache
|
||
import App.State.SetAgeWarnShow
|
||
import App.State.SetCaptureActions
|
||
import App.State.SetDrone
|
||
import App.State.SetExpectedItemId
|
||
import App.State.SetMountedItemsProperties
|
||
import App.State.SetPaint
|
||
import App.State.ShowRequest
|
||
import App.State.TankPartData
|
||
import App.State.TankPreviewSettings
|
||
import App.State.TextureFrameAnimation
|
||
import App.State.TurretMechanism
|
||
import App.State.UpdatePreviewSettings
|
||
import App.State.Reducers.AddGarageItemCount
|
||
import App.State.Reducers.AddNewItem
|
||
import App.State.Reducers.CaptureScreenshot
|
||
import App.State.Reducers.CaptureVideo
|
||
import App.State.Reducers.ClearDroneData
|
||
import App.State.Reducers.ClearGaragePreviewParams
|
||
import App.State.Reducers.ClearGrenadeData
|
||
import App.State.Reducers.ClearHasGrenades
|
||
import App.State.Reducers.ClearInDebt
|
||
import App.State.Reducers.DisableAccountSettings
|
||
import App.State.Reducers.DisableLobbyTutorial
|
||
import App.State.Reducers.ReduceSettings
|
||
import App.State.Reducers.ReduceUser
|
||
import App.State.Reducers.AccountSettingsStateSlice
|
||
import App.State.Reducers.AnnouncementStateSlice
|
||
import App.State.Reducers.ClearAccountRemovalTimer
|
||
import App.State.Reducers.DisableAnnouncement
|
||
import App.State.Reducers.ProBattleStateSlice
|
||
import App.State.Reducers.ReduceAccountSettings
|
||
import App.State.Reducers.ReduceAnnouncement
|
||
import App.State.Reducers.ReduceLanguage
|
||
import App.State.Reducers.ReduceProBattle
|
||
import App.State.Reducers.ResetPasswordFields
|
||
import App.State.Reducers.SetAnnouncementItems
|
||
import App.State.Reducers.SetAnnouncementModelLoaded
|
||
import App.State.Reducers.SetCountry
|
||
import App.State.Reducers.SetCurrentPassword
|
||
import App.State.Reducers.SetCurrentPasswordMandatory
|
||
import App.State.Reducers.SetEmail
|
||
import App.State.Reducers.SetEmailConfirmed
|
||
import App.State.Reducers.SetInDebt
|
||
import App.State.Reducers.SetLanguage
|
||
import App.State.Reducers.SetNewPassword
|
||
import App.State.Reducers.SetPassword as SetBattlePassword
|
||
import App.State.Reducers.SetPasswordStatus
|
||
import App.State.Reducers.SetRepeatNewPassword
|
||
import App.State.Reducers.SetSettingsCategory
|
||
import App.State.Reducers.SetUnlockButtonForRenameUid
|
||
import App.State.Reducers.SettingsStateSlice
|
||
import App.State.ChallengeScreen
|
||
import App.State.Reducers.CloseNews
|
||
import App.State.Reducers.NewsStateSlice
|
||
import App.State.Reducers.NotificationsStateSlice
|
||
import App.State.Reducers.OpenNews
|
||
import App.State.QuestCategoryScreen
|
||
import App.State.QuestSection
|
||
import App.State.Reducers.QuestsStateSlice
|
||
import App.State.Reducers.ReduceNews
|
||
import App.State.Reducers.ReduceNotifications
|
||
import App.State.Reducers.ReduceQuests
|
||
import App.State.Reducers.RemoveFirstNotification
|
||
import App.State.Reducers.RemoveNotifications
|
||
import App.State.Reducers.SetNewsFlag
|
||
import App.State.Reducers.SetNotCompletedQuests
|
||
import App.State.Reducers.AvailabilityByRankStateSlice
|
||
import App.State.Reducers.ClearSupportUrl
|
||
import App.State.Reducers.HideLoader
|
||
import App.State.Reducers.LobbyLoaderStateSlice
|
||
import App.State.Reducers.MarkHomeScreenDialogShown
|
||
import App.State.Reducers.NotificationByCountryStateSlice
|
||
import App.State.Reducers.PromoCodeStateSlice
|
||
import App.State.Reducers.PromoCodeStatus
|
||
import App.State.Reducers.PurchaseDialogStateSlice
|
||
import App.State.Reducers.PurchaseEmailStatus
|
||
import App.State.Reducers.ReduceAvailabilityByRank
|
||
import App.State.Reducers.ReduceLobbyLoader
|
||
import App.State.Reducers.ReduceNotificationByCountry
|
||
import App.State.Reducers.ReducePromoCode
|
||
import App.State.Reducers.ReducePurchaseDialog
|
||
import App.State.Reducers.ReduceRulesUpdated
|
||
import App.State.Reducers.ReduceSupportUrl
|
||
import App.State.Reducers.ResetPurchaseDialog
|
||
import App.State.Reducers.RulesUpdatedStateSlice
|
||
import App.State.Reducers.ShowLoader
|
||
import App.State.Reducers.SupportUrlStateSlice
|
||
import App.State.IconData
|
||
import App.State.SetIcons
|
||
import App.State.Reducers.IconsStateSlice
|
||
import App.State.Reducers.ReduceIcons
|
||
import App.State.QuestCategoryData
|
||
import App.State.QuestCategoryStatistic
|
||
import App.State.RemoveQuestCategory
|
||
import App.State.RequestQuestCategoriesStatistic
|
||
import App.State.SetQuestCategories
|
||
import App.State.SetQuestCategoriesStatistic
|
||
import App.State.Reducers.QuestCategoriesStateSlice
|
||
import App.State.Reducers.ReduceQuestCategories
|
||
import App.State.AddRewardedQuest
|
||
import App.State.MainQuestInfo
|
||
import App.State.QuestsLoaded
|
||
import App.State.SetBattleUpdatedQuests
|
||
import App.State.SetEventPassShopItemId
|
||
import App.State.SetQuests
|
||
import App.State.UpdateQuests
|
||
import App.State.Reducers.MainQuestStateSlice
|
||
import App.State.Reducers.ReduceMainQuest
|
||
import App.State.Reducers.ResetQuestsLoaded
|
||
import App.State.SetProfileBlockStatus
|
||
import App.State.Reducers.ReduceSecuritySettings
|
||
import App.State.Reducers.SecuritySettingsStateSlice
|
||
import App.State.ReferralEventUserModelCC
|
||
import App.State.SetPromoCode
|
||
import App.State.SetReferralEventData
|
||
import App.State.SetReferrals
|
||
import App.State.SetReferrer
|
||
import App.State.Reducers.ReduceReferralEvent
|
||
import App.State.Reducers.ReferralEventStateSlice
|
||
import App.State.UpdateTips
|
||
import App.State.Reducers.ReduceTips
|
||
import App.State.Reducers.TipsStateSlice
|
||
import App.State.ShowAvailableItems
|
||
import App.State.Reducers.RankUpDialogStateSlice
|
||
import App.State.Reducers.ReduceRankUpDialog
|
||
import App.State.Reducers.ResetRankUpDialog
|
||
import App.State.DoInit
|
||
import App.State.SetHasBattleResult
|
||
import App.State.Reducers.BattleResultStateSlice
|
||
import App.State.Reducers.ReduceBattleResult
|
||
import App.State.Reducers.ResetBattleRewards
|
||
import App.State.Reducers.SetIncreasedReward
|
||
import App.State.Reducers.SetSimpleReward
|
||
import App.State.LocationsUpdated
|
||
import App.State.SetLocations
|
||
import App.State.Reducers.AddCaptchaLocationToReset
|
||
import App.State.Reducers.AddVerifiedCaptchaLocation
|
||
import App.State.Reducers.CaptchaStateSlice
|
||
import App.State.Reducers.ReduceCaptcha
|
||
import App.State.Reducers.RemoveCaptchaLocationToReset
|
||
import App.State.Reducers.RemoveVerifiedCaptchaLocation
|
||
import App.State.Reducers.ShowFakeCaptcha
|
||
import App.State.AddModal
|
||
import App.State.RemoveCurrentByName
|
||
import App.State.RemoveModal
|
||
import App.State.Reducers.CloseCurrentModal
|
||
import App.State.Reducers.ModalScreenStateSlice
|
||
import App.State.Reducers.ReduceModalScreen
|
||
import App.State.Reducers.DroppableGoldStateSlice
|
||
import App.State.Reducers.HideDroppableGoldAuthor
|
||
import App.State.Reducers.ReduceDroppableGold
|
||
import App.State.Reducers.ShowDroppableGoldAuthor
|
||
import App.State.InitEventGoods
|
||
import App.State.Reducers.EventGoodsStateSlice
|
||
import App.State.Reducers.ReduceEventGoods
|
||
import App.State.SetAttachedCreatorTag
|
||
import App.State.SetCreatorTag
|
||
import App.State.SetCreatorTags
|
||
import App.State.Reducers.ClearCreatorTag
|
||
import App.State.Reducers.CreatorTagStateSlice
|
||
import App.State.Reducers.ReduceCreatorTag
|
||
import App.State.SetDisplayState
|
||
import App.State.Reducers.ReduceRewardedVideos
|
||
import App.State.Reducers.RewardVideoDisplayState
|
||
import App.State.Reducers.RewardedVideosStateSlice
|
||
import App.State.SetPasswordChangeHash
|
||
import App.State.ShowEmailAndPasswordChangeDialog
|
||
import App.State.Reducers.PasswordChangeStateSlice
|
||
import App.State.Reducers.ReducePasswordChange
|
||
import App.State.DoShowMessage
|
||
import App.State.SetFatalError
|
||
import App.State.Reducers.SystemStateSlice
|
||
import App.State.Reducers.ReduceSystem
|
||
import App.State.LoadCurrentElement
|
||
import App.State.MarkElementAsViewed
|
||
import App.State.SetLoadedElements
|
||
import App.State.SetMissionToElement
|
||
import App.State.SetUnViewedElements
|
||
import App.State.Reducers.ContextTutorialStateSlice
|
||
import App.State.Reducers.ReduceContextTutorial
|
||
import App.State.SetFragsCount
|
||
import App.State.ShowMessage
|
||
import App.State.Reducers.BattleTutorialStateSlice
|
||
import App.State.Reducers.MarkMovementStarted
|
||
import App.State.Reducers.ReduceBattleTutorial
|
||
import App.State.Reducers.ResetBattleTutorial
|
||
import App.State.SetSpectator
|
||
import App.State.UpdateHudLayout
|
||
import App.State.SpectatorInfo
|
||
import App.State.Reducers.AddSpectator
|
||
import App.State.Reducers.ReduceSpectator
|
||
import App.State.Reducers.RemoveSpectator
|
||
import App.State.Reducers.SetSpectators
|
||
import App.State.Reducers.SetSpectatorsListVisible
|
||
import App.State.Reducers.SpectatorStateSlice
|
||
import App.State.ActivationStatus2FA
|
||
import App.State.Enable2FaRequest
|
||
import App.State.SetSecretAndQRCode
|
||
import App.State.SetUse2FA
|
||
import App.State.VerificationStatus
|
||
import App.State.Reducers.ReduceTwoStepVerification
|
||
import App.State.Reducers.TwoStepVerificationStateSlice
|
||
import App.State.SetRealNameAndIdNumber
|
||
import App.State.SetValidateAntiAddictionUserDataStatus
|
||
import App.State.Reducers.AntiAddictionStateSlice
|
||
import App.State.Reducers.EnableRealNameCheck
|
||
import App.State.Reducers.ReduceAntiAddiction
|
||
import App.State.Reducers.RequireUserIdVerification
|
||
import App.State.Reducers.UpdatePlayingTime
|
||
import App.State.Reducers.AddSelectedUser
|
||
import App.State.Reducers.AddSelectedUserFromChat
|
||
import App.State.Reducers.ClearSelectedUsers
|
||
import App.State.Reducers.HideBattleInfo
|
||
import App.State.Reducers.HideBlockUserMenu
|
||
import App.State.Reducers.HideModerationSystemMessage
|
||
import App.State.Reducers.ModerationStateSlice
|
||
import App.State.Reducers.ModerationUserRecord
|
||
import App.State.Reducers.ReduceModeration
|
||
import App.State.Reducers.RemoveSelectedUser
|
||
import App.State.Reducers.ResetSelectedUsers
|
||
import App.State.Reducers.SetModerationUrlTemplates
|
||
import App.State.Reducers.SetModeratorModes
|
||
import App.State.Reducers.ShowBattleInfo
|
||
import App.State.Reducers.ShowBlockUserMenu
|
||
import App.State.Reducers.ShowModerationSystemMessage
|
||
import App.State.EnteredUidIsBusy
|
||
import App.State.ForceSetUid
|
||
import App.State.SetUid
|
||
import App.State.ReceiveOAuthUri
|
||
import App.State.SetOpenedPassword
|
||
import App.State.SetPasswordRestrictions
|
||
import App.State.SocialNetworkAvailability
|
||
import App.State.SocialNetworkClientIds
|
||
import App.State.Reducers.DisableRegister
|
||
import App.State.Reducers.MigrationLoginStatus
|
||
import App.State.Reducers.MigrationRegisterStatus
|
||
import App.State.Reducers.MigrationStateSlice
|
||
import App.State.Reducers.ReduceMigrationState
|
||
import App.State.Reducers.ReduceRegister
|
||
import App.State.Reducers.RegisterFailed
|
||
import App.State.Reducers.RegisterPasswordCorrect
|
||
import App.State.Reducers.RegisterPasswordIncorrect
|
||
import App.State.Reducers.RegisterStateSlice
|
||
import App.State.Reducers.RegistrationStatus
|
||
import App.State.Reducers.ResetAuthenticationForm
|
||
import App.State.Reducers.ResetRegisterStatus
|
||
import App.State.Reducers.ResetUidStatus
|
||
import App.State.Reducers.UidIsFree
|
||
import App.State.Reducers.UidIsIncorrect
|
||
import App.State.Reducers.UidStatus
|
||
import App.State.Reducers.UidTooShort
|
||
import App.State.InitUserReports
|
||
import App.State.ReportBattleChatMessage
|
||
import App.State.ReportLobbyChatMessage
|
||
import App.State.ReportProBattleName
|
||
import App.State.SetAvailableGameReports
|
||
import App.State.SetAvailableReports
|
||
import App.State.Reducers.ReduceUserReports
|
||
import App.State.Reducers.ReportEntry
|
||
import App.State.Reducers.ReportKind
|
||
import App.State.Reducers.UserReportsStateSlice
|
||
import App.State.ChangeFriendAddFindState
|
||
import App.State.AddUsersToAccepted
|
||
import App.State.AddUsersToIncoming
|
||
import App.State.AddUsersToNewAccepted
|
||
import App.State.AddUsersToNewIncoming
|
||
import App.State.RemoveUserFromAccepted
|
||
import App.State.RemoveUserFromIncoming
|
||
import App.State.RemoveUserFromNewAccepted
|
||
import App.State.RemoveUsersFromNewIncoming
|
||
import App.State.SetLimits
|
||
import App.State.SetValidationStatus
|
||
import App.State.Reducers.ClearContextMenuUser
|
||
import App.State.Reducers.FriendSearchState
|
||
import App.State.Reducers.FriendsStateSlice
|
||
import App.State.Reducers.ReduceFriends
|
||
import App.State.Reducers.SetContextMenuUser
|
||
import App.State.Reducers.ReduceSupplies
|
||
import App.State.Reducers.SetSupplyCount
|
||
import App.State.Reducers.SuppliesStateSlice
|
||
import App.State.ClanNotifierData
|
||
import App.State.Loaded
|
||
import App.State.Reducers.ClanUserInfoStateSlice
|
||
import App.State.Reducers.ReduceClanUserInfo
|
||
import App.State.EnableSetBattleInviteNotifications
|
||
import App.State.InvitationResponse
|
||
import App.State.RemoveUserFromInvited
|
||
import App.State.SelectUserToInvite
|
||
import App.State.Reducers.AddInvitedUser
|
||
import App.State.Reducers.BattleInviteStateSlice
|
||
import App.State.Reducers.ClearSelectedForInvite
|
||
import App.State.Reducers.ReduceBattleInvite
|
||
import App.State.InitPartnerInviteAction
|
||
import App.State.InitSocialNetworkParameters
|
||
import App.State.SetLoginParams
|
||
import App.State.SetLoginStatusForBinding
|
||
import App.State.SetPartnerId
|
||
import App.State.SetPartnerParameters
|
||
import App.State.SetPartnerReady
|
||
import App.State.Reducers.MarkAccountLinked
|
||
import App.State.Reducers.MarkAccountNotLinked
|
||
import App.State.Reducers.PartnerStateSlice
|
||
import App.State.Reducers.ReducePartner
|
||
import App.State.Reducers.SetPartnerItemEnabled
|
||
import App.State.Reducers.UpdatePartnerInviteAction
|
||
import App.State.ChangeUidInputState
|
||
import App.State.ResponseActual
|
||
import App.State.ResponseDelivered
|
||
import App.State.SetChangeUidParams
|
||
import App.State.SetRubiesForRename
|
||
import App.State.Reducers.HideLinkErrorWarning
|
||
import App.State.Reducers.MarkFreeRenameAvailable
|
||
import App.State.Reducers.MarkRenameFailed
|
||
import App.State.Reducers.MarkRenameSucceeded
|
||
import App.State.Reducers.ReduceUserRename
|
||
import App.State.Reducers.ResetRenameDialog
|
||
import App.State.Reducers.RenameUidState
|
||
import App.State.Reducers.UserRenameStateSlice
|
||
import App.State.OnPauseMode
|
||
import App.State.SetIdleKickPeriod
|
||
import App.State.Reducers.BattlePauseStateSlice
|
||
import App.State.Reducers.CancelAutoPause
|
||
import App.State.Reducers.ReduceBattlePause
|
||
import App.State.Reducers.ResetBattlePause
|
||
import App.State.SetElementsVisibility
|
||
import App.State.SetVisibility
|
||
import App.State.Reducers.BattleViewStateSlice
|
||
import App.State.Reducers.ClearInvisibleElements
|
||
import App.State.Reducers.DisableBattleInput
|
||
import App.State.Reducers.DisableBattleMenu
|
||
import App.State.Reducers.DisableBattleStatistics
|
||
import App.State.Reducers.EnableBattleInput
|
||
import App.State.Reducers.EnableBattleMenu
|
||
import App.State.Reducers.EnableBattleStatistics
|
||
import App.State.Reducers.EnterBackground
|
||
import App.State.Reducers.HideBattleView
|
||
import App.State.Reducers.LeaveBackground
|
||
import App.State.Reducers.ReduceBattleView
|
||
import App.State.Reducers.ResetBattleView
|
||
import App.State.Reducers.ShowBattleView
|
||
import App.State.OnUserJoinTheBattle
|
||
import App.State.OnUserLeaveTheBattle
|
||
import App.State.UpdateLog
|
||
import App.State.Reducers.ReduceTankActionsLog
|
||
import App.State.Reducers.ResetTankActionsLog
|
||
import App.State.Reducers.TankActionsLogStateSlice
|
||
import App.State.ActivatedInCurrentBattle
|
||
import App.State.SetLoadedElement
|
||
import App.State.Reducers.ReduceSuppliesTutorial
|
||
import App.State.Reducers.SuppliesTutorialStateSlice
|
||
import App.State.Configure
|
||
import App.State.DoAddMessages
|
||
import App.State.Reducers.ClanChatStateSlice
|
||
import App.State.Reducers.CloseClanChat
|
||
import App.State.Reducers.OpenClanChat
|
||
import App.State.Reducers.ReduceClanChat
|
||
import App.State.Reducers.ResetClanChat
|
||
import App.State.BattleFormat
|
||
import App.State.EnterToBattle
|
||
import App.State.RemoveBattle
|
||
import App.State.SelectBattle
|
||
import App.State.SelectBattleFormat
|
||
import App.State.SetFilter
|
||
import App.State.SetSearch
|
||
import App.State.SetSuspiciousFilter
|
||
import App.State.Reducers.BattleSearchStateSlice
|
||
import App.State.Reducers.ClearSelectedBattle
|
||
import App.State.Reducers.ReduceBattleSearch
|
||
import App.State.InitBattleModeWithLevels
|
||
import App.State.Range
|
||
import App.State.SetBattleMode
|
||
import App.State.SetDisplayClientInfo
|
||
import App.State.SetFlagOrBallOwner
|
||
import App.State.SetRankRange
|
||
import App.State.SetReArmor
|
||
import App.State.SetRemainingTime
|
||
import App.State.SetRoundRestartTime
|
||
import App.State.UpdateFund
|
||
import App.State.Reducers.BattleStatisticsStateSlice
|
||
import App.State.Reducers.BeginRound
|
||
import App.State.Reducers.ClearFlagOrBallOwner
|
||
import App.State.Reducers.CloseStatMenu
|
||
import App.State.Reducers.ContinueBattle
|
||
import App.State.Reducers.HideStat
|
||
import App.State.Reducers.InitBattle
|
||
import App.State.Reducers.MarkBattleLoaded
|
||
import App.State.Reducers.MarkBattleUnloaded
|
||
import App.State.Reducers.ReduceBattleStatistics
|
||
import App.State.Reducers.ResetBattleStatistics
|
||
import App.State.Reducers.ResetRoundState
|
||
import App.State.Reducers.ShowStat
|
||
import App.State.Reducers.ToggleStatMenu
|
||
import App.State.ChangeAction
|
||
import App.State.RemoveAction
|
||
import App.State.ResetAction
|
||
import App.State.Reducers.ApplyGameSettings
|
||
import App.State.Reducers.ApplySoundSettings
|
||
import App.State.Reducers.ApplyTouchscreenHudSettings
|
||
import App.State.Reducers.EnableEntranceSound
|
||
import App.State.Reducers.GameSettingsStateSlice
|
||
import App.State.Reducers.KeyboardSettingsStateSlice
|
||
import App.State.Reducers.ReduceGameSettings
|
||
import App.State.Reducers.ReduceKeyboardSettings
|
||
import App.State.Reducers.ReduceSoundSettings
|
||
import App.State.Reducers.ReduceTouchscreenHudSettings
|
||
import App.State.Reducers.ResetKeyMappingFromDefaults
|
||
import App.State.Reducers.ResetSettingsFromStorage
|
||
import App.State.Reducers.ResetTouchscreenHudMapping
|
||
import App.State.Reducers.SetEntranceSoundSettings
|
||
import App.State.Reducers.SoundSettingsStateSlice
|
||
import App.State.Reducers.TouchscreenHudSettingsStateSlice
|
||
import App.State.Reducers.ApplyBattleSettings
|
||
import App.State.Reducers.ApplyControlSettings
|
||
import App.State.Reducers.ApplyGraphicsSettings
|
||
import App.State.Reducers.BattleSettingsStateSlice
|
||
import App.State.Reducers.ControlGamepadSettings
|
||
import App.State.Reducers.ControlMouseSettings
|
||
import App.State.Reducers.ControlSettingsStateSlice
|
||
import App.State.Reducers.ControlTouchscreenSettings
|
||
import App.State.Reducers.GraphicsSettingsStateSlice
|
||
import App.State.Reducers.ReduceBattleSettings
|
||
import App.State.Reducers.ReduceControlSettings
|
||
import App.State.Reducers.ReduceGraphicsSettings
|
||
import App.State.SetActiveContract
|
||
import App.State.SetBattleProgress
|
||
import App.State.SetContract
|
||
import App.State.UpdateProgress
|
||
import App.State.Reducers.ClaimContractReward
|
||
import App.State.Reducers.ContractEntry
|
||
import App.State.Reducers.ContractStateSlice
|
||
import App.State.Reducers.ReduceContractState
|
||
import App.State.Reducers.RemoveCompletedContracts
|
||
import App.State.SetKode
|
||
import App.State.SetParts
|
||
import App.State.SetPartsInfo
|
||
import App.State.Reducers.ReduceTankBuilder
|
||
import App.State.Reducers.ResetTankBuilder
|
||
import App.State.Reducers.TankBuilderPaintInfo
|
||
import App.State.Reducers.TankBuilderPart
|
||
import App.State.Reducers.TankBuilderPartInfo
|
||
import App.State.Reducers.TankBuilderStateSlice
|
||
import App.State.DoAddLocalizedMessage
|
||
import App.State.DoAddLocalizedTeamMessage
|
||
import App.State.DoAddLocalizedUserMessage
|
||
import App.State.DoAddTextMessage
|
||
import App.State.HideMessage
|
||
import App.State.RemoveMessage
|
||
import App.State.Reducers.BattleMessageEntry
|
||
import App.State.Reducers.BattleMessageStateSlice
|
||
import App.State.Reducers.LocalizedMessage
|
||
import App.State.Reducers.LocalizedTeamMessage
|
||
import App.State.Reducers.LocalizedUserMessage
|
||
import App.State.Reducers.ReduceBattleMessage
|
||
import App.State.Reducers.ResetBattleMessages
|
||
import App.State.Reducers.TextMessage
|
||
import App.State.AddMap
|
||
import App.State.MapInfoCC
|
||
import App.State.Reducers.MapsDescriptionEntry
|
||
import App.State.Reducers.MapsStateSlice
|
||
import App.State.Reducers.MapsThemeEntry
|
||
import App.State.Reducers.ReduceMaps
|
||
import App.State.AddMessage
|
||
import App.State.ChatLoaded
|
||
import App.State.ChatMessage
|
||
import App.State.Reducers.BattleChatStateSlice
|
||
import App.State.Reducers.CloseBattleChatInput
|
||
import App.State.Reducers.OpenBattleChatInput
|
||
import App.State.Reducers.ReduceBattleChat
|
||
import App.State.Reducers.ResetBattleChat
|
||
import App.State.Reducers.ToggleBattleChatHistory
|
||
import App.State.BattleNotifierData
|
||
import App.State.RemoteUserData
|
||
import App.State.Reducers.ReduceRemoteUserDataStorage
|
||
import App.State.Reducers.RemoteUserDataStorageStateSlice
|
||
import App.State.UpdateClanTags
|
||
import App.State.UpdateOnlineStatus
|
||
import App.State.UpdateRanks
|
||
import App.State.UpdateUids
|
||
import App.State.UserEnterBattle
|
||
import App.State.UserLeaveBattle
|
||
import App.State.UserLeaveGroup
|
||
import App.State.EnterToBattleApply
|
||
import App.State.MatchmakingModeRank
|
||
import App.State.Play
|
||
import App.State.SelectMatchmakingMode
|
||
import App.State.SetRankIndexToMinGS
|
||
import App.State.WasRegistered
|
||
import App.State.Reducers.CancelLoadingBattle
|
||
import App.State.Reducers.MatchmakingStateSlice
|
||
import App.State.Reducers.ReduceMatchmaking
|
||
import App.State.Reducers.ResetMatchmakingProgress
|
||
import App.State.Reducers.StartLoadingBattle
|
||
import App.State.AddBattle
|
||
import App.State.AddUser
|
||
import App.State.AddUsers
|
||
import App.State.BattleCreateParameters
|
||
import App.State.BattleInfoUser
|
||
import App.State.BattleParams
|
||
import App.State.ChangeName
|
||
import App.State.EnterToBattleInfo
|
||
import App.State.InitProBattleRewardLimit
|
||
import App.State.LimitReached
|
||
import App.State.ProBattleRewardLimitData
|
||
import App.State.RemoveUser
|
||
import App.State.RoundFinish
|
||
import App.State.RoundStarted
|
||
import App.State.SetReEnterProtectBattleId
|
||
import App.State.SwapTeam
|
||
import App.State.UpdateSuspicion
|
||
import App.State.UpdateTeamScore
|
||
import App.State.UpdateUserScore
|
||
import App.State.UpdateUserSuspicion
|
||
import App.State.Reducers.BattlesSelectStateSlice
|
||
import App.State.Reducers.MarkBattlesLoaded
|
||
import App.State.Reducers.ReduceBattlesSelect
|
||
import App.State.Reducers.ResetBattlesSelect
|
||
import App.State.InitSuspiciousState
|
||
import App.State.InitUserLevels
|
||
import App.State.SetGearScore
|
||
import App.State.SetTankInfo
|
||
import App.State.SetTankResistance
|
||
import App.State.TankInfoCC
|
||
import App.State.TankResistance
|
||
import App.State.UpdateUserLevel
|
||
import App.State.UpdateUserRank
|
||
import App.State.UpdateUserScoreKillsAndDeaths
|
||
import App.State.UpdateUserSuspiciousState
|
||
import App.State.UpdateUsersClanTag
|
||
import App.State.UpdateUsersScoreKillsAndDeaths
|
||
import App.State.Reducers.AddBattleUser
|
||
import App.State.Reducers.AddBattleUsers
|
||
import App.State.Reducers.BattleUsersStateSlice
|
||
import App.State.Reducers.MigrateBattleUserTeams
|
||
import App.State.Reducers.ReduceBattleUsers
|
||
import App.State.Reducers.RemoveOnlineUser
|
||
import App.State.Reducers.ResetBattleUsers
|
||
import App.State.Reducers.SetUserPremium
|
||
import App.State.RemoveTankInfo
|
||
import App.State.UserStat
|
||
import App.State.Challenge
|
||
import App.State.RemoveChallenge
|
||
import App.State.SetBattlePass
|
||
import App.State.SetBattlePassPackages
|
||
import App.State.SetChallenge
|
||
import App.State.SetRewardForDialog
|
||
import App.State.SetTakenBattlePassRewardIndex
|
||
import App.State.SetTakenFreeRewardIndex
|
||
import App.State.UpdateStars
|
||
import App.State.Reducers.ChallengesStateSlice
|
||
import App.State.Reducers.ReduceChallenges
|
||
import App.State.Reducers.RefreshBattlePasses
|
||
import App.State.ClanEditPriceCC
|
||
import App.State.ClanInfo
|
||
import App.State.ClanUnloaded
|
||
import App.State.BuyAdditionalPlace
|
||
import App.State.ForeignClanLoaded
|
||
import App.State.MinimalRankChanged
|
||
import App.State.RecruitmentChanged
|
||
import App.State.ShowForeignClan
|
||
import App.State.UpdateClan
|
||
import App.State.Reducers.AddClanUser
|
||
import App.State.Reducers.LoadClanIntoStorage
|
||
import App.State.Reducers.ClanStorageStateSlice
|
||
import App.State.Reducers.ClearForeignClan
|
||
import App.State.Reducers.ReduceClanStorage
|
||
import App.State.Reducers.RemoveClanUser
|
||
import App.State.AddIncomingClan
|
||
import App.State.AddNewIncoming
|
||
import App.State.AddNewIncomingClan
|
||
import App.State.AddOutgoingClan
|
||
import App.State.AcceptedUserViewed
|
||
import App.State.ClanBanned
|
||
import App.State.ClanModalVisible
|
||
import App.State.IncomingAdded
|
||
import App.State.IncomingRemoved
|
||
import App.State.IncomingUserViewed
|
||
import App.State.MembersFetched
|
||
import App.State.NewAcceptedUserNotification
|
||
import App.State.NewClanInviteNotification
|
||
import App.State.NewIncomingUserNotification
|
||
import App.State.OutgoingAdded
|
||
import App.State.OutgoingRemoved
|
||
import App.State.ClanInfoLoaded
|
||
import App.State.RemoveIncomingClan
|
||
import App.State.RemoveNewIncoming
|
||
import App.State.RemoveNewIncomingClan
|
||
import App.State.RemoveOutgoingClan
|
||
import App.State.SetClanEditPrice
|
||
import App.State.SetMinRankForCreate
|
||
import App.State.UpdateClanLicense
|
||
import App.State.UpdateClanNameValidation
|
||
import App.State.UpdateHasClan
|
||
import App.State.UpdateTimeToJoinRestriction
|
||
import App.State.UpdateValidation
|
||
import App.State.Reducers.ClanStateSlice
|
||
import App.State.Reducers.DecrementClanNotifications
|
||
import App.State.Reducers.IncrementClanNotifications
|
||
import App.State.Reducers.ReduceClan
|
||
import App.State.Reducers.ResetClanNameTagValidation
|
||
import App.State.Reducers.ResetClanUserValidation
|
||
import App.State.Reducers.SetClanNotificationsCount
|
||
import App.State.Reducers.UpdateIntermediateDescription
|
||
import App.State.Reducers.UpdateIntermediateMinRank
|
||
import App.State.Reducers.UpdateIntermediateRequests
|
||
import App.State.ChangeChannel
|
||
import App.State.CleanUsersMessage
|
||
import App.State.IgnoreUserMessages
|
||
import App.State.SaveChatMessage
|
||
import App.State.SendMessageToServer
|
||
import App.State.SetMessages
|
||
import App.State.SetSelectedUid
|
||
import App.State.ShowContextMenu
|
||
import App.State.ShowUserMessages
|
||
import App.State.UpdateTypingSpeedAntifloodParams
|
||
import App.State.UpdateUsers
|
||
import App.State.Reducers.CancelHideChatKeyboard
|
||
import App.State.Reducers.ChatStateSlice
|
||
import App.State.Reducers.ClearChatContextMenu
|
||
import App.State.Reducers.CloseChat
|
||
import App.State.Reducers.DisableChatScroll
|
||
import App.State.Reducers.EnableChatScroll
|
||
import App.State.Reducers.HideChatKeyboard
|
||
import App.State.Reducers.MarkFirstChannelMessagesLoaded
|
||
import App.State.Reducers.OpenChat
|
||
import App.State.Reducers.ReduceChat
|
||
import App.State.Reducers.RequireHideChatKeyboard
|
||
import App.State.Reducers.ResetChat
|
||
import App.State.Reducers.SetChatConfig
|
||
import App.State.Reducers.ShowChatKeyboard
|
||
import App.State.AddItemNotification
|
||
import App.State.AddItemNotifications
|
||
import App.State.ItemNotificationData
|
||
import App.State.RemoveItemNotification
|
||
import App.State.UpdateItemNotification
|
||
import App.State.Reducers.ItemNotificationsStateSlice
|
||
import App.State.Reducers.PersistItemNotifications
|
||
import App.State.Reducers.ReduceItemNotifications
|
||
import App.State.AcceptedInviteToGroup
|
||
import App.State.AddUserToSelected
|
||
import App.State.InitMatchmakingGroup
|
||
import App.State.Invite
|
||
import App.State.RejectedInviteToGroup
|
||
import App.State.RemoveUserToSelected
|
||
import App.State.SetAllowedFromFriends
|
||
import App.State.SetSelectedUsers
|
||
import App.State.UpdateMountItems
|
||
import App.State.UpdateUserReadiness
|
||
import App.State.UserRemoved
|
||
import App.State.Reducers.AddGroupMember
|
||
import App.State.Reducers.MatchmakingGroupMember
|
||
import App.State.Reducers.MatchmakingGroupStateSlice
|
||
import App.State.Reducers.ReduceMatchmakingGroup
|
||
import App.State.Reducers.ResetMatchmakingGroup
|
||
import App.State.Reducers.UserRankBounds
|
||
import App.State.Reducers.UserStateSlice
|
||
import App.State.Reducers.ClearHullData
|
||
import App.State.Reducers.ClearUpgradeItemInProcessing
|
||
import App.State.Reducers.ClearWaitServerItemMount
|
||
import App.State.Reducers.ClearWeaponData
|
||
import App.State.Reducers.GarageDefaultCategory
|
||
import App.State.Reducers.GarageLoaded
|
||
import App.State.Reducers.GarageMountItem
|
||
import App.State.Reducers.GarageMountItems
|
||
import App.State.Reducers.GaragePreviewStateSlice
|
||
import App.State.Reducers.GarageResistanceCategoryKey
|
||
import App.State.Reducers.GarageStateSlice
|
||
import App.State.Reducers.GarageUnmountItem
|
||
import App.State.Reducers.HideGaragePreview
|
||
import App.State.Reducers.HideVideoInDescription
|
||
import App.State.Reducers.PreviewDroneCategory
|
||
import App.State.Reducers.PreviewHullCategory
|
||
import App.State.Reducers.PreviewPaintCategory
|
||
import App.State.Reducers.PreviewWeaponCategory
|
||
import App.State.Reducers.ReduceGarage
|
||
import App.State.Reducers.EntranceStateSlice
|
||
import App.State.Reducers.HideSocialNetworks
|
||
import App.State.Reducers.ReduceEntrance
|
||
import App.State.Reducers.ReduceGaragePreview
|
||
import App.State.Reducers.RemoveNewItem
|
||
import App.State.Reducers.SetAuthorizedByChinaSdkToken
|
||
import App.State.Reducers.SetClassic
|
||
import App.State.Reducers.SetControlsForChina
|
||
import App.State.Reducers.SetRunningInChinaDataCenter
|
||
import App.State.Reducers.SetRunningInChinaPartners
|
||
import App.State.Reducers.RemoveWaitServerItemMount
|
||
import App.State.Reducers.ResetGarage
|
||
import App.State.Reducers.ResetMountDelay
|
||
import App.State.Reducers.SelectGarageCategory
|
||
import App.State.Reducers.SetCooldownReduceEndTime
|
||
import App.State.Reducers.SetGarageNotLoaded
|
||
import App.State.Reducers.SetGarageObject
|
||
import App.State.Reducers.SetGrenadeData
|
||
import App.State.Reducers.SetHasGrenades
|
||
import App.State.Reducers.SetHullData
|
||
import App.State.Reducers.SetItemsOnDepot
|
||
import App.State.Reducers.SetItemsOnMarket
|
||
import App.State.Reducers.SetMountedItems
|
||
import App.State.Reducers.SetMountedShotSkinForItems
|
||
import App.State.Reducers.SetMountedSkinForItems
|
||
import App.State.Reducers.SetNewItems
|
||
import App.State.Reducers.SetResistanceReadyToMount
|
||
import App.State.Reducers.SetWeaponData
|
||
import App.State.Reducers.ShowGarageDiscountNotification
|
||
import App.State.Reducers.ShowGaragePreview
|
||
import App.State.Reducers.StartCooldownReduce
|
||
import App.State.Reducers.UpdateViewStyle
|
||
import State.InitState
|
||
|
||
private data class AddFrags(val fragsCount: Int)
|
||
private data class SetScreen(val screen: String)
|
||
|
||
private data class UiState(val Frags: Int = 0, val Screen: String = "Preloader")
|
||
|
||
private val ExitWith: (Int) -> Unit = js("(function (C) { process.exit(C); })")
|
||
|
||
/** 按顶层逗号切分(尊重圆括号/方括号/花括号)。 */
|
||
private fun SplitTop(Text: String): List<String> {
|
||
val Out = ArrayList<String>()
|
||
var Depth = 0
|
||
val Cur = StringBuilder()
|
||
for (C in Text) {
|
||
when (C) {
|
||
'(', '[', '{' -> { Depth++; Cur.append(C) }
|
||
')', ']', '}' -> { Depth--; Cur.append(C) }
|
||
',' -> if (Depth == 0) { Out.add(Cur.toString()); Cur.clear() } else Cur.append(C)
|
||
else -> Cur.append(C)
|
||
}
|
||
}
|
||
if (Cur.isNotBlank()) Out.add(Cur.toString())
|
||
return Out
|
||
}
|
||
|
||
/** 运行时值形态是否与生成类里声明的类型相符(只做能一锤定音的判断)。 */
|
||
private fun ValueFits(Type: String, Value: String): Boolean {
|
||
val V = Value.trim()
|
||
if (V.isEmpty()) return true
|
||
// null 只允许出现在可空类型上
|
||
if (V == "null") return Type == "Any?" || Type.endsWith("?")
|
||
return when {
|
||
Type == "Any?" || Type == "Any" -> true
|
||
Type == "Boolean" -> V == "true" || V == "false"
|
||
Type == "Int" || Type == "Long" -> Regex("-?\\d+").matches(V)
|
||
Type == "Double" -> Regex("-?\\d+(\\.\\d+)?([eE][-+]?\\d+)?").matches(V)
|
||
Type == "String" -> true
|
||
Type.startsWith("List<") -> V.startsWith("[")
|
||
Type.startsWith("Map<") -> V.startsWith("{")
|
||
else -> true
|
||
}
|
||
}
|
||
|
||
object StateCheck {
|
||
|
||
fun Wanted(): Boolean = js("process.env.TANKIOFFLINE_STATE_TEST === '1'") as Boolean
|
||
|
||
fun Run() {
|
||
val Results = ArrayList<Pair<String, Boolean>>()
|
||
fun Check(Name: String, Condition: Boolean) { Results.add(Name to Condition) }
|
||
|
||
// ① Store:归约 + 监听器
|
||
run {
|
||
val Store = ReduxStore(UiState(), { S, A ->
|
||
when (A) {
|
||
is AddFrags -> S.copy(Frags = S.Frags + A.fragsCount)
|
||
is SetScreen -> S.copy(Screen = A.screen)
|
||
else -> S
|
||
}
|
||
})
|
||
var Notified = 0
|
||
var Seen = -1
|
||
Store.Subscribe(StoreListener { Notified++; Seen = Store.State.Frags })
|
||
Store.Dispatch(AddFrags(3))
|
||
Store.Dispatch(AddFrags(4))
|
||
Check("Store 归约:两次派发累加", Store.State.Frags == 7)
|
||
Check("Store 通知:每次状态变化通知一次", Notified == 2)
|
||
Check("Store 通知:监听器看到的是新状态", Seen == 7)
|
||
}
|
||
|
||
// ② 中间件顺序
|
||
run {
|
||
val Log = ArrayList<String>()
|
||
val First = Middleware<UiState> { _, Next ->
|
||
{ A -> Log.add("First.In"); val R = Next(A); Log.add("First.Out"); R }
|
||
}
|
||
val Second = Middleware<UiState> { _, Next ->
|
||
{ A -> Log.add("Second.In"); val R = Next(A); Log.add("Second.Out"); R }
|
||
}
|
||
val Store = ReduxStore(UiState(), { S, A -> if (A is AddFrags) S.copy(Frags = S.Frags + A.fragsCount) else S }, listOf(First, Second))
|
||
Store.Dispatch(AddFrags(1))
|
||
Check("中间件:靠后的包在更外层", Log == listOf("Second.In", "First.In", "First.Out", "Second.Out"))
|
||
}
|
||
|
||
// ③ 网关:动作监听器先于状态更新
|
||
run {
|
||
val Store = ReduxStore(UiState(), { S, A ->
|
||
if (A is AddFrags) S.copy(Frags = S.Frags + A.fragsCount) else S
|
||
})
|
||
val Gateway = ReduxGateway(Store) { Task -> Task() }
|
||
var FragsAtListener = -1
|
||
Gateway.OnAction(AddFrags::class) { FragsAtListener = Gateway.StoreState.Frags }
|
||
Gateway.Dispatch(AddFrags(5))
|
||
Check("网关:动作监听器在归约之前触发", FragsAtListener == 0)
|
||
Check("网关:派发后状态已更新", Gateway.StoreState.Frags == 5)
|
||
}
|
||
|
||
// ④ 模型订阅 / 退订 / 重复订阅
|
||
run {
|
||
val Store = ReduxStore(UiState(), { S, A -> S })
|
||
val Gateway = ReduxGateway(Store) { Task -> Task() }
|
||
val Model = Any()
|
||
var Hits = 0
|
||
Gateway.SubscribeModel(Model) { Scope -> Scope.Listen(SetScreen::class) { Hits++ } }
|
||
Gateway.Dispatch(SetScreen("Lobby"))
|
||
val AfterFirst = Hits
|
||
Gateway.UnsubscribeModel(Model)
|
||
Gateway.Dispatch(SetScreen("Garage"))
|
||
Check("网关:模型订阅收到动作", AfterFirst == 1)
|
||
Check("网关:退订后不再收到动作", Hits == 1)
|
||
|
||
val Twin = Any()
|
||
Gateway.SubscribeModel(Twin) { }
|
||
val DuplicateThrew = try {
|
||
Gateway.SubscribeModel(Twin) { }
|
||
false
|
||
} catch (E: IllegalArgumentException) {
|
||
true
|
||
}
|
||
Check("网关:重复订阅模型直接失败", DuplicateThrew)
|
||
}
|
||
|
||
// ⑤ 调度器:派发确实经过调度器
|
||
run {
|
||
val Store = ReduxStore(UiState(), { S, A -> if (A is AddFrags) S.copy(Frags = S.Frags + A.fragsCount) else S })
|
||
var Scheduled = 0
|
||
val Gateway = ReduxGateway(Store) { Task -> Scheduled++; Task() }
|
||
Gateway.Dispatch(AddFrags(2))
|
||
Check("网关:派发经过调度器", Scheduled == 1 && Store.State.Frags == 2)
|
||
}
|
||
|
||
// ⑥ 动作目录(由 module_10032 的 816 个数据类生成)
|
||
run {
|
||
val All = ActionCatalog.All
|
||
Check("动作目录:非空", All.isNotEmpty())
|
||
Check("动作目录:名字唯一", All.map { it.Name }.toSet().size == All.size)
|
||
Check("动作目录:每个条目都能拿到 Kotlin 类", All.all { it.Type.simpleName != null })
|
||
Check("动作目录:运行时 dispatch 过的动作名全部在册", ActionCatalog.MissingTraced().isEmpty())
|
||
Check("动作目录:ByName 与 All 对齐", ActionCatalog.ByName.size == All.size)
|
||
println(" 目录 ${All.size} 条;打点动作名 ${ActionCatalog.TracedNames.size} 个;" +
|
||
"缺口 ${ActionCatalog.MissingTraced().size} 个")
|
||
}
|
||
|
||
// ⑦ 回放对照:用运行时真实打点原文校验目录里的类与字段
|
||
run {
|
||
var Actions = 0
|
||
val NoClass = ArrayList<String>()
|
||
var MissingField = 0
|
||
var TypeMismatch = 0
|
||
val MismatchSamples = ArrayList<String>()
|
||
for ((Name, Raw) in ActionSamples.Raw) {
|
||
Actions++
|
||
val Meta = ActionCatalog.ByName[Name]
|
||
if (Meta == null) { NoClass.add(Name); continue }
|
||
val Open = Raw.indexOf('(')
|
||
val Close = Raw.lastIndexOf(')')
|
||
if (Open < 0 || Close <= Open) continue
|
||
val Body = Raw.substring(Open + 1, Close)
|
||
if (Body.isBlank()) continue
|
||
for (Part in SplitTop(Body)) {
|
||
val Eq = Part.indexOf('=')
|
||
if (Eq < 0) continue
|
||
val Field = Part.substring(0, Eq).trim()
|
||
val Value = Part.substring(Eq + 1).trim()
|
||
val Decl = Meta.Fields.firstOrNull { it.Name == Field }
|
||
if (Decl == null) {
|
||
MissingField++
|
||
if (MismatchSamples.size < 8) MismatchSamples.add("$Name.$Field 不在类里")
|
||
continue
|
||
}
|
||
if (!ValueFits(Decl.Type, Value)) {
|
||
TypeMismatch++
|
||
if (MismatchSamples.size < 8) {
|
||
MismatchSamples.add("类型不符 $Name.$Field 声明 ${Decl.Type} 实际 $Value")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Check("回放:样例里的动作名都能在目录里找到", NoClass.isEmpty())
|
||
Check("回放:样例字段都能在类里找到", MissingField == 0)
|
||
Check("回放:字段值形态与声明类型一致", TypeMismatch == 0)
|
||
println(" 回放 $Actions 个动作;缺类 ${NoClass.size};缺字段 $MissingField;类型不符 $TypeMismatch")
|
||
for (S in MismatchSamples) println(" · " + S.replace(Regex("\\s+"), " ").take(120))
|
||
}
|
||
|
||
// ⑧ 全目录动作类注册:验证 KClass 身份唯一、按类路由正确
|
||
run {
|
||
val Store = ReduxStore(UiState(), { S, _ -> S })
|
||
val Gateway = ReduxGateway(Store) { Task -> Task() }
|
||
var Hits = 0
|
||
for (Meta in ActionCatalog.All) Gateway.OnAction(Meta.Type) { Hits++ }
|
||
Gateway.OnAction(AddFrags::class) { Hits++ }
|
||
Gateway.Dispatch(AddFrags(1))
|
||
Check("网关:全目录 ${ActionCatalog.All.size} 个动作类可注册且只路由命中项", Hits == 1)
|
||
}
|
||
|
||
// ⑨ 切片 reducer:InviteState(原 module_10032 的 aD / pD.er5)
|
||
run {
|
||
val S0 = InviteStateSlice()
|
||
Check(
|
||
"切片 Invite:默认值与原构造一致",
|
||
S0.IsInviteNeed && S0.Invite == "" && S0.Status == InviteStatus.UNKNOWN &&
|
||
S0.ExistsUid == null && !S0.InviteAccepted
|
||
)
|
||
|
||
val S1 = ReduceInvite(S0, SetInviteNeed(false))
|
||
Check("切片 Invite:SetInviteNeed 只改 isInviteNeed", !S1.IsInviteNeed && S1.Status == S0.Status)
|
||
|
||
val S2 = ReduceInvite(S1, SetInviteStatus("FREE", 12345L))
|
||
Check(
|
||
"切片 Invite:SetInviteStatus 更新 status/existsUid/inviteAccepted",
|
||
S2.Status == InviteStatus.FREE && S2.ExistsUid == 12345L && S2.InviteAccepted
|
||
)
|
||
|
||
val S3 = ReduceInvite(S2, SetInviteStatus("NOT_FOUND", null))
|
||
Check("切片 Invite:非 FREE 状态 inviteAccepted 归 false", !S3.InviteAccepted && S3.Status == InviteStatus.NOT_FOUND)
|
||
|
||
val S4 = ReduceInvite(S3, ApplyInvite("INVITE-CODE"))
|
||
Check(
|
||
"切片 Invite:ApplyInvite 写入邀请码并重置 status/existsUid",
|
||
S4.Invite == "INVITE-CODE" && S4.Status == InviteStatus.UNKNOWN && S4.ExistsUid == null &&
|
||
!S4.InviteAccepted
|
||
)
|
||
|
||
val S5 = ReduceInvite(S4, AddFrags(1))
|
||
Check("切片 Invite:无关动作不改状态", S5 == S4)
|
||
|
||
val Store = ReduxStore(S0, { S, A -> ReduceInvite(S, A) })
|
||
var Notified = false
|
||
Store.Subscribe(StoreListener { Notified = true })
|
||
Store.Dispatch(SetInviteStatus("ALREADY_ACTIVATED", 7L))
|
||
Check(
|
||
"切片 Invite:接 Store 派发后状态已归约",
|
||
Store.State.Status == InviteStatus.ALREADY_ACTIVATED && Store.State.ExistsUid == 7L && Notified
|
||
)
|
||
}
|
||
|
||
// ⑩ 切片 reducer:LoginByPasswordState(原 module_10032 的 gD / MD.cr6,枚举 kD)
|
||
run {
|
||
val S0 = LoginByPasswordStateSlice()
|
||
Check(
|
||
"切片 Login:默认值与原构造一致",
|
||
S0.Login == "" && S0.Password == "" && S0.RememberMe && S0.Status == ByPasswordStatus.NONE &&
|
||
!S0.OnlyLogin && !S0.IncorrectLogin2FA && S0.RequestsNumber2FA == 0
|
||
)
|
||
|
||
// 原包 SetLogin 分支是 `pr5(t.qr5_1, cd, cd, OD())`:写 login 的同时把 status 打回 NONE
|
||
val S1 = ReduceLoginByPassword(
|
||
S0.copy(Status = ByPasswordStatus.WRONG_PASSWORD),
|
||
SetLogin("pilot"),
|
||
)
|
||
Check("切片 Login:SetLogin 写 login 并把 status 复位为 NONE", S1.Login == "pilot" && S1.Status == ByPasswordStatus.NONE)
|
||
|
||
val S2 = ReduceLoginByPassword(S1.copy(Status = ByPasswordStatus.LOGGING_IN), SetPassword("secret"))
|
||
Check(
|
||
"切片 Login:SetPassword 写 password 并把 status 复位为 NONE",
|
||
S2.Password == "secret" && S2.Status == ByPasswordStatus.NONE && S2.Login == "pilot"
|
||
)
|
||
|
||
val S3 = ReduceLoginByPassword(S2.copy(Status = ByPasswordStatus.WRONG_CAPTCHA), SetRememberMe(false))
|
||
Check("切片 Login:SetRememberMe 只改 rememberMe", !S3.RememberMe && S3.Status == ByPasswordStatus.WRONG_CAPTCHA)
|
||
|
||
val S4 = ReduceLoginByPassword(S3, SetStatus("WRONG_PASSWORD"))
|
||
Check("切片 Login:SetStatus 按枚举名解析", S4.Status == ByPasswordStatus.WRONG_PASSWORD)
|
||
Check(
|
||
"切片 Login:SetStatus 认不出时落到 NONE",
|
||
ReduceLoginByPassword(S4, SetStatus("SOMETHING_ELSE")).Status == ByPasswordStatus.NONE
|
||
)
|
||
|
||
val S5 = ReduceLoginByPassword(S4, LoginOnly("uid-only"))
|
||
Check(
|
||
"切片 Login:LoginOnly 写 login/onlyLogin 且保留 status",
|
||
S5.Login == "uid-only" && S5.OnlyLogin && S5.Status == ByPasswordStatus.WRONG_PASSWORD
|
||
)
|
||
|
||
val S6 = ReduceLoginByPassword(S5, IncorrectLogin2FA(true))
|
||
val S7 = ReduceLoginByPassword(S6, RequestsNumber2FA(3))
|
||
Check(
|
||
"切片 Login:IncorrectLogin2FA / RequestsNumber2FA 各自只改本字段",
|
||
S7.IncorrectLogin2FA && S7.RequestsNumber2FA == 3 && S7.Login == "uid-only"
|
||
)
|
||
|
||
val S8 = ReduceLoginByPassword(S7, ResetLoginByPasswordStatus())
|
||
Check(
|
||
"切片 Login:ResetLoginByPasswordStatus 只清 status",
|
||
S8.Status == ByPasswordStatus.NONE && S8.Login == "uid-only" && S8.RequestsNumber2FA == 3
|
||
)
|
||
|
||
val S9 = ReduceLoginByPassword(S8, ResetLoginByPassword())
|
||
Check("切片 Login:ResetLoginByPassword 整片复位", S9 == LoginByPasswordStateSlice())
|
||
|
||
val SA = ReduceLoginByPassword(S4, AddFrags(1))
|
||
Check("切片 Login:无关动作不改状态", SA == S4)
|
||
|
||
val Store = ReduxStore(S0, { S, A -> ReduceLoginByPassword(S, A) })
|
||
var Notified = false
|
||
Store.Subscribe(StoreListener { Notified = true })
|
||
Store.Dispatch(SetPassword("p"))
|
||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||
Check(
|
||
"切片 Login:接 Store 派发后状态已归约",
|
||
Store.State.Password == "p" && Store.State.Status == ByPasswordStatus.LOGGING_IN && Notified
|
||
)
|
||
}
|
||
|
||
// ⑪ 切片 reducer:TwitchState(原 module_10032 的 g9 / y9.gta)
|
||
run {
|
||
val S0 = TwitchStateSlice()
|
||
Check(
|
||
"切片 Twitch:默认值与原构造一致",
|
||
!S0.IsTwitchEnabled && S0.TwitchAccount == null && S0.DaysForNextLinking == 0
|
||
)
|
||
|
||
val S1 = ReduceTwitch(S0, SetTwitchEnabled(true))
|
||
Check("切片 Twitch:SetTwitchEnabled 只改开关", S1.IsTwitchEnabled && S1.TwitchAccount == null)
|
||
|
||
// 样例 SetAccount(account=null) 就落在这一片:SetAccount 写的是 twitchAccount
|
||
val S2 = ReduceTwitch(S1, SetAccount("tank_tv"))
|
||
Check("切片 Twitch:SetAccount 写 twitchAccount", S2.TwitchAccount == "tank_tv" && S2.IsTwitchEnabled)
|
||
Check("切片 Twitch:SetAccount(null) 清空账号", ReduceTwitch(S2, SetAccount(null)).TwitchAccount == null)
|
||
|
||
val S3 = ReduceTwitch(S2, SetDaysForNextLinking(7))
|
||
Check("切片 Twitch:SetDaysForNextLinking 只改天数", S3.DaysForNextLinking == 7 && S3.TwitchAccount == "tank_tv")
|
||
Check("切片 Twitch:无关动作不改状态", ReduceTwitch(S3, AddFrags(1)) == S3)
|
||
|
||
val Store = ReduxStore(S0, { S, A -> ReduceTwitch(S, A) })
|
||
Store.Dispatch(SetDaysForNextLinking(2))
|
||
Check("切片 Twitch:接 Store 派发后状态已归约", Store.State.DaysForNextLinking == 2)
|
||
}
|
||
|
||
// ⑫ 切片 reducer:HomeScreen(原 module_10032 的 s1 + 根 reducer `(inline@home)` 分支)
|
||
run {
|
||
val S0 = HomeStateSlice()
|
||
Check("切片 Home:默认值与 HomeScreen 一致", !S0.ShopDiscountNotification)
|
||
|
||
// 动作 o1(无 toString,定名 ShowShopDiscountNotification)→ copy(shopDiscountNotification = true)
|
||
val S1 = ReduceHome(S0, ShowShopDiscountNotification())
|
||
Check("切片 Home:ShowShopDiscountNotification 把折扣提示置 true", S1.ShopDiscountNotification)
|
||
|
||
Check("切片 Home:无关动作不改状态", ReduceHome(S1, AddFrags(1)) == S1)
|
||
|
||
val Store = ReduxStore(S0, { S, A -> ReduceHome(S, A) })
|
||
Store.Dispatch(ShowShopDiscountNotification())
|
||
Check("切片 Home:接 Store 派发后状态已归约", Store.State.ShopDiscountNotification)
|
||
}
|
||
|
||
// ⑬ 切片 reducer:LocalBattleUserState(原 module_10032 的 lE + 根 reducer `(inline@localBattleUserState)`)
|
||
run {
|
||
val S0 = LocalBattleUserStateSlice()
|
||
Check(
|
||
"切片 LocalBattleUser:默认值与原构造一致",
|
||
S0.WeaponResistanceProperty == null && !S0.IsReArmorTemporaryDisabled
|
||
)
|
||
|
||
val S1 = ReduceLocalBattleUser(S0, SetLocalWeaponResistanceProperty("FREEZE_RESISTANCE"))
|
||
Check(
|
||
"切片 LocalBattleUser:SetLocalWeaponResistanceProperty 只写武器抗性属性",
|
||
S1.WeaponResistanceProperty == "FREEZE_RESISTANCE" && !S1.IsReArmorTemporaryDisabled
|
||
)
|
||
|
||
val S2 = ReduceLocalBattleUser(S1, SetReArmorTemporaryDisabled(true))
|
||
Check(
|
||
"切片 LocalBattleUser:SetReArmorTemporaryDisabled 只改开关",
|
||
S2.IsReArmorTemporaryDisabled && S2.WeaponResistanceProperty == "FREEZE_RESISTANCE"
|
||
)
|
||
Check("切片 LocalBattleUser:SetReArmorTemporaryDisabled(false) 关掉", !ReduceLocalBattleUser(S2, SetReArmorTemporaryDisabled(false)).IsReArmorTemporaryDisabled)
|
||
Check("切片 LocalBattleUser:无关动作不改状态", ReduceLocalBattleUser(S2, AddFrags(1)) == S2)
|
||
|
||
val Store = ReduxStore(S0, { S, A -> ReduceLocalBattleUser(S, A) })
|
||
Store.Dispatch(SetReArmorTemporaryDisabled(true))
|
||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||
}
|
||
|
||
// ⑭ 根 reducer:89 个槽位的委派表 + 88 个已迁移切片(87 归约 + launchParams 透传)
|
||
run {
|
||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||
Check(
|
||
"根:字段名与槽位都唯一",
|
||
RootWiring.All.map { it.Field }.toSet().size == RootWiring.All.size &&
|
||
RootWiring.All.map { it.Slot }.toSet().size == RootWiring.All.size
|
||
)
|
||
Check(
|
||
"根:每个槽位都有 reducer 证据",
|
||
RootWiring.All.all { it.Reducer.isNotBlank() && it.Reducer != "(未识别)" }
|
||
)
|
||
Check(
|
||
"根:4 个内联 IIFE 已按字段具名并抓到动作",
|
||
RootWiring.All.filter { it.Reducer.startsWith("(inline@") }.map { it.Field } ==
|
||
listOf("shop", "home", "localBattleUserState", "partner") &&
|
||
RootWiring.All.filter { it.Reducer.startsWith("(inline@") }.all { it.Actions.isNotEmpty() }
|
||
)
|
||
Check(
|
||
"根:内联动作名与原包一致(shop/home/localBattle/partner)",
|
||
RootWiring.ByField("shop")!!.Actions.containsAll(listOf("SelectItem", "SetPurchaseCompleted", "TryToBuyOneTimePurchase")) &&
|
||
RootWiring.ByField("home")!!.Actions == listOf("ShowShopDiscountNotification") &&
|
||
RootWiring.ByField("localBattleUserState")!!.Actions.contains("SetReArmorTemporaryDisabled") &&
|
||
RootWiring.ByField("partner")!!.Actions.containsAll(listOf("SetPartnerId", "InitPartnerInviteAction"))
|
||
)
|
||
Check(
|
||
"根:无 toString 的 ?o1 已按效果定名(HomeScreen 折扣提示)",
|
||
RootWiring.ByField("home")!!.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:唯一纯透传切片是 launchParams(已强类型收口)",
|
||
RootWiring.All.count { it.Reducer == "(pass-through)" } == 1 &&
|
||
RootWiring.ByField("launchParams")!!.Actions.isEmpty()
|
||
)
|
||
|
||
// 已迁移三片的委派与原包一致:nu.cr6 / Ke.er5 / uf.gta
|
||
val LoginW = RootWiring.ByField("loginByPassword")
|
||
val InviteW = RootWiring.ByField("inviteState")
|
||
val TwitchW = RootWiring.ByField("twitchState")
|
||
Check(
|
||
"根:loginByPassword 委派到 gD / nu.cr6",
|
||
LoginW != null && LoginW.SliceClass == "gD" && LoginW.Reducer == "nu.cr6"
|
||
)
|
||
Check(
|
||
"根:inviteState 委派到 aD / Ke.er5",
|
||
InviteW != null && InviteW.SliceClass == "aD" && InviteW.Reducer == "Ke.er5"
|
||
)
|
||
Check(
|
||
"根:twitchState 委派到 g9 / uf.gta",
|
||
TwitchW != null && TwitchW.SliceClass == "g9" && TwitchW.Reducer == "uf.gta"
|
||
)
|
||
// 后两片是内联 IIFE,委派名是 `(inline@<field>)`
|
||
val HomeW = RootWiring.ByField("home")
|
||
val LocalBattleW = RootWiring.ByField("localBattleUserState")
|
||
Check(
|
||
"根:home 委派到 s1 / (inline@home)",
|
||
HomeW != null && HomeW.SliceClass == "s1" && HomeW.Reducer == "(inline@home)"
|
||
)
|
||
Check(
|
||
"根:localBattleUserState 委派到 lE / (inline@localBattleUserState)",
|
||
LocalBattleW != null && LocalBattleW.SliceClass == "lE" &&
|
||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||
)
|
||
Check(
|
||
"根:迁移表 = 88 个已迁移字段,其余 1 个待迁",
|
||
RootReducer.Migrated ==
|
||
setOf(
|
||
"inviteState", "loginByPassword", "twitchState", "home",
|
||
"localBattleUserState", "shop", "screen", "lootBoxes", "garage",
|
||
"garagePreview", "entrance", "user", "settings", "accountSettings",
|
||
"proBattle", "language", "announcement", "launchParams",
|
||
"news", "notifications", "quests",
|
||
"lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl",
|
||
"rulesUpdated", "notificationByCountry", "availabilityByRank",
|
||
"icons", "questCategories", "mainQuest", "securitySettings", "referralEvent",
|
||
"tipsState", "rankUpDialogState", "battleResult", "captchaState",
|
||
"modalScreen", "droppableGold", "eventGoods", "creatorTagState",
|
||
"rewardedVideosState", "passwordChangeState",
|
||
"system", "contextTutorial", "battleTutorial", "spectator",
|
||
"twoStepVerificationState", "antiAddiction", "moderation",
|
||
"migrationState", "register", "userReportsState",
|
||
"friends", "suppliesState", "clanUserInfo", "battleInviteState",
|
||
"partner", "userRenameState",
|
||
"battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial",
|
||
"clanChatState", "battleSearchState", "battleStatistics",
|
||
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
|
||
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
|
||
"tankBuilderState", "battleMessage", "maps", "battleChat",
|
||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan", "chatState", "itemNotifications",
|
||
"matchmakingGroup",
|
||
) &&
|
||
RootWiring.Unmigrated.size == 1
|
||
)
|
||
|
||
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
||
val S0 = RootState(User = UserStateSlice(Uid = "user-1"), Screen = ScreenStateSlice(Current = NamedScreen("Preloader")))
|
||
val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
|
||
Check(
|
||
"根:ApplyInvite 只动 inviteState",
|
||
S1.InviteState.Invite == "uid-1" &&
|
||
S1.LoginByPassword == S0.LoginByPassword &&
|
||
S1.TwitchState == S0.TwitchState &&
|
||
S1.Home == S0.Home
|
||
)
|
||
|
||
val S2 = ReduceRoot(S1, SetLogin("pilot"))
|
||
Check(
|
||
"根:SetLogin 只动 loginByPassword",
|
||
S2.LoginByPassword.Login == "pilot" &&
|
||
S2.InviteState == S1.InviteState &&
|
||
S2.TwitchState == S1.TwitchState
|
||
)
|
||
|
||
val S3 = ReduceRoot(S2, SetAccount("tank_tv"))
|
||
Check(
|
||
"根:SetAccount 只动 twitchState",
|
||
S3.TwitchState.TwitchAccount == "tank_tv" &&
|
||
S3.InviteState == S2.InviteState &&
|
||
S3.LoginByPassword == S2.LoginByPassword
|
||
)
|
||
|
||
val S4 = ReduceRoot(S3, ShowShopDiscountNotification())
|
||
Check(
|
||
"根:ShowShopDiscountNotification 只动 home",
|
||
S4.Home.ShopDiscountNotification && S4.TwitchState == S3.TwitchState
|
||
)
|
||
|
||
val S5 = ReduceRoot(S4, SetReArmorTemporaryDisabled(true))
|
||
Check(
|
||
"根:SetReArmorTemporaryDisabled 只动 localBattleUserState",
|
||
S5.LocalBattleUserState.IsReArmorTemporaryDisabled && S5.Home == S4.Home
|
||
)
|
||
Check(
|
||
"根:五次归约后五个切片各自保留结果",
|
||
S5.InviteState.Invite == "uid-1" &&
|
||
S5.LoginByPassword.Login == "pilot" &&
|
||
S5.TwitchState.TwitchAccount == "tank_tv" &&
|
||
S5.Home.ShopDiscountNotification &&
|
||
S5.LocalBattleUserState.IsReArmorTemporaryDisabled
|
||
)
|
||
|
||
// 未迁移字段原样透传;无关动作不改变任何字段
|
||
Check(
|
||
"根:未迁移字段原样透传",
|
||
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||
)
|
||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 88/89")
|
||
|
||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||
Store.Dispatch(SetTwitchEnabled(true))
|
||
Store.Dispatch(ShowShopDiscountNotification())
|
||
Check(
|
||
"根:接 Store 派发后三片同时归约",
|
||
Store.State.LoginByPassword.Status == ByPasswordStatus.LOGGING_IN &&
|
||
Store.State.TwitchState.IsTwitchEnabled &&
|
||
Store.State.Home.ShopDiscountNotification
|
||
)
|
||
}
|
||
|
||
// ⑮ 切片 reducer:Shop(原 module_10032 的 j4 + `(inline@shop)`;U7 类别 7 字段、w3 物品 23 字段)
|
||
run {
|
||
val S0 = ShopStateSlice()
|
||
Check(
|
||
"切片 Shop:默认值与原构造一致(enabled 缺省 true、previousScreen 缺省 null)",
|
||
S0.ShopCategories == ShopCategoriesState() && S0.ShopItems == ShopItemsState() &&
|
||
S0.InitShopItemId == null && S0.SelectedItemId == null && !S0.IsLoaded &&
|
||
S0.ItemForPurchase == null && S0.Enabled && S0.CoinPaymentProviderUrl == null &&
|
||
S0.PayModes.isEmpty() && S0.SelectedPayModeId == null && S0.PaymentViewType.isEmpty() &&
|
||
S0.PaymentUrl == null && !S0.ReceivePaymentUrlFailed && S0.MobileCountryCode == "+7 " &&
|
||
S0.MobilePhoneLength == 13 && S0.MobilePaymentState == MobilePaymentState.NONE &&
|
||
S0.PaymentProviderState == PaymentProviderState.NONE && S0.AsiassoftAmountInAtc == 0 &&
|
||
S0.AsiassoftEnoughMoney == null && S0.BraintreeToken == null && !S0.IsBlockedBackButton &&
|
||
!S0.BraintreeLoadingInterrupt && !S0.GoToHomeAfterBuy && S0.PreviousScreen == null &&
|
||
S0.TargetCategory == null && S0.TargetProductId == null && S0.LastPurchasedItem == null &&
|
||
S0.OrdersInProcessing.isEmpty()
|
||
)
|
||
|
||
val S1 = ReduceShop(S0, SetCoinPaymentProviderUrl("https://pay"))
|
||
val S2 = ReduceShop(S1, ShopLoaded())
|
||
Check("切片 Shop:SetCoinPaymentProviderUrl / ShopLoaded 各改本字段", S1.CoinPaymentProviderUrl == "https://pay" && S2.IsLoaded)
|
||
|
||
// F5:复位但保留 coinPaymentProviderUrl
|
||
val S3 = ReduceShop(S2.copy(PaymentUrl = "u", MobilePaymentState = MobilePaymentState.PAYMENT_INITED), ResetShop())
|
||
Check(
|
||
"切片 Shop:ResetShop 复位其余字段但保留 coinPaymentProviderUrl",
|
||
S3.CoinPaymentProviderUrl == "https://pay" && S3.PaymentUrl == null && !S3.IsLoaded &&
|
||
S3.MobilePaymentState == MobilePaymentState.NONE
|
||
)
|
||
|
||
val S4 = ReduceShop(S3, PaymentUrlReceiveFailed())
|
||
val S5 = ReduceShop(S4, ResetPaymentState())
|
||
Check(
|
||
"切片 Shop:支付失败后 ResetPaymentState 清 url/失败位/两个状态",
|
||
S4.PaymentUrl == null && S4.ReceivePaymentUrlFailed &&
|
||
!S5.ReceivePaymentUrlFailed && S5.MobilePaymentState == MobilePaymentState.NONE &&
|
||
S5.PaymentProviderState == PaymentProviderState.NONE
|
||
)
|
||
|
||
val S6 = ReduceShop(S5, MobilePaymentStateChanged("PHONE_VALID"))
|
||
Check("切片 Shop:MobilePaymentStateChanged 按枚举名解析", S6.MobilePaymentState == MobilePaymentState.PHONE_VALID)
|
||
Check(
|
||
"切片 Shop:三种 provider 状态各自落到枚举",
|
||
ReduceShop(S6, PaymentProviderInProgress()).PaymentProviderState == PaymentProviderState.IN_PROGRESS &&
|
||
ReduceShop(S6, PaymentProviderFinishSuccess()).PaymentProviderState == PaymentProviderState.FINISH_SUCCESS &&
|
||
ReduceShop(S6, PaymentProviderFinishFail()).PaymentProviderState == PaymentProviderState.FINISH_FAIL
|
||
)
|
||
|
||
val Mode = PayMode(3L, "card", "desc", 1, 0L, "")
|
||
val S7 = ReduceShop(S6, InitPayModeAction(Mode))
|
||
val S8 = ReduceShop(S7, InitPaymentViewType(3L, "BANK_CARD"))
|
||
Check(
|
||
"切片 Shop:InitPayModeAction 加集合项、InitPaymentViewType 按 payModeId 建表",
|
||
S8.PayModes.contains(Mode) && S8.PaymentViewType[3L] == "BANK_CARD"
|
||
)
|
||
|
||
val S9 = ReduceShop(S8, AddItemToOrderProcessing(11L, "ok"))
|
||
Check(
|
||
"切片 Shop:AddItemToOrderProcessing 写 itemId → (itemId, onSuccess)",
|
||
S9.OrdersInProcessing[11L] == (11L to "ok")
|
||
)
|
||
Check(
|
||
"切片 Shop:RemoveItemFromOrderProcessing itemId=null 时原样返回",
|
||
ReduceShop(S9, RemoveItemFromOrderProcessing(null, false)) == S9
|
||
)
|
||
Check(
|
||
"切片 Shop:RemoveItemFromOrderProcessing 删键",
|
||
ReduceShop(S9, RemoveItemFromOrderProcessing(11L, false)).OrdersInProcessing.isEmpty()
|
||
)
|
||
|
||
val S10 = ReduceShop(S9, SavePreviousScreen("Garage"))
|
||
val S11 = ReduceShop(S10, SaveTargetVisitedCategory("CRYSTALS"))
|
||
val S12 = ReduceShop(S11, SaveTargetProductId(77L))
|
||
Check(
|
||
"切片 Shop:SavePreviousScreen / SaveTargetVisitedCategory / SaveTargetProductId",
|
||
S12.PreviousScreen == "Garage" && S12.TargetCategory == "CRYSTALS" && S12.TargetProductId == 77L
|
||
)
|
||
Check(
|
||
"切片 Shop:ClearPreviousScreen / ClearTargetCategory / ClearTargetProductId 置空",
|
||
ReduceShop(S12, ClearPreviousScreen()).PreviousScreen == null &&
|
||
ReduceShop(S12, ClearTargetCategory()).TargetCategory == null &&
|
||
ReduceShop(S12, ClearTargetProductId()).TargetProductId == null
|
||
)
|
||
|
||
Check(
|
||
"切片 Shop:ToggleShopActivity / SelectItem / SetInitShopItemId 各改本字段",
|
||
!ReduceShop(S12, ToggleShopActivity(false)).Enabled &&
|
||
ReduceShop(S12, SelectItem(42L, true)).SelectedItemId == 42L &&
|
||
ReduceShop(S12, SetInitShopItemId(7L)).InitShopItemId == 7L
|
||
)
|
||
Check(
|
||
"切片 Shop:BlockBackButton / UnblockBackButton 切换返回键锁",
|
||
ReduceShop(S12, BlockBackButton()).IsBlockedBackButton &&
|
||
!ReduceShop(S12, UnblockBackButton()).IsBlockedBackButton &&
|
||
ReduceShop(S12, SetGoToHomeAfterBuy()).GoToHomeAfterBuy
|
||
)
|
||
|
||
// U7:类别表 7 字段 + 5 臂
|
||
val C = Category(
|
||
id = 1L,
|
||
orderIndex = 0,
|
||
name = "Crystals",
|
||
description = "desc",
|
||
type = "CRYSTALS",
|
||
preview = null,
|
||
isSpecial = false,
|
||
icon = null,
|
||
categoryBackground = null,
|
||
cardHoverBackground = 0L,
|
||
confirmPurchaseForCrystals = false,
|
||
canBeShownWhenUserIsInDebt = false,
|
||
showTimerOnCard = false,
|
||
)
|
||
val C1 = ReduceShopCategories(ShopCategoriesState(), InitCategory(C))
|
||
Check(
|
||
"切片 Shop/Categories:InitCategory 同时写 categories / type→id / id→type",
|
||
C1.Categories[1L] == C && C1.CategoryTypeToId[CategoryType.CRYSTALS] == 1L &&
|
||
C1.CategoryIdToType[1L] == CategoryType.CRYSTALS
|
||
)
|
||
val C2 = ReduceShopCategories(C1, SetupDiscountCategoryByItem(1L))
|
||
val C3 = ReduceShopCategories(C2, AddCategoriesWithBonus(setOf(2L, 3L)))
|
||
Check(
|
||
"切片 Shop/Categories:折扣集合加单个 id、奖励集合并入多个 id",
|
||
C3.CategoriesWithDiscount == setOf<Any?>(1L) && C3.CategoriesWithBonus == setOf<Any?>(2L, 3L)
|
||
)
|
||
val C4 = ReduceShopCategories(C3, SelectShopCategory("COINS"))
|
||
val C5 = ReduceShopCategories(C4, SelectCategoryId(9L))
|
||
Check(
|
||
"切片 Shop/Categories:SelectShopCategory 解析枚举、SelectCategoryId 写 id",
|
||
C5.SelectedCategoryType == CategoryType.COINS && C5.SelectedCategoryId == 9L
|
||
)
|
||
|
||
// w3:物品表 23 字段 + 32 臂(抽 12 臂覆盖增删改)
|
||
val Item = ShopItem(
|
||
id = 1L,
|
||
gameObject = null,
|
||
categoryId = 1L,
|
||
orderIndex = 0,
|
||
price = 100,
|
||
originalPrice = 100.0,
|
||
currencyName = "coin",
|
||
currencyId = 1L,
|
||
roundingPrecision = 0,
|
||
previewImageURL = 0L,
|
||
isSellingForCoin = true,
|
||
isSellingForCrystal = false,
|
||
isSellingForRuby = false,
|
||
isSellingForMoney = false,
|
||
isSellingForADS = false,
|
||
needPaymentProviderItem = false,
|
||
)
|
||
val I1 = ReduceShopItems(ShopItemsState(), InitShopItem(1L, Item))
|
||
Check("切片 Shop/Items:InitShopItem 写 items", I1.Items[1L] == Item)
|
||
|
||
val Card = ShopCard(
|
||
activeUntil = 100L,
|
||
goodInfo = null,
|
||
name = "card",
|
||
sold = false,
|
||
pinned = false,
|
||
type = 0,
|
||
view = null,
|
||
discount = null,
|
||
sortIndex = 1,
|
||
shopCategoryId = 1L,
|
||
)
|
||
val I2 = ReduceShopItems(I1, InitShopCard(2L, Card, "goods", 1L))
|
||
Check(
|
||
"切片 Shop/Items:InitShopCard 重建卡片(goodInfo=goods、view/discount 置空)",
|
||
I2.ShopCards[2L]?.name == "card" && I2.ShopCards[2L]?.goodInfo == "goods" &&
|
||
I2.ShopCards[2L]?.view == null && I2.ShopCards[2L]?.discount == null
|
||
)
|
||
|
||
val Discount = DiscountItem(90, 10, 5)
|
||
val I3 = ReduceShopItems(I2, InitShopCardView(2L, "view"))
|
||
val I4 = ReduceShopItems(I3, InitShopCardDiscount(2L, Discount))
|
||
Check(
|
||
"切片 Shop/Items:InitShopCardView / InitShopCardDiscount 只改对应字段",
|
||
I4.ShopCards[2L]?.view == "view" && I4.ShopCards[2L]?.discount == Discount
|
||
)
|
||
val I5 = ReduceShopItems(I4, ShopCardSold(2L))
|
||
val I6 = ReduceShopItems(I5, ShopCardExpired(2L))
|
||
Check(
|
||
"切片 Shop/Items:ShopCardSold 置 sold、ShopCardExpired 把 activeUntil 归零",
|
||
I6.ShopCards[2L]?.sold == true && I6.ShopCards[2L]?.activeUntil == 0L
|
||
)
|
||
Check("切片 Shop/Items:ShopCardRemoved 删卡片", ReduceShopItems(I6, ShopCardRemoved(2L)).ShopCards.isEmpty())
|
||
|
||
val I7 = ReduceShopItems(I6, SetupDiscountItem(1L, Discount))
|
||
Check("切片 Shop/Items:SetupDiscountItem 写 discountItems", I7.DiscountItems[1L] == Discount)
|
||
val I8 = ReduceShopItems(I7, SetupDiscountItemForItem(4L, Discount))
|
||
Check(
|
||
"切片 Shop/Items:o5 同族动作写/删 discountItems",
|
||
I8.DiscountItems[4L] == Discount &&
|
||
ReduceShopItems(I8, RemoveDiscountItemForItem(4L)).DiscountItems[4L] == null
|
||
)
|
||
|
||
val ProviderItem = PaymentProviderItem(5L, 1.0, "USD", "US", "name", "desc", "1 USD")
|
||
val I9 = ReduceShopItems(I8, InitPaymentProviderItems(listOf(ProviderItem)))
|
||
Check(
|
||
"切片 Shop/Items:InitPaymentProviderItems 按 itemId 建表",
|
||
I9.PaymentProviderItems[5L] == ProviderItem
|
||
)
|
||
|
||
val I10 = ReduceShopItems(I9, InitAdditionalDescription(1L, "title", "desc", 0L))
|
||
Check(
|
||
"切片 Shop/Items:InitAdditionalDescription 组装 AdditionalDescription",
|
||
I10.AdditionalDescription[1L] == AdditionalDescription("title", "desc", 0L)
|
||
)
|
||
|
||
val I11 = ReduceShopItems(I10, InitOneTimePurchaseItem(3L, OneTimePurchaseItem(false, 1)))
|
||
val I12 = ReduceShopItems(I11, TryToBuyOneTimePurchase(3L))
|
||
Check(
|
||
"切片 Shop/Items:TryToBuyOneTimePurchase 复制已有项并置 isTriedToBuy",
|
||
I12.OneTimePurchaseItems[3L]?.isTriedToBuy == true
|
||
)
|
||
|
||
val I13 = ReduceShopItems(I12, InitGroup(4L, 2))
|
||
Check("切片 Shop/Items:InitGroup 写 groups", I13.Groups[4L] == 2)
|
||
Check(
|
||
"切片 Shop/Items:InitAmountString / InitShopItemName 写各自表",
|
||
ReduceShopItems(I13, InitAmountString(1L, "100")).AmountString[1L] == "100" &&
|
||
ReduceShopItems(I13, InitShopItemName(1L, "name")).ShopItemName[1L] == "name"
|
||
)
|
||
|
||
val I14 = ReduceShopItems(I13, DeleteOneTimePurchaseItem(1L))
|
||
Check(
|
||
"切片 Shop/Items:DeleteOneTimePurchaseItem 从六个表删同一键",
|
||
I14.Items[1L] == null && I14.FeatureItems[1L] == null && I14.SpecialKitPackage[1L] == null &&
|
||
I14.KitBanner[1L] == null && I14.KitPackage[1L] == null && I14.Groups[1L] == null
|
||
)
|
||
|
||
Check("切片 Shop:无关动作不改状态", ReduceShop(S12, AddFrags(1)) == S12)
|
||
|
||
// 根:一次 ReduceRoot 里商店动作只动 shop
|
||
val R0 = RootState()
|
||
val R1 = ReduceRoot(R0, InitShopItem(1L, Item))
|
||
Check(
|
||
"根:InitShopItem 只动 shop",
|
||
R1.Shop.ShopItems.Items[1L] == Item && R1.User == R0.User && R1.Home == R0.Home
|
||
)
|
||
val R2 = ReduceRoot(R1, InitCategory(C))
|
||
Check(
|
||
"根:InitCategory 与 InitShopItem 在同一片的两张子表里共存",
|
||
R2.Shop.ShopCategories.Categories[1L] == C && R2.Shop.ShopItems.Items[1L] == Item
|
||
)
|
||
val R3 = ReduceRoot(R2, SetCoinPaymentProviderUrl("https://pay"))
|
||
Check(
|
||
"根:SetCoinPaymentProviderUrl 只动 shop 顶层字段",
|
||
R3.Shop.CoinPaymentProviderUrl == "https://pay" &&
|
||
R3.Shop.ShopItems == R2.Shop.ShopItems && R3.LoginByPassword == R0.LoginByPassword
|
||
)
|
||
Check("根:shop 迁移后无关动作仍返回等值状态", ReduceRoot(R3, AddFrags(1)) == R3)
|
||
|
||
// ⑯ shop 真实派发回放:用 action_trace_merged.json 里实际出现过的两条商店动作
|
||
val UrlRaw = ActionSamples.Raw["SetCoinPaymentProviderUrl"]
|
||
val ToggleRaw = ActionSamples.Raw["ToggleShopActivity"]
|
||
Check("shop 回放:两条真实动作都在样例表里", UrlRaw != null && ToggleRaw != null)
|
||
val Url = UrlRaw!!.substringAfter("url=").substringBeforeLast(")")
|
||
val Enabled = ToggleRaw!!.substringAfter("enabled=").substringBeforeLast(")") == "true"
|
||
Check(
|
||
"shop 回放:样例值与运行时一致",
|
||
Url.startsWith("https://open-api.3dtank.com/mgr/tank-pay/") && Enabled
|
||
)
|
||
|
||
val P0 = RootState()
|
||
val P1 = ReduceRoot(P0, SetCoinPaymentProviderUrl(Url))
|
||
Check(
|
||
"shop 回放:真实 SetCoinPaymentProviderUrl 写入完整 URL",
|
||
P1.Shop.CoinPaymentProviderUrl == Url
|
||
)
|
||
val P2 = ReduceRoot(P1.copy(Shop = P1.Shop.copy(Enabled = false)), ToggleShopActivity(Enabled))
|
||
Check(
|
||
"shop 回放:真实 ToggleShopActivity(true) 把 enabled 置 true",
|
||
P2.Shop.Enabled && P2.Shop.CoinPaymentProviderUrl == Url
|
||
)
|
||
Check(
|
||
"shop 回放:只动 shop,其他切片不变",
|
||
P2.LoginByPassword == P0.LoginByPassword && P2.Home == P0.Home &&
|
||
P2.TwitchState == P0.TwitchState && P2.User == P0.User
|
||
)
|
||
Check(
|
||
"shop 回放:两条动作都在动作目录里",
|
||
listOf("SetCoinPaymentProviderUrl", "ToggleShopActivity").all { ActionCatalog.ByName[it] != null }
|
||
)
|
||
// 69 个动作里只有 x4 是标记接口(没有独立效果),其余都已有名字
|
||
val ShopUnknown = RootWiring.ByField("shop")!!.Actions.filter { it.startsWith("?") }
|
||
Check("shop 回放:唯一未具名的是标记 x4", ShopUnknown == listOf("?x4"))
|
||
|
||
// ⑰ 切片 reducer:Screen(原 module_10032 的 WK / wY.ks5,13 个 instanceof 臂)
|
||
run {
|
||
val Lobby = NamedScreen("Lobby.Home")
|
||
val Shop = NamedScreen("Shop")
|
||
|
||
val S0 = ScreenStateSlice()
|
||
Check(
|
||
"切片 Screen:默认值与原构造一致(current=System.Loader、enableBack=true)",
|
||
S0.Current == LoaderScreen && S0.LoaderBackgroundImage == null && !S0.Foreground &&
|
||
S0.ScreenStack.isEmpty() && S0.EnableBack && !S0.IsResetBackStackOnNextGoTo &&
|
||
!S0.IsDelayedGoto && !S0.PressedAnyButton && !S0.DisplayMainEntranceScreen &&
|
||
!S0.NeedReauthorization
|
||
)
|
||
|
||
Check(
|
||
"切片 Screen:前景开关只改 foreground",
|
||
!ReduceScreen(S0, DisableForeground).Foreground &&
|
||
ReduceScreen(S0, EnableForeground).Foreground &&
|
||
ReduceScreen(S0, EnableForeground).Current == LoaderScreen
|
||
)
|
||
|
||
val S1 = S0.copy(Current = Lobby, ScreenStack = listOf(NamedScreen("Preloader")))
|
||
Check(
|
||
"切片 Screen:ClearScreenStack 清历史但保留 current",
|
||
ReduceScreen(S1, ClearScreenStack).let { it.ScreenStack.isEmpty() && it.Current == Lobby }
|
||
)
|
||
|
||
val S2 = ReduceScreen(S1, GoTo(Shop, false))
|
||
Check(
|
||
"切片 Screen:GoTo 无 history 只换 current",
|
||
S2.Current == Shop && S2.ScreenStack == S1.ScreenStack
|
||
)
|
||
|
||
val S3 = ReduceScreen(S1, GoTo(Shop, true))
|
||
Check(
|
||
"切片 Screen:GoTo history 把原 current 压栈",
|
||
S3.Current == Shop && S3.ScreenStack == S1.ScreenStack + Lobby
|
||
)
|
||
Check(
|
||
"切片 Screen:GoTo 到当前屏仍按 history 压栈(原包只在 analytics 里去重)",
|
||
ReduceScreen(S1, GoTo(Lobby, true)).ScreenStack == listOf(NamedScreen("Preloader"), Lobby)
|
||
)
|
||
|
||
val S4 = ReduceScreen(S3.copy(IsResetBackStackOnNextGoTo = true), GoTo(Lobby, true))
|
||
Check(
|
||
"切片 Screen:复位标志优先于 history,且 GoTo 后归 false",
|
||
S4.Current == Lobby && S4.ScreenStack.isEmpty() && !S4.IsResetBackStackOnNextGoTo
|
||
)
|
||
Check(
|
||
"切片 Screen:GoTo 清 isDelayedGoto",
|
||
!ReduceScreen(S3.copy(IsDelayedGoto = true), GoTo(Shop, false)).IsDelayedGoto
|
||
)
|
||
|
||
Check(
|
||
"切片 Screen:ResetScreenStackOnNextGoTo / DelayNextGoTo 各置本标志",
|
||
ReduceScreen(S0, ResetScreenStackOnNextGoTo).IsResetBackStackOnNextGoTo &&
|
||
ReduceScreen(S0, DelayNextGoTo).IsDelayedGoto
|
||
)
|
||
|
||
val S5 = S1.copy(ScreenStack = listOf(NamedScreen("Battle"), Lobby, Shop))
|
||
Check(
|
||
"切片 Screen:FilterScreenStack 只保留 predicate 命中项",
|
||
ReduceScreen(S5, FilterScreenStack { it.name == "Battle" }).ScreenStack ==
|
||
listOf(NamedScreen("Battle"))
|
||
)
|
||
Check(
|
||
"切片 Screen:FilterScreenStack 缺省谓词(原 YK)清空历史",
|
||
ReduceScreen(S5, FilterScreenStack()).ScreenStack.isEmpty()
|
||
)
|
||
|
||
Check(
|
||
"切片 Screen:EnableBack / DisableBack 切返回键",
|
||
ReduceScreen(S0.copy(EnableBack = false), EnableBack).EnableBack &&
|
||
!ReduceScreen(S0, DisableBack).EnableBack
|
||
)
|
||
Check(
|
||
"切片 Screen:PopScreen 丢最后一个,空栈安全",
|
||
ReduceScreen(S1, PopScreen).ScreenStack.isEmpty() &&
|
||
ReduceScreen(S0, PopScreen).ScreenStack.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 Screen:PressedAnyButton 写 action.isPressed(false 也生效)",
|
||
!ReduceScreen(S0.copy(PressedAnyButton = true), PressedAnyButton(false)).PressedAnyButton &&
|
||
ReduceScreen(S0, PressedAnyButton(true)).PressedAnyButton
|
||
)
|
||
Check(
|
||
"切片 Screen:DisplayMainEntranceScreen / DisplayReauthorizationMessage 各写本字段",
|
||
ReduceScreen(S0, DisplayMainEntranceScreen(true)).DisplayMainEntranceScreen &&
|
||
ReduceScreen(S0, DisplayReauthorizationMessage(true)).NeedReauthorization
|
||
)
|
||
Check("切片 Screen:无关动作不改状态", ReduceScreen(S1, AddFrags(1)) == S1)
|
||
|
||
// 根:screen 动作只动 screen,且与已迁移的其余六片共存
|
||
val R0 = RootState()
|
||
val R1 = ReduceRoot(R0, GoTo(Shop, false))
|
||
Check(
|
||
"根:GoTo 只动 screen",
|
||
R1.Screen.Current == Shop && R1.User == R0.User && R1.Shop == R0.Shop &&
|
||
R1.InviteState == R0.InviteState && R1.Home == R0.Home
|
||
)
|
||
val R2 = ReduceRoot(R1, EnableForeground)
|
||
Check(
|
||
"根:EnableForeground 只动 screen 前景位",
|
||
R2.Screen.Foreground && R2.Screen.Current == Shop &&
|
||
R2.Screen.ScreenStack == R1.Screen.ScreenStack && R2.Shop == R1.Shop
|
||
)
|
||
Check("根:screen 迁移后无关动作仍返回等值状态", ReduceRoot(R2, AddFrags(1)) == R2)
|
||
|
||
val W = RootWiring.ByField("screen")!!
|
||
Check(
|
||
"根:screen 委派到 WK / dY().ks5 且 13 个动作全部具名",
|
||
W.SliceClass == "WK" && W.Reducer == "dY().ks5" && W.Actions.size == 13 &&
|
||
W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:screen 的动作名覆盖 13 个 instanceof 臂",
|
||
listOf(
|
||
"DisableForeground", "EnableForeground", "ClearScreenStack",
|
||
"ResetScreenStackOnNextGoTo", "DelayNextGoTo", "GoTo", "FilterScreenStack",
|
||
"EnableBack", "DisableBack", "PopScreen", "PressedAnyButton",
|
||
"DisplayMainEntranceScreen", "DisplayReauthorizationMessage",
|
||
).all { it in W.Actions }
|
||
)
|
||
}
|
||
|
||
// ⑱ 切片 reducer:LootBoxes(原 module_10032 的 aH / EH.wrv,18 个 instanceof 臂)
|
||
run {
|
||
val Item = { Name: String ->
|
||
ContainerGivenItem("INVENTORY", 1, "INVENTORY", 10007277, null, Name, "mine")
|
||
}
|
||
val S0 = LootBoxesStateSlice()
|
||
Check(
|
||
"切片 LootBoxes:默认值与原构造一致",
|
||
S0.RewardsForKeys == emptyList<ContainerGivenItem>() &&
|
||
S0.CurrentContainerType == ContainerType.STANDARD &&
|
||
S0.Keys.isEmpty() && S0.ContainerNames.isEmpty() &&
|
||
S0.ContainerDescriptions.isEmpty() && S0.Rewards.isEmpty() &&
|
||
S0.ShownRewards.isEmpty() && !S0.GotoGarage && S0.TimeToGiveLootBox == 0L &&
|
||
S0.PeriodOfGiveLootBoxInSec == 0 && !S0.OpeningFailed && !S0.AdsAvailable &&
|
||
S0.CurrentCategory == null && !S0.IsOpening && !S0.IsAccelerated &&
|
||
!S0.IsShopVisited && !S0.IsReopening && !S0.IsBlockOpenButtons
|
||
)
|
||
|
||
val Rewards = listOf(Item("mine"))
|
||
Check(
|
||
"切片 LootBoxes:SetPossibleRewards / SetCurrentContainer 各写本字段",
|
||
ReduceLootBoxes(S0, SetPossibleRewards(Rewards)).RewardsForKeys == Rewards &&
|
||
ReduceLootBoxes(S0, SetCurrentContainer(ContainerType.EPIC))
|
||
.CurrentContainerType == ContainerType.EPIC &&
|
||
ReduceLootBoxes(S0, SetCurrentContainer(ContainerType.EPIC))
|
||
.CurrentContainerType != ContainerType.RARE
|
||
)
|
||
|
||
val Keyed = ReduceLootBoxes(S0, SetKeys(mapOf("STANDARD" to 3)))
|
||
Check(
|
||
"切片 LootBoxes:SetKeys 覆盖整张表",
|
||
Keyed.Keys == mapOf("STANDARD" to 3) &&
|
||
ReduceLootBoxes(S0, SetKeys(emptyMap())).Keys.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:AddKeys 在原值上加 delta",
|
||
ReduceLootBoxes(Keyed, AddKeys("STANDARD", 2)).Keys == mapOf("STANDARD" to 5) &&
|
||
ReduceLootBoxes(Keyed, AddKeys("STANDARD", -3)).Keys == mapOf("STANDARD" to 0)
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:AddKeys 缺键抛错(原 nm 是 Map.getValue)",
|
||
runCatching { ReduceLootBoxes(Keyed, AddKeys("MISSING", 1)) }.isFailure
|
||
)
|
||
|
||
val Described = ReduceLootBoxes(S0, SetContainerData("STANDARD", "标准箱", "普通补给箱"))
|
||
Check(
|
||
"切片 LootBoxes:SetContainerData 同时写名字与描述两张表",
|
||
Described.ContainerNames == mapOf("STANDARD" to "标准箱") &&
|
||
Described.ContainerDescriptions == mapOf("STANDARD" to "普通补给箱") &&
|
||
Described.Keys.isEmpty() && Described.Rewards.isEmpty()
|
||
)
|
||
|
||
Check(
|
||
"切片 LootBoxes:SetGoToGarage 写 gotoGarage",
|
||
ReduceLootBoxes(S0, SetGoToGarage(true)).GotoGarage &&
|
||
!ReduceLootBoxes(S0.copy(GotoGarage = true), SetGoToGarage(false)).GotoGarage
|
||
)
|
||
|
||
val Rewarded = ReduceLootBoxes(
|
||
S0.copy(IsReopening = true, ShownRewards = listOf(Item("old"))),
|
||
SetRewards(Rewards)
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:SetRewards 换奖励并把 isReopening 归 false",
|
||
Rewarded.Rewards == Rewards && !Rewarded.IsReopening &&
|
||
Rewarded.ShownRewards == listOf(Item("old"))
|
||
)
|
||
|
||
val Before = kotlin.js.Date.now().toLong()
|
||
val Timed = ReduceLootBoxes(S0, SetTimeToGiveLootBox(3))
|
||
val After = kotlin.js.Date.now().toLong()
|
||
Check(
|
||
"切片 LootBoxes:SetTimeToGiveLootBox = 当前毫秒 + 秒 * 1000",
|
||
Timed.TimeToGiveLootBox >= Before + 3000L && Timed.TimeToGiveLootBox <= After + 3000L
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:SetPeriodOfGiveLootBox 写周期(秒)",
|
||
ReduceLootBoxes(S0, SetPeriodOfGiveLootBox(3600)).PeriodOfGiveLootBoxInSec == 3600
|
||
)
|
||
|
||
val Cleared = ReduceLootBoxes(
|
||
S0.copy(Rewards = Rewards, ShownRewards = Rewards, OpeningFailed = true),
|
||
ClearRewards
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:ClearRewards 只清两个奖励表",
|
||
Cleared.Rewards.isEmpty() && Cleared.ShownRewards.isEmpty() && Cleared.OpeningFailed
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:LootBoxOpeningFailed 置 openingFailed",
|
||
ReduceLootBoxes(S0, LootBoxOpeningFailed).OpeningFailed
|
||
)
|
||
|
||
Check(
|
||
"切片 LootBoxes:SelectCategory 写可空 currentCategory",
|
||
ReduceLootBoxes(S0, SelectCategory(ContainerType.RARE)).CurrentCategory == ContainerType.RARE &&
|
||
ReduceLootBoxes(S0.copy(CurrentCategory = ContainerType.RARE), SelectCategory(null))
|
||
.CurrentCategory == null
|
||
)
|
||
|
||
val Shown = ReduceLootBoxes(S0.copy(ShownRewards = listOf(Item("old"))), ShownRewards(Rewards))
|
||
Check(
|
||
"切片 LootBoxes:ShownRewards 追加到 shownRewards(原 Im 是 List.plus)",
|
||
Shown.ShownRewards == listOf(Item("old"), Item("mine")) && Shown.Rewards.isEmpty()
|
||
)
|
||
|
||
Check(
|
||
"切片 LootBoxes:四个布尔动作各写本字段",
|
||
!ReduceLootBoxes(S0.copy(IsOpening = true), SetIsOpening(false)).IsOpening &&
|
||
ReduceLootBoxes(S0, SetIsOpening(true)).IsOpening &&
|
||
ReduceLootBoxes(S0, SetIsAccelerated(true)).IsAccelerated &&
|
||
ReduceLootBoxes(S0, SetIsShopVisited(true)).IsShopVisited
|
||
)
|
||
Check(
|
||
"切片 LootBoxes:SetIsReopening 置 true;SetIsBlockOpenButtons 写值",
|
||
ReduceLootBoxes(S0, SetIsReopening).IsReopening &&
|
||
ReduceLootBoxes(S0, SetIsBlockOpenButtons(true)).IsBlockOpenButtons &&
|
||
!ReduceLootBoxes(S0.copy(IsBlockOpenButtons = true), SetIsBlockOpenButtons(false))
|
||
.IsBlockOpenButtons
|
||
)
|
||
Check("切片 LootBoxes:无关动作不改状态", ReduceLootBoxes(S0, AddFrags(1)) == S0)
|
||
|
||
// 根:lootBoxes 动作只动 lootBoxes
|
||
val R0 = RootState()
|
||
val R1 = ReduceRoot(R0, SetCurrentContainer(ContainerType.LEGENDARY))
|
||
Check(
|
||
"根:SetCurrentContainer 只动 lootBoxes",
|
||
R1.LootBoxes.CurrentContainerType == ContainerType.LEGENDARY &&
|
||
R1.Shop == R0.Shop && R1.Screen == R0.Screen &&
|
||
R1.InviteState == R0.InviteState && R1.Home == R0.Home
|
||
)
|
||
val R2 = ReduceRoot(R1, SetRewards(listOf(Item("root"))))
|
||
Check(
|
||
"根:SetRewards 只动 lootBoxes,且保留前一次归约",
|
||
R2.LootBoxes.Rewards == listOf(Item("root")) &&
|
||
R2.LootBoxes.CurrentContainerType == ContainerType.LEGENDARY &&
|
||
R2.Shop == R1.Shop
|
||
)
|
||
Check("根:lootBoxes 迁移后无关动作仍返回等值状态", ReduceRoot(R2, AddFrags(1)) == R2)
|
||
|
||
val W = RootWiring.ByField("lootBoxes")!!
|
||
Check(
|
||
"根:lootBoxes 委派到 aH / ws.wrv 且 18 个动作全部具名",
|
||
W.SliceClass == "aH" && W.Reducer == "ws.wrv" && W.Actions.size == 18 &&
|
||
W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:lootBoxes 的动作名覆盖 18 个 instanceof 臂",
|
||
listOf(
|
||
"SetPossibleRewards", "SetCurrentContainer", "SetKeys", "AddKeys",
|
||
"SetContainerData", "SetGoToGarage", "SetRewards", "SetTimeToGiveLootBox",
|
||
"SetPeriodOfGiveLootBox", "ClearRewards", "LootBoxOpeningFailed",
|
||
"SelectCategory", "ShownRewards", "SetIsOpening", "SetIsAccelerated",
|
||
"SetIsShopVisited", "SetIsReopening", "SetIsBlockOpenButtons",
|
||
).all { it in W.Actions }
|
||
)
|
||
}
|
||
}
|
||
|
||
// ⑲ 切片 reducer:Garage(原 module_10032 的 UB / hF,31 字段;58 个 instanceof + 2 个接口/单例臂)
|
||
run {
|
||
fun ToObj(
|
||
Id: Long,
|
||
Base: Long? = null,
|
||
Category: Int = 1,
|
||
View: Int = 1,
|
||
Count: Int = 3,
|
||
Buyable: Boolean = true,
|
||
): GarageItemObject = GarageItemObject(
|
||
id = Id,
|
||
description = "obj-$Id",
|
||
item = "preview-$Id",
|
||
isPremium = false,
|
||
itemCategory = Category,
|
||
itemViewCategory = View,
|
||
buyable = Buyable,
|
||
availableForAll = false,
|
||
discount = null,
|
||
timePeriod = null,
|
||
modification = if (Base != null) GarageItemModification(Base, 0) else null,
|
||
properties = null,
|
||
upgradeableParams = null,
|
||
devices = null,
|
||
count = Count,
|
||
kit = null,
|
||
countable = false,
|
||
mountIndex = null,
|
||
availableSkins = null,
|
||
availableShotSkins = null,
|
||
mountedSkin = null,
|
||
mountedShotSkin = null,
|
||
skinPreview = null,
|
||
grouped = null,
|
||
rarity = null,
|
||
favorite = false,
|
||
isSellingForCoin = false,
|
||
isSellingForCrystal = false,
|
||
isSellingForRuby = false,
|
||
resistanceBackgroundImg = null,
|
||
visible = true,
|
||
descriptionMedia = null,
|
||
saleByKitItems = null,
|
||
)
|
||
|
||
fun ToItem(
|
||
Id: Long,
|
||
Base: Long? = null,
|
||
Category: Int = 1,
|
||
View: Int = 1,
|
||
Owned: Boolean = true,
|
||
Mounted: Boolean = false,
|
||
Count: Int = 1,
|
||
Buyable: Boolean = true,
|
||
Favorite: Boolean = false,
|
||
HasDeviceSale: Boolean = false,
|
||
DevicePreview: Any? = null,
|
||
Level: Int = 0,
|
||
Confirmed: Boolean = false,
|
||
Discount: Any? = null,
|
||
): GarageItem = GarageItem(
|
||
id = Id,
|
||
name = "item-$Id",
|
||
category = Category,
|
||
viewCategory = View,
|
||
priceWithoutDiscount = 100,
|
||
description = "item-$Id",
|
||
descriptionMedia = null,
|
||
preview = "preview-$Id",
|
||
isPremium = false,
|
||
owned = Owned,
|
||
mountable = true,
|
||
mounted = Mounted,
|
||
mountIndex = null,
|
||
position = 0,
|
||
minRank = 0,
|
||
maxRank = 0,
|
||
discount = Discount,
|
||
modification = if (Base != null) GarageItemModification(Base, Level) else null,
|
||
properties = null,
|
||
upgradeableParams = UpgradableItemParams(
|
||
itemPropertyParams = null,
|
||
properties = null,
|
||
currentLevel = Level,
|
||
upgradeParams = null,
|
||
upgradeDiscount = 0,
|
||
upgradeConfirmed = Confirmed,
|
||
),
|
||
count = Count,
|
||
kit = null,
|
||
countable = false,
|
||
hasDeviceSale = HasDeviceSale,
|
||
devicesAvailable = false,
|
||
devicePreview = DevicePreview,
|
||
buyable = Buyable,
|
||
availableForAll = false,
|
||
availableSkins = null,
|
||
availableShotSkins = null,
|
||
mountedSkin = null,
|
||
mountedShotSkin = null,
|
||
skinPreview = null,
|
||
group = null,
|
||
rarity = null,
|
||
favorite = Favorite,
|
||
resistanceBackgroundImg = null,
|
||
visible = true,
|
||
secret = false,
|
||
saleByKitItems = null,
|
||
isSellingForCoin = false,
|
||
isSellingForCrystal = false,
|
||
isSellingForRuby = false,
|
||
isSellingForMoney = false,
|
||
isSellingForADS = false,
|
||
)
|
||
|
||
fun ToDevice(
|
||
Id: Long,
|
||
Base: Long,
|
||
Installed: Boolean = false,
|
||
Preview: Any? = "device-$Id",
|
||
Infinity: Boolean = false,
|
||
): GarageDevice = GarageDevice(
|
||
id = Id,
|
||
baseItemId = Base,
|
||
installed = Installed,
|
||
name = "device-$Id",
|
||
category = 1,
|
||
viewCategory = 1,
|
||
priceWithoutDiscount = 50,
|
||
description = "device-$Id",
|
||
descriptionMedia = null,
|
||
previewImage = Preview,
|
||
isPremium = false,
|
||
position = 0,
|
||
minRank = 0,
|
||
maxRank = 0,
|
||
discount = 0,
|
||
timeDiscountEnd = null,
|
||
timeDiscountToStart = null,
|
||
infinityLifetimeItem = Infinity,
|
||
lifeTimeInSec = 0,
|
||
property = "",
|
||
properties = null,
|
||
isSellingForCoin = false,
|
||
isSellingForCrystal = false,
|
||
isSellingForRuby = false,
|
||
isSellingForADS = false,
|
||
isSellingForMoney = false,
|
||
rarity = null,
|
||
visible = true,
|
||
secret = false,
|
||
buyable = true,
|
||
)
|
||
|
||
fun ToDeviceObj(Id: Long, Base: Long, Preview: Any? = "device-$Id"): GarageDeviceObject =
|
||
GarageDeviceObject(
|
||
id = Id,
|
||
baseItemId = Base,
|
||
temporaryItemCC = null,
|
||
description = "device-$Id",
|
||
item = Preview,
|
||
isPremium = false,
|
||
itemCategory = 1,
|
||
itemViewCategory = 1,
|
||
buyable = true,
|
||
discount = null,
|
||
timePeriod = null,
|
||
deviceProperty = "",
|
||
rarity = null,
|
||
visible = true,
|
||
descriptionMedia = null,
|
||
)
|
||
|
||
fun BaseState(
|
||
ItemList: List<GarageItem>,
|
||
DeviceMap: Map<Long, List<GarageDevice>> = emptyMap(),
|
||
): GarageStateSlice {
|
||
val Grouped = ItemList
|
||
.groupBy { it.modification?.vcf_1 ?: it.id }
|
||
.mapValues { GarageBaseItems(it.value) }
|
||
return GarageStateSlice(
|
||
Items = GarageItemsState(Grouped),
|
||
Devices = Devices(DeviceMap),
|
||
)
|
||
}
|
||
|
||
fun ItemIn(State: GarageStateSlice, Id: Long): GarageItem =
|
||
State.Items.itemsByBase.values.flatMap { it.Items }.first { it.id == Id }
|
||
|
||
val S0 = GarageStateSlice()
|
||
Check(
|
||
"切片 Garage:默认值与原构造一致(31 字段)",
|
||
S0.Items.itemsByBase.isEmpty() && S0.Devices.baseItemIdToDevices.isEmpty() &&
|
||
S0.GarageObject == null && !S0.IsLoaded &&
|
||
S0.CurrentCategory == GarageDefaultCategory && S0.CurrentItemId == null &&
|
||
S0.SelectAlterationId == null && S0.SelectedSkinId == null &&
|
||
S0.SelectedShotSkinId == null && S0.NewItems.isEmpty() &&
|
||
!S0.ResistanceReadyToMount && S0.DelayMountTimeMs == 0L &&
|
||
S0.CooldownReduceEndTime == 0L && S0.TimeToReduceUpdateInMin == 0 &&
|
||
S0.CurrentRarity == null && S0.PurchaseOfUpgradesShopItemId == 920109776942L &&
|
||
!S0.GarageDiscountNotification && S0.ShowVideoInDescription &&
|
||
S0.UnlockedProtectionSlots == 1 && S0.PricesToUnlockSlot.isEmpty() &&
|
||
S0.NumberRowsInPaintsUI == 2 && S0.OverdriveDescriptions.isEmpty() &&
|
||
S0.WeaponToResistanceModule.isEmpty() && S0.WaitServerItemMount.isEmpty() &&
|
||
S0.BazookaToSupply.isEmpty() && S0.UpgradeCoolDownEndTimes.isEmpty() &&
|
||
S0.UpgradeItemInProcessingId == null
|
||
)
|
||
|
||
val O1 = ToObj(101, Count = 3)
|
||
val O2 = ToObj(202, Count = 1, Buyable = false)
|
||
val O3 = ToObj(303, Count = 2)
|
||
val O4 = ToObj(404, Count = 1)
|
||
val GarageObj = GarageObject("temp")
|
||
val S1 = ReduceGarage(S0, SetGarageObject(GarageObj))
|
||
Check("切片 Garage:SetGarageObject", S1.GarageObject == GarageObj)
|
||
val S1a = ReduceGarage(S1, SetItemsOnDepot(listOf(O1, O3)))
|
||
Check(
|
||
"切片 Garage:SetItemsOnDepot",
|
||
S1a.ItemsOnDepot.toList() == listOf(O1, O3) && S1a.GarageObject == GarageObj
|
||
)
|
||
val S1b = ReduceGarage(S1a, SetItemsOnMarket(listOf(O2)))
|
||
Check("切片 Garage:SetItemsOnMarket", S1b.ItemsOnMarket.toList() == listOf(O2))
|
||
val S1c = ReduceGarage(S1b, SetMountedItems(listOf(O3)))
|
||
Check("切片 Garage:SetMountedItems", S1c.MountedItems.toList() == listOf(O3))
|
||
val S1d = ReduceGarage(S1c, SetGarageSkinsAction(listOf(O4)))
|
||
Check("切片 Garage:SetGarageSkinsAction", S1d.SkinsItems.toList() == listOf(O4))
|
||
|
||
val Loaded0 = GarageStateSlice(
|
||
ItemsOnDepot = listOf(O1, O3, ToObj(505, Count = 1)),
|
||
MountedItems = listOf(O3),
|
||
ItemsOnMarket = listOf(O2),
|
||
SkinsItems = listOf(O4),
|
||
)
|
||
val Loaded1 = ReduceGarage(Loaded0, GarageLoaded())
|
||
val LoadedItems = Loaded1.Items.itemsByBase.values.flatMap { it.Items }
|
||
Check(
|
||
"切片 Garage:GarageLoaded 重建 PB(depot/mounted/market/skins)",
|
||
Loaded1.IsLoaded && LoadedItems.map { it.id }.sorted() == listOf(101L, 202L, 303L, 404L, 505L) &&
|
||
LoadedItems.first { it.id == 101L }.owned &&
|
||
LoadedItems.first { it.id == 303L }.owned && LoadedItems.first { it.id == 303L }.mounted &&
|
||
!LoadedItems.first { it.id == 202L }.owned && !LoadedItems.first { it.id == 202L }.buyable &&
|
||
!LoadedItems.first { it.id == 404L }.owned
|
||
)
|
||
Check(
|
||
"切片 Garage:SetGarageNotLoaded",
|
||
!ReduceGarage(Loaded1, SetGarageNotLoaded()).IsLoaded
|
||
)
|
||
|
||
val SC = ReduceGarage(S0, SelectGarageCategory("paints"))
|
||
Check(
|
||
"切片 Garage:SelectGarageCategory 同时清空 currentItemId",
|
||
SC.CurrentCategory == "paints" && SC.CurrentItemId == null
|
||
)
|
||
val SI = ReduceGarage(SC, GarageSelectItem(101))
|
||
Check(
|
||
"切片 Garage:GarageSelectItem 只改 currentItemId",
|
||
SI.CurrentItemId == 101L && SI.CurrentCategory == "paints"
|
||
)
|
||
|
||
val I101 = ToItem(101, Count = 3)
|
||
val SU = ReduceGarage(BaseState(listOf(I101)), GarageUpdateItemCount(I101, 9))
|
||
Check("切片 Garage:GarageUpdateItemCount", ItemIn(SU, 101).count == 9)
|
||
|
||
val L1 = ToItem(110, Base = 110, Level = 1, DevicePreview = "lvl-1")
|
||
val L2 = ToItem(111, Base = 110, Level = 2, DevicePreview = "lvl-2")
|
||
val L3 = ToItem(112, Base = 110, Level = 3, DevicePreview = "lvl-3")
|
||
val AddCount = ReduceGarage(BaseState(listOf(L1, L2, L3)), AddGarageItemCount(L2, 4))
|
||
Check(
|
||
"切片 Garage:AddGarageItemCount(owned=true、count+=、devicePreview 取等级项)",
|
||
ItemIn(AddCount, 111).owned && ItemIn(AddCount, 111).count == 5 &&
|
||
ItemIn(AddCount, 111).devicePreview == "lvl-2"
|
||
)
|
||
|
||
val M1 = ToItem(201, Mounted = true)
|
||
val M2 = ToItem(202)
|
||
val Mounted = ReduceGarage(BaseState(listOf(M1, M2)), GarageMountItem(M2))
|
||
Check(
|
||
"切片 Garage:GarageMountItem 先卸同 viewCategory 再装,并写 waitServerItemMount",
|
||
!ItemIn(Mounted, 201).mounted && ItemIn(Mounted, 202).mounted &&
|
||
Mounted.WaitServerItemMount[1] == true
|
||
)
|
||
val Unmounted = ReduceGarage(Mounted, GarageUnmountItem(202))
|
||
Check("切片 Garage:GarageUnmountItem", !ItemIn(Unmounted, 202).mounted)
|
||
|
||
val MountAll = ReduceGarage(
|
||
BaseState(listOf(M1, M2)),
|
||
GarageMountItems(listOf(201, 202)),
|
||
)
|
||
Check(
|
||
"切片 Garage:GarageMountItems 批量 owned+mounted",
|
||
ItemIn(MountAll, 201).mounted && ItemIn(MountAll, 201).owned &&
|
||
ItemIn(MountAll, 202).mounted && ItemIn(MountAll, 202).owned
|
||
)
|
||
|
||
val SkinBase = BaseState(listOf(ToItem(301, Base = 301), ToItem(302, Base = 301)))
|
||
val Skinned = ReduceGarage(SkinBase, SetMountedSkinForItems(9001, ItemIn(SkinBase, 302)))
|
||
Check(
|
||
"切片 Garage:SetMountedSkinForItems 整组写入 mountedSkin",
|
||
ItemIn(Skinned, 301).mountedSkin == 9001L && ItemIn(Skinned, 302).mountedSkin == 9001L
|
||
)
|
||
val Shot = ReduceGarage(SkinBase, SetMountedShotSkinForItems(9002, ItemIn(SkinBase, 302)))
|
||
Check(
|
||
"切片 Garage:SetMountedShotSkinForItems 整组写入 mountedShotSkin",
|
||
ItemIn(Shot, 301).mountedShotSkin == 9002L && ItemIn(Shot, 302).mountedShotSkin == 9002L
|
||
)
|
||
|
||
val R1 = ToItem(401, View = 5)
|
||
val R2 = ToItem(402, View = 5)
|
||
val Resist = ReduceGarage(BaseState(listOf(R1, R2)), GarageApplyResistanceMount(R1, 3, false))
|
||
Check(
|
||
"切片 Garage:GarageApplyResistanceMount(mounted + mountIndex + waitServerItemMount)",
|
||
ItemIn(Resist, 401).mounted && ItemIn(Resist, 401).mountIndex == 3 &&
|
||
Resist.WaitServerItemMount[GarageResistanceCategoryKey] == true
|
||
)
|
||
var ResistanceThrew = false
|
||
try {
|
||
ReduceGarage(Resist, GarageApplyResistanceMount(R2, 4, false))
|
||
} catch (_: Throwable) {
|
||
ResistanceThrew = true
|
||
}
|
||
Check("切片 Garage:抵抗模块装配数受 unlockedProtectionSlots 限制", ResistanceThrew)
|
||
val ResistOff = ReduceGarage(Resist, GarageResistanceUnMount(401, true))
|
||
Check(
|
||
"切片 Garage:GarageResistanceUnMount",
|
||
!ItemIn(ResistOff, 401).mounted &&
|
||
ResistOff.WaitServerItemMount[GarageResistanceCategoryKey] == true
|
||
)
|
||
|
||
val Up0 = ReduceGarage(
|
||
BaseState(listOf(ToItem(501, Level = 2, Confirmed = true))),
|
||
GarageUpgradeItemAction(501, 100, "coin"),
|
||
)
|
||
Check(
|
||
"切片 Garage:GarageUpgradeItemAction(level+1、confirmed=false、processing=itemId)",
|
||
ItemIn(Up0, 501).upgradeableParams!!.currentLevel == 3 &&
|
||
ItemIn(Up0, 501).upgradeableParams!!.upgradeConfirmed == false &&
|
||
Up0.UpgradeItemInProcessingId == 501L
|
||
)
|
||
val Up1 = ReduceGarage(Up0, GarageUpgradeItemCoolDownStarted(501, 12345))
|
||
Check("切片 Garage:GarageUpgradeItemCoolDownStarted", Up1.UpgradeCoolDownEndTimes[501L] == 12345L)
|
||
val Up2 = ReduceGarage(Up1, GarageUpgradeItemCoolDownFinished(501, 12345))
|
||
Check("切片 Garage:GarageUpgradeItemCoolDownFinished 命中时删键", Up2.UpgradeCoolDownEndTimes.isEmpty())
|
||
Check(
|
||
"切片 Garage:GarageUpgradeItemCoolDownFinished 不命中时原样返回",
|
||
ReduceGarage(Up1, GarageUpgradeItemCoolDownFinished(501, 999)) == Up1
|
||
)
|
||
val Confirmed = ReduceGarage(Up0, GarageUpgradeItemConfirm(501, 3))
|
||
Check(
|
||
"切片 Garage:GarageUpgradeItemConfirm 确认并清 processing",
|
||
ItemIn(Confirmed, 501).upgradeableParams!!.upgradeConfirmed == true &&
|
||
Confirmed.UpgradeItemInProcessingId == null
|
||
)
|
||
val ConfirmedOther = ReduceGarage(
|
||
Up0.copy(UpgradeItemInProcessingId = 999L),
|
||
GarageUpgradeItemConfirm(501, 3),
|
||
)
|
||
Check(
|
||
"切片 Garage:Confirm 其他 processing id 不清",
|
||
ConfirmedOther.UpgradeItemInProcessingId == 999L
|
||
)
|
||
Check(
|
||
"切片 Garage:ClearUpgradeItemInProcessing(原 xU 单例)",
|
||
ReduceGarage(Up0, ClearUpgradeItemInProcessing()).UpgradeItemInProcessingId == null
|
||
)
|
||
|
||
val DItem = ToItem(601)
|
||
val DBase = BaseState(listOf(DItem), mapOf(601L to listOf(ToDevice(9001, 601))))
|
||
val DLoaded = ReduceGarage(
|
||
DBase,
|
||
GarageDevicesLoadedAction(
|
||
601,
|
||
listOf(ToDeviceObj(9001, 601), ToDeviceObj(9002, 601)),
|
||
ToDevice(9002, 601),
|
||
),
|
||
)
|
||
val DList = DLoaded.Devices.baseItemIdToDevices[601L].orEmpty()
|
||
Check(
|
||
"切片 Garage:GarageDevicesLoadedAction 合并旧列表并按 mountedDevice 置 installed",
|
||
DList.map { it.id }.sorted() == listOf(9001L, 9002L) &&
|
||
!DList.first { it.id == 9001L }.installed &&
|
||
DList.first { it.id == 9002L }.installed
|
||
)
|
||
val DRemoved = ReduceGarage(DLoaded, GarageRemoveDevice(ToDevice(9001, 601), DItem))
|
||
Check(
|
||
"切片 Garage:GarageRemoveDevice(devicePreview=null、installed=false)",
|
||
ItemIn(DRemoved, 601).devicePreview == null &&
|
||
!DRemoved.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9001L }.installed
|
||
)
|
||
val DInserted = ReduceGarage(
|
||
DLoaded,
|
||
GarageInsertDeviceClientAndServer(ToDevice(9002, 601, Preview = "dev-preview"), DItem),
|
||
)
|
||
Check(
|
||
"切片 Garage:GarageInsertDeviceClientAndServer(devicePreview=设备预览图、installed=true)",
|
||
ItemIn(DInserted, 601).devicePreview == "dev-preview" &&
|
||
DInserted.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.installed
|
||
)
|
||
val DBought = ReduceGarage(
|
||
DLoaded,
|
||
GarageBuyDevice(ToDevice(9002, 601, Preview = "bought"), DItem, "coin", true),
|
||
)
|
||
Check(
|
||
"切片 Garage:GarageBuyDevice(installed=mount、infinityLifetimeItem=true、devicePreview)",
|
||
ItemIn(DBought, 601).devicePreview == "bought" &&
|
||
DBought.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.installed &&
|
||
DBought.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9002L }.infinityLifetimeItem
|
||
)
|
||
val DInf = ReduceGarage(DLoaded, GarageDeviceTrySetInfinityLifetime(9001))
|
||
Check(
|
||
"切片 Garage:GarageDeviceTrySetInfinityLifetime",
|
||
DInf.Devices.baseItemIdToDevices[601L]!!.first { it.id == 9001L }.infinityLifetimeItem
|
||
)
|
||
|
||
val Sel = ReduceGarage(S0, GarageSelectAlteration(7001))
|
||
val Sel2 = ReduceGarage(Sel, GarageSelectSkin(7002))
|
||
val Sel3 = ReduceGarage(Sel2, GarageSelectShotSkin(7003))
|
||
Check(
|
||
"切片 Garage:三个选择动作",
|
||
Sel3.SelectAlterationId == 7001L && Sel3.SelectedSkinId == 7002L &&
|
||
Sel3.SelectedShotSkinId == 7003L
|
||
)
|
||
|
||
val DiscItem = ToItem(801, Owned = true, HasDeviceSale = true)
|
||
val NoDisc = ReduceGarage(BaseState(listOf(DiscItem)), GarageRemoveDiscountForDevice(DiscItem))
|
||
Check(
|
||
"切片 Garage:GarageRemoveDiscountForDevice(hasDeviceSale && owned)",
|
||
ItemIn(NoDisc, 801).hasDeviceSale
|
||
)
|
||
val UnownedDisc = ToItem(802, Owned = false, HasDeviceSale = true)
|
||
val NoDisc2 = ReduceGarage(BaseState(listOf(UnownedDisc)), GarageRemoveDiscountForDevice(UnownedDisc))
|
||
Check("切片 Garage:未拥有项去折扣后 hasDeviceSale=false", !ItemIn(NoDisc2, 802).hasDeviceSale)
|
||
Check(
|
||
"切片 Garage:UpdateDiscount 保留原 discount 值",
|
||
ReduceGarage(BaseState(listOf(ToItem(803, Discount = "d"))), UpdateDiscount(ToItem(803, Discount = "d")))
|
||
.let { ItemIn(it, 803).discount == "d" }
|
||
)
|
||
|
||
val DepotItem = ToItem(901, Owned = false, Count = 1)
|
||
val DepotState = BaseState(listOf(DepotItem))
|
||
val Added = ReduceGarage(DepotState, GarageAddItemToDepot(901, 5))
|
||
Check("切片 Garage:GarageAddItemToDepot(owned=true、count 覆盖)",
|
||
ItemIn(Added, 901).owned && ItemIn(Added, 901).count == 5)
|
||
val Removed = ReduceGarage(Added, GarageRemoveItemFromDepot(ToObj(901, Count = 2)))
|
||
Check("切片 Garage:GarageRemoveItemFromDepot(owned=false、count 取动作 item)",
|
||
!ItemIn(Removed, 901).owned && ItemIn(Removed, 901).count == 2)
|
||
val NotSold = ReduceGarage(Added, GarageRemoveItemFromStore(901))
|
||
Check("切片 Garage:GarageRemoveItemFromStore(buyable=false)", !ItemIn(NotSold, 901).buyable)
|
||
|
||
val New0 = ReduceGarage(S0, SetNewItems(mapOf(1L to setOf(10L, 20L))))
|
||
val New1 = ReduceGarage(New0, AddNewItem(1, 30))
|
||
val New2 = ReduceGarage(New1, AddNewItem(2, 5))
|
||
Check(
|
||
"切片 Garage:SetNewItems / AddNewItem",
|
||
New2.NewItems[1L] == setOf(10L, 20L, 30L) && New2.NewItems[2L] == setOf(5L)
|
||
)
|
||
val New3 = ReduceGarage(New2, RemoveNewItem(1, 10))
|
||
val New4 = ReduceGarage(New3, RemoveNewItem(1, 20))
|
||
val New5 = ReduceGarage(New4, RemoveNewItem(1, 30))
|
||
Check(
|
||
"切片 Garage:RemoveNewItem 逐个移除并在空集时删键",
|
||
New3.NewItems[1L] == setOf(20L, 30L) && !New5.NewItems.containsKey(1L) &&
|
||
New5.NewItems[2L] == setOf(5L)
|
||
)
|
||
|
||
Check(
|
||
"切片 Garage:SetResistanceReadyToMount",
|
||
ReduceGarage(S0, SetResistanceReadyToMount(true)).ResistanceReadyToMount
|
||
)
|
||
|
||
val SaleBase = BaseState(
|
||
listOf(ToItem(1001, Base = 1001), ToItem(1002, Base = 1001), ToItem(1003, Base = 1003))
|
||
)
|
||
val Sale = ReduceGarage(SaleBase, RemoveFromSale(listOf(1001L)))
|
||
Check(
|
||
"切片 Garage:RemoveFromSale 只影响给定 baseItemId 组",
|
||
!ItemIn(Sale, 1001).buyable && !ItemIn(Sale, 1002).buyable && ItemIn(Sale, 1003).buyable
|
||
)
|
||
|
||
val Delay = ReduceGarage(S0, SetRestrictionMount(5000))
|
||
Check(
|
||
"切片 Garage:SetRestrictionMount 正延迟写成 now+delay",
|
||
Delay.DelayMountTimeMs > 0L && ReduceGarage(S0, SetRestrictionMount(0)).DelayMountTimeMs == 0L
|
||
)
|
||
Check("切片 Garage:ResetMountDelay", ReduceGarage(Delay, ResetMountDelay()).DelayMountTimeMs == 0L)
|
||
|
||
val Cool = ReduceGarage(S0, StartCooldownReduce(60, 5))
|
||
Check(
|
||
"切片 Garage:StartCooldownReduce(now+seconds*1000、minutes)",
|
||
Cool.CooldownReduceEndTime > 0L && Cool.TimeToReduceUpdateInMin == 5
|
||
)
|
||
Check(
|
||
"切片 Garage:SetCooldownReduceEndTime(now+seconds*1000)",
|
||
ReduceGarage(S0, SetCooldownReduceEndTime(10)).CooldownReduceEndTime > 0L
|
||
)
|
||
Check("切片 Garage:SetItemRarity", ReduceGarage(S0, SetItemRarity("rare")).CurrentRarity == "rare")
|
||
|
||
val Fav = BaseState(listOf(ToItem(1101)))
|
||
val Favorited = ReduceGarage(Fav, AddToFavoriteItem(1101))
|
||
Check("切片 Garage:AddToFavoriteItem", ItemIn(Favorited, 1101).favorite)
|
||
Check(
|
||
"切片 Garage:RemoveFromFavoriteItem",
|
||
!ItemIn(ReduceGarage(Favorited, RemoveFromFavoriteItem(1101)), 1101).favorite
|
||
)
|
||
|
||
val GoGarage = ReduceGarage(SC.copy(CurrentItemId = 101L), GoTo(NamedScreen("Garage"), false))
|
||
Check(
|
||
"切片 Garage:GoTo Garage 复位 currentCategory 并原样返回 currentItemId",
|
||
GoGarage.CurrentCategory == GarageDefaultCategory && GoGarage.CurrentItemId == 101L
|
||
)
|
||
val GoShop = ReduceGarage(SC, GoTo(NamedScreen("Shop"), false))
|
||
Check("切片 Garage:GoTo 其他屏幕不改分类", GoShop.CurrentCategory == "paints")
|
||
|
||
Check(
|
||
"切片 Garage:ShowGarageDiscountNotification",
|
||
ReduceGarage(S0, ShowGarageDiscountNotification()).GarageDiscountNotification
|
||
)
|
||
Check(
|
||
"切片 Garage:HideVideoInDescription",
|
||
!ReduceGarage(S0, HideVideoInDescription()).ShowVideoInDescription
|
||
)
|
||
Check(
|
||
"切片 Garage:BuyProtectionSlot",
|
||
ReduceGarage(S0, BuyProtectionSlot(1)).UnlockedProtectionSlots == 2
|
||
)
|
||
Check(
|
||
"切片 Garage:SwitchingRowsScroll",
|
||
ReduceGarage(S0, SwitchingRowsScroll(4)).NumberRowsInPaintsUI == 4
|
||
)
|
||
|
||
val Desc = OverdriveDescriptionData("name", "icon", "text")
|
||
Check(
|
||
"切片 Garage:AddOverdriveDescription",
|
||
ReduceGarage(S0, AddOverdriveDescription(12, Desc)).OverdriveDescriptions[12L] == Desc
|
||
)
|
||
Check(
|
||
"切片 Garage:AddRelatedResistanceModule",
|
||
ReduceGarage(S0, AddRelatedResistanceModule(12, 34)).WeaponToResistanceModule[12L] == 34L
|
||
)
|
||
Check(
|
||
"切片 Garage:AddRelatedBazooka",
|
||
ReduceGarage(S0, AddRelatedBazooka(12, 34)).BazookaToSupply[12L] == 34L
|
||
)
|
||
|
||
val Wait0 = S0.copy(WaitServerItemMount = mapOf(1 to true, 2 to true))
|
||
val Wait1 = ReduceGarage(Wait0, RemoveWaitServerItemMount(listOf(1)))
|
||
Check(
|
||
"切片 Garage:RemoveWaitServerItemMount 给定键置 false",
|
||
Wait1.WaitServerItemMount[1] == false && Wait1.WaitServerItemMount[2] == true
|
||
)
|
||
Check(
|
||
"切片 Garage:ClearWaitServerItemMount",
|
||
ReduceGarage(Wait1, ClearWaitServerItemMount()).WaitServerItemMount.isEmpty()
|
||
)
|
||
|
||
val Reset0 = S0.copy(
|
||
Devices = Devices(mapOf(1L to listOf(ToDevice(1, 1)))),
|
||
UpgradeCoolDownEndTimes = mapOf(1L to 2L),
|
||
UpgradeItemInProcessingId = 3L,
|
||
)
|
||
val Reset1 = ReduceGarage(Reset0, ResetGarage())
|
||
Check(
|
||
"切片 Garage:ResetGarage 清 devices / coolDown / processing",
|
||
Reset1.Devices.baseItemIdToDevices.isEmpty() &&
|
||
Reset1.UpgradeCoolDownEndTimes.isEmpty() && Reset1.UpgradeItemInProcessingId == null
|
||
)
|
||
|
||
val Init = ReduceGarage(
|
||
S0,
|
||
InitState(GarageStateSlice::class, { S: GarageStateSlice -> S.copy(IsLoaded = true) }),
|
||
)
|
||
Check("切片 Garage:InitState 按 GarageStateSlice::class 灌入状态", Init.IsLoaded)
|
||
Check(
|
||
"切片 Garage:InitState 其他类型不改状态",
|
||
ReduceGarage(S0, InitState(String::class, { S: GarageStateSlice -> S.copy(IsLoaded = true) })) == S0
|
||
)
|
||
Check("切片 Garage:无关动作返回等值状态", ReduceGarage(BaseState(listOf(I101)), AddFrags(1)) == BaseState(listOf(I101)))
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, GarageSelectItem(101))
|
||
Check(
|
||
"根:GarageSelectItem 只动 garage",
|
||
Root1.Garage.CurrentItemId == 101L && Root1.Shop == Root0.Shop && Root1.Screen == Root0.Screen &&
|
||
Root1.LootBoxes == Root0.LootBoxes && Root1.Home == Root0.Home
|
||
)
|
||
val Root2 = ReduceRoot(Root1, SetGarageObject(GarageObj))
|
||
Check(
|
||
"根:SetGarageObject 保留 garage 前一次归约",
|
||
Root2.Garage.GarageObject == GarageObj && Root2.Garage.CurrentItemId == 101L &&
|
||
Root2.LootBoxes == Root1.LootBoxes
|
||
)
|
||
Check("根:garage 迁移后无关动作仍返回等值状态", ReduceRoot(Root2, AddFrags(1)) == Root2)
|
||
|
||
val W = RootWiring.ByField("garage")!!
|
||
Check(
|
||
"根:garage 委派到 UB / hF 且 58 个动作全部具名",
|
||
W.SliceClass == "UB" && W.Reducer == "hF" && W.Actions.size == 58 &&
|
||
W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:garage 的动作名覆盖 58 个 instanceof 臂",
|
||
listOf(
|
||
"ResetGarage", "SetGarageObject", "SetItemsOnDepot", "SetItemsOnMarket",
|
||
"SetMountedItems", "GarageLoaded", "SetGarageNotLoaded", "SelectGarageCategory",
|
||
"GarageSelectItem", "GarageUpdateItemCount", "AddGarageItemCount", "GarageMountItem", "GarageUnmountItem",
|
||
"GarageMountItems", "SetMountedSkinForItems", "SetMountedShotSkinForItems",
|
||
"SetGarageSkinsAction",
|
||
"GarageApplyResistanceMount", "GarageResistanceUnMount", "GarageUpgradeItemAction",
|
||
"GarageUpgradeItemCoolDownStarted", "GarageUpgradeItemCoolDownFinished",
|
||
"GarageUpgradeItemConfirm", "GarageDevicesLoadedAction", "GarageRemoveDevice",
|
||
"GarageBuyDevice",
|
||
"GarageDeviceTrySetInfinityLifetime", "GarageSelectAlteration", "GarageSelectSkin",
|
||
"GarageSelectShotSkin", "GarageRemoveDiscountForDevice", "UpdateDiscount",
|
||
"GarageAddItemToDepot", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore",
|
||
"SetNewItems", "AddNewItem", "RemoveNewItem", "SetResistanceReadyToMount",
|
||
"RemoveFromSale", "SetRestrictionMount", "ResetMountDelay", "StartCooldownReduce",
|
||
"SetCooldownReduceEndTime", "SetItemRarity", "AddToFavoriteItem",
|
||
"RemoveFromFavoriteItem", "GoTo", "ShowGarageDiscountNotification",
|
||
"HideVideoInDescription", "BuyProtectionSlot", "SwitchingRowsScroll",
|
||
"AddOverdriveDescription", "AddRelatedResistanceModule", "AddRelatedBazooka",
|
||
"RemoveWaitServerItemMount", "ClearWaitServerItemMount", "InitState",
|
||
).all { it in W.Actions }
|
||
)
|
||
Check(
|
||
"根:garage 的动作表不含 FX / UB 标识类",
|
||
W.Actions.none { it == "FX" || it == "UB" || it == "Garage" }
|
||
)
|
||
}
|
||
|
||
// ⑳ 切片 reducer:GaragePreview(原 module_10032 的 IG / fs.eru,22 字段;25 个顶层臂)
|
||
run {
|
||
val S0 = GaragePreviewStateSlice()
|
||
Check(
|
||
"切片 GaragePreview:默认值与原构造一致(22 字段)",
|
||
!S0.IsVisible && !S0.PhotoMode && !S0.NeedVisible &&
|
||
S0.ViewStyle == null && S0.DynViewStyle == null &&
|
||
S0.TankPreviewSettings is TankPreviewSettings && !S0.NeedShowDrone &&
|
||
S0.Params == null && S0.HullData == null && S0.WeaponData == null &&
|
||
S0.DroneData == null && S0.GrenadeData == null && !S0.HasGrenades &&
|
||
S0.HullEngineType == HullEngineType.TRACKED &&
|
||
S0.TurretMechanism == TurretMechanism.ROTATING_TURRET &&
|
||
S0.Paint == null && S0.Properties.isEmpty() &&
|
||
S0.CaptureScreenshotAction == null && S0.CaptureVideoAction == null &&
|
||
S0.Item3dModelActionCache.isEmpty() && S0.RequestedItems.isEmpty() &&
|
||
S0.ExpectedItemId.isEmpty()
|
||
)
|
||
|
||
val Colors = EmissionColors(1, 2, 3, 4, "STRICT")
|
||
val OldSettings = TankPreviewSettings(cameraHeight = 111)
|
||
val NewSettings = TankPreviewSettings(cameraHeight = 777)
|
||
val Params = GaragePreviewModelCC(80, 1000, 1.57, -110, null, null)
|
||
val OldPaint = Coloring(null, TextureFrameAnimation(32, 4, 8, 16))
|
||
val NewPaint = Coloring(null, TextureFrameAnimation(64, 8, 8, 32))
|
||
val OldPart = TankPartData(resource = "old")
|
||
val Base = S0.copy(
|
||
IsVisible = true,
|
||
NeedVisible = false,
|
||
ViewStyle = "old",
|
||
DynViewStyle = "dyn-old",
|
||
TankPreviewSettings = OldSettings,
|
||
Params = Params,
|
||
HullData = OldPart,
|
||
WeaponData = OldPart,
|
||
DroneData = OldPart,
|
||
GrenadeData = OldPart,
|
||
Paint = OldPaint,
|
||
Properties = listOf("old"),
|
||
)
|
||
|
||
val ClearParams = ReduceGaragePreview(Base, ClearGaragePreviewParams())
|
||
Check("切片 GaragePreview:ClearGaragePreviewParams 只清 params", ClearParams.Params == null)
|
||
|
||
val Shown = ReduceGaragePreview(Base.copy(NeedVisible = true), ShowGaragePreview())
|
||
Check(
|
||
"切片 GaragePreview:ShowGaragePreview 置 isVisible=true、needVisible=false",
|
||
Shown.IsVisible && !Shown.NeedVisible
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:HideGaragePreview 置 isVisible=false",
|
||
!ReduceGaragePreview(Base, HideGaragePreview()).IsVisible
|
||
)
|
||
|
||
val Req = ShowRequest(
|
||
viewStyle = "new",
|
||
tankPreviewSettings = NewSettings,
|
||
needShowDrone = true,
|
||
photoMode = true,
|
||
withDelay = false,
|
||
)
|
||
val S1 = ReduceGaragePreview(Base, Req)
|
||
Check(
|
||
"切片 GaragePreview:ShowRequest(无延迟)保留 isVisible 并写 view/settings/photo/drone",
|
||
S1.IsVisible && !S1.NeedVisible && S1.ViewStyle == "new" &&
|
||
S1.TankPreviewSettings == NewSettings && S1.PhotoMode &&
|
||
S1.NeedShowDrone && S1.DynViewStyle == "dyn-old" &&
|
||
S1.Params == Params && S1.HullData == OldPart
|
||
)
|
||
val S2 = ReduceGaragePreview(Base, Req.copy(withDelay = true))
|
||
Check(
|
||
"切片 GaragePreview:ShowRequest(延迟)置 isVisible=false、needVisible=true",
|
||
!S2.IsVisible && S2.NeedVisible && S2.ViewStyle == "new"
|
||
)
|
||
|
||
Check(
|
||
"切片 GaragePreview:UpdatePreviewSettings 只写 tankPreviewSettings",
|
||
ReduceGaragePreview(Base, UpdatePreviewSettings(NewSettings)).TankPreviewSettings == NewSettings
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:UpdateViewStyle 无字段,原 fru_1=undefined 被 trs 视作省略",
|
||
ReduceGaragePreview(Base, UpdateViewStyle()) == Base
|
||
)
|
||
|
||
val Init = ReduceGaragePreview(S0, InitGarageResources(Params))
|
||
Check("切片 GaragePreview:InitGarageResources 写 params", Init.Params == Params)
|
||
|
||
val Expected1 = ReduceGaragePreview(S0, SetExpectedItemId(PreviewHullCategory, 42L))
|
||
Check(
|
||
"切片 GaragePreview:SetExpectedItemId 写分类键",
|
||
Expected1.ExpectedItemId[PreviewHullCategory] == 42L
|
||
)
|
||
val Expected2 = ReduceGaragePreview(Expected1, SetExpectedItemId(PreviewHullCategory, null))
|
||
Check(
|
||
"切片 GaragePreview:SetExpectedItemId(itemId=null) 删除分类键",
|
||
!Expected2.ExpectedItemId.containsKey(PreviewHullCategory)
|
||
)
|
||
|
||
Check(
|
||
"切片 GaragePreview:AddRequestedItem 加入 requestedItems",
|
||
ReduceGaragePreview(S0, AddRequestedItem(42L)).RequestedItems == setOf(42L)
|
||
)
|
||
|
||
val HullAction = SetHullData(
|
||
itemId = 42L,
|
||
hullEngineType = HullEngineType.HOVER,
|
||
resource = "hull",
|
||
emissionColors = Colors,
|
||
turretMountPoint = "mount",
|
||
skinOffset = "skin",
|
||
leftTrackTexToWorldRatio = 7,
|
||
rightTrackTexToWorldRatio = 8,
|
||
)
|
||
val HullBase = S0.copy(RequestedItems = setOf(42L))
|
||
val HullHit = ReduceGaragePreview(HullBase, HullAction)
|
||
Check(
|
||
"切片 GaragePreview:SetHullData 命中时写 hullData/hullEngineType、缓存动作并删 requested",
|
||
HullHit.HullData == TankPartData("hull", Colors, "mount", "skin", 7, 8) &&
|
||
HullHit.HullEngineType == HullEngineType.HOVER &&
|
||
HullHit.Item3dModelActionCache[42L] == HullAction &&
|
||
HullHit.RequestedItems.isEmpty()
|
||
)
|
||
val HullMiss = ReduceGaragePreview(
|
||
HullBase.copy(ExpectedItemId = mapOf<Any?, Any?>(PreviewHullCategory to 99L)),
|
||
HullAction,
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetHullData 未命中只缓存动作,不写 hullData",
|
||
HullMiss.HullData == null && HullMiss.HullEngineType == HullEngineType.TRACKED &&
|
||
HullMiss.Item3dModelActionCache[42L] == HullAction &&
|
||
HullMiss.RequestedItems.isEmpty()
|
||
)
|
||
|
||
val WeaponAction = SetWeaponData(
|
||
itemId = 43L,
|
||
turretMechanism = TurretMechanism.ELEVATING_BARREL,
|
||
resource = "weapon",
|
||
emissionColors = Colors,
|
||
)
|
||
val WeaponBase = S0.copy(RequestedItems = setOf(43L))
|
||
val WeaponHit = ReduceGaragePreview(WeaponBase, WeaponAction)
|
||
Check(
|
||
"切片 GaragePreview:SetWeaponData 命中时写 weaponData/turretMechanism、缓存动作并删 requested",
|
||
WeaponHit.WeaponData == TankPartData("weapon", Colors) &&
|
||
WeaponHit.TurretMechanism == TurretMechanism.ELEVATING_BARREL &&
|
||
WeaponHit.Item3dModelActionCache[43L] == WeaponAction &&
|
||
WeaponHit.RequestedItems.isEmpty()
|
||
)
|
||
val WeaponMiss = ReduceGaragePreview(
|
||
WeaponBase.copy(ExpectedItemId = mapOf<Any?, Any?>(PreviewWeaponCategory to 99L)),
|
||
WeaponAction,
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetWeaponData 未命中只缓存动作,不写 weaponData",
|
||
WeaponMiss.WeaponData == null &&
|
||
WeaponMiss.TurretMechanism == TurretMechanism.ROTATING_TURRET &&
|
||
WeaponMiss.Item3dModelActionCache[43L] == WeaponAction
|
||
)
|
||
|
||
val DroneAction = SetDrone(44L, "drone", Colors)
|
||
val DroneHit = ReduceGaragePreview(S0.copy(RequestedItems = setOf(44L)), DroneAction)
|
||
Check(
|
||
"切片 GaragePreview:SetDrone 命中时写 droneData 并删 requested",
|
||
DroneHit.DroneData == TankPartData("drone", Colors) &&
|
||
DroneHit.Item3dModelActionCache[44L] == DroneAction &&
|
||
DroneHit.RequestedItems.isEmpty()
|
||
)
|
||
val DroneMiss = ReduceGaragePreview(
|
||
S0.copy(
|
||
DroneData = OldPart,
|
||
ExpectedItemId = mapOf<Any?, Any?>(PreviewDroneCategory to 99L),
|
||
),
|
||
DroneAction,
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetDrone 未命中保留 droneData",
|
||
DroneMiss.DroneData == OldPart && DroneMiss.Item3dModelActionCache[44L] == DroneAction
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetDrone(resource=null) 命中时写 null",
|
||
ReduceGaragePreview(S0.copy(DroneData = OldPart), SetDrone(45L, null, Colors)).DroneData == null
|
||
)
|
||
|
||
Check(
|
||
"切片 GaragePreview:SetGrenadeData 写 grenadeData",
|
||
ReduceGaragePreview(S0, SetGrenadeData("grenade", Colors)).GrenadeData ==
|
||
TankPartData("grenade", Colors)
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetGrenadeData(resource=null) 写 null",
|
||
ReduceGaragePreview(S0.copy(GrenadeData = OldPart), SetGrenadeData(null, Colors)).GrenadeData == null
|
||
)
|
||
|
||
Check("切片 GaragePreview:ClearHullData", ReduceGaragePreview(Base, ClearHullData()).HullData == null)
|
||
Check("切片 GaragePreview:ClearWeaponData", ReduceGaragePreview(Base, ClearWeaponData()).WeaponData == null)
|
||
Check("切片 GaragePreview:ClearDroneData", ReduceGaragePreview(Base, ClearDroneData()).DroneData == null)
|
||
Check("切片 GaragePreview:ClearGrenadeData", ReduceGaragePreview(Base, ClearGrenadeData()).GrenadeData == null)
|
||
|
||
Check(
|
||
"切片 GaragePreview:SetHasGrenades",
|
||
ReduceGaragePreview(S0, SetHasGrenades()).HasGrenades
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:ClearHasGrenades",
|
||
!ReduceGaragePreview(S0.copy(HasGrenades = true), ClearHasGrenades()).HasGrenades
|
||
)
|
||
|
||
val PaintAction = SetPaint(46L, NewPaint)
|
||
val PaintHit = ReduceGaragePreview(S0.copy(RequestedItems = setOf(46L)), PaintAction)
|
||
Check(
|
||
"切片 GaragePreview:SetPaint 命中时写 paint、缓存动作并删 requested",
|
||
PaintHit.Paint == NewPaint && PaintHit.Item3dModelActionCache[46L] == PaintAction &&
|
||
PaintHit.RequestedItems.isEmpty()
|
||
)
|
||
val PaintMiss = ReduceGaragePreview(
|
||
S0.copy(
|
||
Paint = OldPaint,
|
||
ExpectedItemId = mapOf<Any?, Any?>(PreviewPaintCategory to 99L),
|
||
),
|
||
PaintAction,
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetPaint 未命中保留 paint",
|
||
PaintMiss.Paint == OldPaint && PaintMiss.Item3dModelActionCache[46L] == PaintAction
|
||
)
|
||
|
||
Check(
|
||
"切片 GaragePreview:ResetItem3dModelActionCache 删缓存",
|
||
!ReduceGaragePreview(PaintHit, ResetItem3dModelActionCache(46L))
|
||
.Item3dModelActionCache.containsKey(46L)
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:SetMountedItemsProperties 写 properties",
|
||
ReduceGaragePreview(S0, SetMountedItemsProperties(listOf("ROTATING_WEAPON", "HOVER_CHASSIS")))
|
||
.Properties == listOf("ROTATING_WEAPON", "HOVER_CHASSIS")
|
||
)
|
||
|
||
var Shots = 0
|
||
var Videos = 0
|
||
val Capture = ReduceGaragePreview(S0, SetCaptureActions({ Shots += 1 }, { Videos += 1 }))
|
||
Check(
|
||
"切片 GaragePreview:SetCaptureActions 写两个回调",
|
||
Capture.CaptureScreenshotAction != null && Capture.CaptureVideoAction != null
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:CaptureScreenshot 只调用回调、状态不变",
|
||
ReduceGaragePreview(Capture, CaptureScreenshot()) == Capture && Shots == 1
|
||
)
|
||
Check(
|
||
"切片 GaragePreview:CaptureVideo 只调用回调、状态不变",
|
||
ReduceGaragePreview(Capture, CaptureVideo()) == Capture && Videos == 1
|
||
)
|
||
|
||
Check(
|
||
"切片 GaragePreview:无关动作返回等值状态",
|
||
ReduceGaragePreview(Base, AddFrags(1)) == Base
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddRequestedItem(7L))
|
||
Check(
|
||
"根:AddRequestedItem 只动 garagePreview",
|
||
Root1.GaragePreview.RequestedItems == setOf(7L) && Root1.Garage == Root0.Garage &&
|
||
Root1.Shop == Root0.Shop && Root1.Screen == Root0.Screen && Root1.LootBoxes == Root0.LootBoxes
|
||
)
|
||
val Root2 = ReduceRoot(Root1, SetHasGrenades())
|
||
Check(
|
||
"根:SetHasGrenades 保留 garagePreview 前一次归约",
|
||
Root2.GaragePreview.HasGrenades && Root2.GaragePreview.RequestedItems == setOf(7L)
|
||
)
|
||
Check("根:garagePreview 迁移后无关动作仍返回等值状态", ReduceRoot(Root2, AddFrags(1)) == Root2)
|
||
|
||
val W = RootWiring.ByField("garagePreview")!!
|
||
Check(
|
||
"根:garagePreview 委派到 IG / fs.eru 且 22 个动作全部具名",
|
||
W.SliceClass == "IG" && W.Reducer == "fs.eru" && W.Actions.size == 22 &&
|
||
W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:garagePreview 的动作名覆盖 22 个 instanceof 臂",
|
||
listOf(
|
||
"AddRequestedItem", "CaptureScreenshot", "CaptureVideo", "ClearDroneData",
|
||
"ClearGrenadeData", "ClearHasGrenades", "ClearHullData", "ClearWeaponData",
|
||
"InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions",
|
||
"SetDrone", "SetExpectedItemId", "SetGrenadeData", "SetHasGrenades",
|
||
"SetHullData", "SetMountedItemsProperties", "SetPaint", "SetWeaponData",
|
||
"ShowRequest", "UpdatePreviewSettings", "UpdateViewStyle",
|
||
).all { it in W.Actions }
|
||
)
|
||
Check(
|
||
"根:garagePreview 的动作表不含 IG 标识和 3 个单例臂",
|
||
W.Actions.none {
|
||
it == "IG" || it == "GaragePreview" ||
|
||
it == "ClearGaragePreviewParams" || it == "ShowGaragePreview" || it == "HideGaragePreview"
|
||
}
|
||
)
|
||
}
|
||
|
||
// ㉑ 切片类型收口:launchParams(原 hpx_1;根 reducer 纯透传,无动作)
|
||
run {
|
||
val S0 = RootState()
|
||
Check(
|
||
"切片 LaunchParams:默认是空 Map",
|
||
S0.LaunchParams.isEmpty()
|
||
)
|
||
|
||
val Params = mapOf(
|
||
"token" to "token-1",
|
||
"singleUseHash" to "hash-1",
|
||
"changeUidHash" to "uid-hash-1",
|
||
"failed-redirect-url" to "https://example.test/failed",
|
||
)
|
||
val S1 = S0.copy(LaunchParams = Params)
|
||
Check(
|
||
"根:launchParams 在无关动作后原样保留",
|
||
ReduceRoot(S1, AddFrags(1)).LaunchParams == Params
|
||
)
|
||
Check(
|
||
"根:其他切片动作不修改 launchParams",
|
||
ReduceRoot(S1, ApplyInvite("uid-1")).LaunchParams == Params
|
||
)
|
||
|
||
val W = RootWiring.ByField("launchParams")!!
|
||
Check(
|
||
"根:launchParams 是无 SliceClass / 无动作的纯透传",
|
||
W.SliceClass == "?" && W.Reducer == "(pass-through)" &&
|
||
W.Delegate == "n.hpx_1" && W.Actions.isEmpty()
|
||
)
|
||
Check(
|
||
"根:launchParams 计入迁移表且不再出现在待迁清单",
|
||
"launchParams" in RootReducer.Migrated &&
|
||
RootWiring.Unmigrated.none { it.Field == "launchParams" }
|
||
)
|
||
}
|
||
|
||
// ㉒ 切片 reducer:Entrance(原 module_10032 的 hD / Bv.pth,8 字段;7 个 instanceof 臂)
|
||
run {
|
||
val S0 = EntranceStateSlice()
|
||
Check(
|
||
"切片 Entrance:默认值与原构造一致(showSocialNetworks 缺省 true,其余 false)",
|
||
S0.ShowSocialNetworks && !S0.ControlsForChina &&
|
||
!S0.IsRunningInChinaDataCenter && !S0.IsRunningInChinaPartners &&
|
||
!S0.IsAgeWarnShow && !S0.AuthorizedByChinaSdkToken &&
|
||
!S0.IsClassic && !S0.AuthorizedByClassicEarlyAccessToken
|
||
)
|
||
|
||
Check(
|
||
"切片 Entrance:HideSocialNetworks 置 showSocialNetworks=false",
|
||
!ReduceEntrance(S0, HideSocialNetworks()).ShowSocialNetworks
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetControlsForChina 置 controlsForChina=true",
|
||
ReduceEntrance(S0, SetControlsForChina()).ControlsForChina
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetRunningInChinaDataCenter 置对应字段",
|
||
ReduceEntrance(S0, SetRunningInChinaDataCenter()).IsRunningInChinaDataCenter
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetRunningInChinaPartners 置对应字段",
|
||
ReduceEntrance(S0, SetRunningInChinaPartners()).IsRunningInChinaPartners
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetAgeWarnShow 写入 isAgeWarnShow",
|
||
ReduceEntrance(S0, SetAgeWarnShow(true)).IsAgeWarnShow &&
|
||
!ReduceEntrance(S0.copy(IsAgeWarnShow = true), SetAgeWarnShow(false)).IsAgeWarnShow
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetAuthorizedByChinaSdkToken 置对应字段",
|
||
ReduceEntrance(S0, SetAuthorizedByChinaSdkToken()).AuthorizedByChinaSdkToken
|
||
)
|
||
Check(
|
||
"切片 Entrance:SetClassic 置 isClassic",
|
||
ReduceEntrance(S0, SetClassic()).IsClassic
|
||
)
|
||
|
||
val All = listOf(
|
||
HideSocialNetworks(),
|
||
SetControlsForChina(),
|
||
SetRunningInChinaDataCenter(),
|
||
SetRunningInChinaPartners(),
|
||
SetAgeWarnShow(true),
|
||
SetAuthorizedByChinaSdkToken(),
|
||
SetClassic(),
|
||
).fold(S0) { S, A -> ReduceEntrance(S, A) }
|
||
Check(
|
||
"切片 Entrance:七个动作依次归约后只改对应字段",
|
||
!All.ShowSocialNetworks && All.ControlsForChina &&
|
||
All.IsRunningInChinaDataCenter && All.IsRunningInChinaPartners &&
|
||
All.IsAgeWarnShow && All.AuthorizedByChinaSdkToken &&
|
||
All.IsClassic && !All.AuthorizedByClassicEarlyAccessToken
|
||
)
|
||
Check("切片 Entrance:无关动作不改状态", ReduceEntrance(All, AddFrags(1)) == All)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetClassic())
|
||
Check(
|
||
"根:SetClassic 只动 entrance",
|
||
Root1.Entrance.IsClassic && Root1.LoginByPassword == Root0.LoginByPassword &&
|
||
Root1.GaragePreview == Root0.GaragePreview && Root1.Garage == Root0.Garage
|
||
)
|
||
Check(
|
||
"根:entrance 无关动作保留前一次归约",
|
||
ReduceRoot(Root1, AddFrags(1)).Entrance == Root1.Entrance
|
||
)
|
||
|
||
val W = RootWiring.ByField("entrance")!!
|
||
Check(
|
||
"根:entrance 委派到 hD / Bv.pth 且 7 个动作全部具名",
|
||
W.SliceClass == "hD" && W.Reducer == "Bv.pth" && W.Actions.size == 7 &&
|
||
W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:entrance 的动作名覆盖 7 个 instanceof 臂",
|
||
listOf(
|
||
"HideSocialNetworks", "SetAgeWarnShow", "SetAuthorizedByChinaSdkToken",
|
||
"SetClassic", "SetControlsForChina", "SetRunningInChinaDataCenter",
|
||
"SetRunningInChinaPartners",
|
||
).all { it in W.Actions }
|
||
)
|
||
}
|
||
|
||
// ㉓ 切片 reducer:User(原 module_10032 的 t8 / M8().qtd,32 字段;28 个 instanceof 臂)
|
||
run {
|
||
val S0 = UserStateSlice()
|
||
Check(
|
||
"切片 User:默认值与原构造一致",
|
||
!S0.IsUserPropertiesLoaded && S0.Id == 0L && S0.Clan == null &&
|
||
S0.Crystals == 0 && S0.Coins == 0 && S0.SpentCoins == 0 &&
|
||
S0.Rubies == 0 && S0.SpentRubies == 0 && !S0.HasTemporaryCash &&
|
||
S0.Rank == 1 && S0.RankBounds == UserRankBounds(0, 0) &&
|
||
S0.Score == 0 && S0.GearScore == 0 && S0.WeaponGS == 0 &&
|
||
S0.HullGS == 0 && S0.DroneGS == 0 && S0.BazookaGS == 0 &&
|
||
S0.ResistanceGS == 0 && S0.Rating == 0 && S0.Uid == "" &&
|
||
S0.ClanTag == null && S0.PremiumExpirationTime == 0L &&
|
||
S0.DisabledWithoutPremiumLocations.isEmpty() && !S0.HasPremium &&
|
||
!S0.HasSpectatorPermissions && S0.RankNames.isEmpty() &&
|
||
S0.NormalizedRank == 1 && S0.ProfileUrl == "" &&
|
||
S0.EventPassEndTime == 0L && S0.LobbyTutorialEnabled &&
|
||
!S0.InDebt && S0.BatteryLevel == null
|
||
)
|
||
|
||
Check(
|
||
"切片 User:SetClanInfo 写 clan",
|
||
ReduceUser(S0, SetClanInfo("clan-1")).Clan == "clan-1"
|
||
)
|
||
Check(
|
||
"切片 User:UpdateCrystals / SpendCrystals 写 crystal",
|
||
ReduceUser(S0, UpdateCrystals(50)).Crystals == 50 &&
|
||
ReduceUser(S0.copy(Crystals = 50), SpendCrystals(20)).Crystals == 30
|
||
)
|
||
|
||
val Coins = ReduceUser(
|
||
ReduceUser(ReduceUser(S0, SetCoins(100)), SpendCoins(30)),
|
||
AddCoins(10),
|
||
)
|
||
Check(
|
||
"切片 User:SetCoins / SpendCoins / AddCoins 维护 coins 与 spentCoins",
|
||
Coins.Coins == 110 && Coins.SpentCoins == 0
|
||
)
|
||
|
||
val Rubies = ReduceUser(
|
||
ReduceUser(ReduceUser(S0, SetRubies(80)), SpendRubies(20)),
|
||
AddRubies(5),
|
||
)
|
||
Check(
|
||
"切片 User:SetRubies / SpendRubies / AddRubies 维护 rubies 与 spentRubies",
|
||
Rubies.Rubies == 85 && Rubies.SpentRubies == 0
|
||
)
|
||
|
||
Check(
|
||
"切片 User:SetHasTemporaryCash 写 hasTemporaryCash",
|
||
ReduceUser(S0, SetHasTemporaryCash(true)).HasTemporaryCash
|
||
)
|
||
Check(
|
||
"切片 User:UpdateUserRating 写 rating",
|
||
ReduceUser(S0, UpdateUserRating(12)).Rating == 12
|
||
)
|
||
Check(
|
||
"切片 User:UpdateUid / UpdateClanTag 写 uid / clanTag",
|
||
ReduceUser(S0, UpdateUid("uid-42")).Uid == "uid-42" &&
|
||
ReduceUser(S0, UpdateClanTag("TAG")).ClanTag == "TAG"
|
||
)
|
||
Check(
|
||
"切片 User:UpdateScore 写 score",
|
||
ReduceUser(S0, UpdateScore(team = "red", score = 123)).Score == 123
|
||
)
|
||
Check(
|
||
"切片 User:UpdateGearScore 写六个装备分",
|
||
ReduceUser(S0, UpdateGearScore(1, 2, 3, 4, 5, 6)).let {
|
||
it.GearScore == 1 && it.WeaponGS == 2 && it.HullGS == 3 &&
|
||
it.DroneGS == 4 && it.BazookaGS == 5 && it.ResistanceGS == 6
|
||
}
|
||
)
|
||
|
||
val Rank = ReduceUser(
|
||
S0.copy(RankNames = listOf("A", "B", "C")),
|
||
UpdateRank(rank = 5, score = 500, bounds = UserRankBounds(1, 10)),
|
||
)
|
||
Check(
|
||
"切片 User:UpdateRank 写 rank/rankBounds/score 并重算 normalizedRank",
|
||
Rank.Rank == 5 && Rank.Score == 500 &&
|
||
Rank.RankBounds == UserRankBounds(1, 10) && Rank.NormalizedRank == 3
|
||
)
|
||
|
||
val Props = UserPropertiesCC(
|
||
bazookaGearScore = 6,
|
||
canUseGroup = true,
|
||
crystals = 321,
|
||
crystalsRating = 0,
|
||
droneGearScore = 5,
|
||
eventPassEndTime = 0,
|
||
gearScore = 21,
|
||
goldsTakenRating = 0,
|
||
hasSpectatorPermissions = true,
|
||
hullGearScore = 4,
|
||
id = 99L,
|
||
partnerUid = null,
|
||
rank = 7,
|
||
rankBounds = "1-10",
|
||
topBound = "10",
|
||
registrationTimestamp = 0,
|
||
resistanceGearScore = 3,
|
||
score = 777,
|
||
scoreRating = 0,
|
||
uid = "uid-99",
|
||
userProfileUrl = "https://example.test/u/99",
|
||
userRating = 11,
|
||
weaponGearScore = 2,
|
||
)
|
||
val SP = ReduceUser(
|
||
S0.copy(RankNames = listOf("A", "B", "C")),
|
||
SetUserPropertiesAction(Props),
|
||
)
|
||
Check(
|
||
"切片 User:SetUserPropertiesAction 复制服务端字段并置 isUserPropertiesLoaded",
|
||
SP.IsUserPropertiesLoaded && SP.Id == 99L && SP.Crystals == 321 &&
|
||
SP.Rank == 7 && SP.RankBounds == "1-10" && SP.Score == 777 &&
|
||
SP.GearScore == 21 && SP.WeaponGS == 2 && SP.HullGS == 4 &&
|
||
SP.DroneGS == 5 && SP.BazookaGS == 6 && SP.ResistanceGS == 3 &&
|
||
SP.Rating == 11 && SP.Uid == "uid-99" &&
|
||
SP.HasSpectatorPermissions && SP.NormalizedRank == 3 &&
|
||
SP.ProfileUrl == "https://example.test/u/99"
|
||
)
|
||
|
||
Check(
|
||
"切片 User:SetPremiumExpirationTime / UpdateEventPass 写时间戳",
|
||
ReduceUser(S0, SetPremiumExpirationTime(1_700_000_000_000L))
|
||
.PremiumExpirationTime == 1_700_000_000_000L &&
|
||
ReduceUser(S0, UpdateEventPass(1_700_000_000_001L))
|
||
.EventPassEndTime == 1_700_000_000_001L
|
||
)
|
||
Check(
|
||
"切片 User:SetDisabledWithoutPremiumLocations 去重成 Set",
|
||
ReduceUser(S0, SetDisabledWithoutPremiumLocations(listOf(1, 2, 2)))
|
||
.DisabledWithoutPremiumLocations == setOf(1, 2)
|
||
)
|
||
Check(
|
||
"切片 User:UpdatePremium / UpdateEventPass 写对应字段",
|
||
ReduceUser(S0, UpdatePremium(true)).HasPremium
|
||
)
|
||
Check(
|
||
"切片 User:SetRankNames 只写 rankNames",
|
||
ReduceUser(S0.copy(NormalizedRank = 9), SetRankNames(listOf("A", "B")))
|
||
.let { it.RankNames == listOf("A", "B") && it.NormalizedRank == 9 }
|
||
)
|
||
Check(
|
||
"切片 User:SetUserId 写 id,null 归零",
|
||
ReduceUser(S0, SetUserId(42L)).Id == 42L &&
|
||
ReduceUser(S0, SetUserId(null)).Id == 0L
|
||
)
|
||
Check(
|
||
"切片 User:DisableLobbyTutorial / SetBatteryLevel",
|
||
!ReduceUser(S0, DisableLobbyTutorial()).LobbyTutorialEnabled &&
|
||
ReduceUser(S0, SetBatteryLevel(0.75)).BatteryLevel == 0.75
|
||
)
|
||
Check(
|
||
"切片 User:SetInDebt / ClearInDebt 切换 inDebt",
|
||
ReduceUser(S0, SetInDebt()).InDebt &&
|
||
!ReduceUser(S0.copy(InDebt = true), ClearInDebt()).InDebt
|
||
)
|
||
Check("切片 User:无关动作不改状态", ReduceUser(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateCrystals(7))
|
||
Check(
|
||
"根:UpdateCrystals 只动 user",
|
||
Root1.User.Crystals == 7 &&
|
||
Root1.LoginByPassword == Root0.LoginByPassword &&
|
||
Root1.Entrance == Root0.Entrance
|
||
)
|
||
Check(
|
||
"根:user 无关动作保留前一次归约",
|
||
ReduceRoot(Root1, AddFrags(1)).User == Root1.User
|
||
)
|
||
|
||
val W = RootWiring.ByField("user")!!
|
||
Check(
|
||
"根:user 委派到 t8 / M8().qtd 且 27 个动作全部具名",
|
||
W.SliceClass == "t8" && W.Reducer == "M8().qtd" &&
|
||
W.Actions.size == 27 && W.Actions.none { it.startsWith("?") }
|
||
)
|
||
Check(
|
||
"根:user 的匿名动作名覆盖三个分支",
|
||
listOf("DisableLobbyTutorial", "SetInDebt", "ClearInDebt")
|
||
.all { it in W.Actions }
|
||
)
|
||
}
|
||
|
||
// ㉔ 切片 reducer:Settings(原 module_10032 的 Uut / xw.ltg,2 字段;2 个 instanceof 臂)
|
||
run {
|
||
val S0 = SettingsStateSlice()
|
||
Check(
|
||
"切片 Settings:默认 GAME / disableAccountSettings=false",
|
||
S0.CurrentCategory == SettingsCategory.GAME && !S0.DisableAccountSettings
|
||
)
|
||
Check(
|
||
"切片 Settings:SetSettingsCategory 只写 currentCategory",
|
||
ReduceSettings(S0, SetSettingsCategory(SettingsCategory.ACCOUNT)).let {
|
||
it.CurrentCategory == SettingsCategory.ACCOUNT && !it.DisableAccountSettings
|
||
}
|
||
)
|
||
Check(
|
||
"切片 Settings:DisableAccountSettings 只把开关置 true",
|
||
ReduceSettings(
|
||
S0.copy(CurrentCategory = SettingsCategory.SOUND),
|
||
DisableAccountSettings(),
|
||
).let {
|
||
it.CurrentCategory == SettingsCategory.SOUND && it.DisableAccountSettings
|
||
}
|
||
)
|
||
Check("切片 Settings:无关动作不改状态", ReduceSettings(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetSettingsCategory(SettingsCategory.CONTROL))
|
||
Check(
|
||
"根:SetSettingsCategory 只动 settings",
|
||
Root1.Settings.CurrentCategory == SettingsCategory.CONTROL &&
|
||
Root1.User == Root0.User && Root1.Home == Root0.Home
|
||
)
|
||
Check(
|
||
"根:settings 无关动作保留前一次归约",
|
||
ReduceRoot(Root1, AddFrags(1)).Settings == Root1.Settings
|
||
)
|
||
|
||
val W = RootWiring.ByField("settings")!!
|
||
Check(
|
||
"根:settings 委派到 Uut / xw.ltg 且 2 个动作全部具名",
|
||
W.SliceClass == "Uut" && W.Reducer == "xw.ltg" &&
|
||
W.Actions.toSet() == setOf("DisableAccountSettings", "SetSettingsCategory")
|
||
)
|
||
}
|
||
|
||
// ㉕ 切片 reducer:AccountSettings(oet / Lp.mtg,13 字段;13 个 instanceof 臂)
|
||
run {
|
||
val S0 = AccountSettingsStateSlice()
|
||
Check(
|
||
"切片 AccountSettings:默认值完整",
|
||
!S0.IsCurrentPasswordMandatory && S0.CurrentPassword == "" &&
|
||
S0.NewPassword == "" && S0.RepeatNewPassword == "" &&
|
||
S0.NewPasswordStatus == PasswordStatus.NONE && S0.CountryCode == null &&
|
||
S0.CountryName == null && S0.Email == null && !S0.EmailConfirmed &&
|
||
!S0.LocationCheckEnabled && !S0.IsUnlockButtonForRenameUid &&
|
||
!S0.IsClearInputUid && S0.RemovalTimer == null
|
||
)
|
||
val Reset = ReduceAccountSettings(
|
||
S0.copy(
|
||
CurrentPassword = "old",
|
||
NewPassword = "new",
|
||
RepeatNewPassword = "new",
|
||
NewPasswordStatus = PasswordStatus.OK,
|
||
IsUnlockButtonForRenameUid = true,
|
||
Email = "keep@example.com",
|
||
RemovalTimer = 42L,
|
||
),
|
||
ResetPasswordFields,
|
||
)
|
||
Check(
|
||
"切片 AccountSettings:ResetPasswordFields 只清密码三件套/状态/解锁按钮",
|
||
Reset.CurrentPassword == "" && Reset.NewPassword == "" &&
|
||
Reset.RepeatNewPassword == "" && Reset.NewPasswordStatus == PasswordStatus.NONE &&
|
||
!Reset.IsUnlockButtonForRenameUid && Reset.Email == "keep@example.com" &&
|
||
Reset.RemovalTimer == 42L
|
||
)
|
||
val S1 = listOf<Any>(
|
||
SetCurrentPassword("cur"),
|
||
SetNewPassword("next"),
|
||
SetRepeatNewPassword("next"),
|
||
SetPasswordStatus(PasswordStatus.SIMPLE),
|
||
SetCurrentPasswordMandatory(true),
|
||
SetEmail("user@example.com"),
|
||
SetEmailConfirmed(true),
|
||
SetCountry("RU", "Russia"),
|
||
SetUnlockButtonForRenameUid(true),
|
||
ClearInputUid(true),
|
||
SetAccountRemovalDays(1_700_000_000_000L),
|
||
).fold(S0) { S, A -> ReduceAccountSettings(S, A) }
|
||
Check(
|
||
"切片 AccountSettings:11 个单字段动作按顺序写入",
|
||
S1.CurrentPassword == "cur" && S1.NewPassword == "next" &&
|
||
S1.RepeatNewPassword == "next" && S1.NewPasswordStatus == PasswordStatus.SIMPLE &&
|
||
S1.IsCurrentPasswordMandatory && S1.Email == "user@example.com" &&
|
||
S1.EmailConfirmed && S1.CountryCode == "RU" && S1.CountryName == "Russia" &&
|
||
S1.IsUnlockButtonForRenameUid && S1.IsClearInputUid &&
|
||
S1.RemovalTimer == 1_700_000_000_000L
|
||
)
|
||
Check(
|
||
"切片 AccountSettings:ClearAccountRemovalTimer 只清 removalTimer",
|
||
ReduceAccountSettings(S1, ClearAccountRemovalTimer).let {
|
||
it.RemovalTimer == null && it.Email == "user@example.com"
|
||
}
|
||
)
|
||
Check(
|
||
"切片 AccountSettings:无关动作不改状态",
|
||
ReduceAccountSettings(S0, AddFrags(1)) == S0
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetCurrentPassword("root-pass"))
|
||
Check(
|
||
"根:SetCurrentPassword 只动 accountSettings",
|
||
Root1.AccountSettings.CurrentPassword == "root-pass" &&
|
||
Root1.Language == Root0.Language && Root1.ProBattle == Root0.ProBattle
|
||
)
|
||
Check(
|
||
"根:accountSettings 无关动作保留前一次归约",
|
||
ReduceRoot(Root1, AddFrags(1)).AccountSettings == Root1.AccountSettings
|
||
)
|
||
val W = RootWiring.ByField("accountSettings")!!
|
||
Check(
|
||
"根:accountSettings 委派到 oet / Lp.mtg 且 13 个动作全部具名",
|
||
W.SliceClass == "oet" && W.Reducer == "Lp.mtg" && W.Actions.size == 13 &&
|
||
W.Actions.containsAll(
|
||
setOf(
|
||
"ClearAccountRemovalTimer", "ClearInputUid", "ResetPasswordFields",
|
||
"SetAccountRemovalDays", "SetCountry", "SetCurrentPassword",
|
||
"SetCurrentPasswordMandatory", "SetEmail", "SetEmailConfirmed",
|
||
"SetNewPassword", "SetPasswordStatus", "SetRepeatNewPassword",
|
||
"SetUnlockButtonForRenameUid",
|
||
)
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㉖ 切片 reducer:ProBattle(Wtt / uv.wth,3 字段;2 个 instanceof 臂)
|
||
run {
|
||
val S0 = ProBattleStateSlice()
|
||
Check(
|
||
"切片 ProBattle:默认 -1 / NONE / 空密码表",
|
||
S0.LastFightBattleId == -1L && S0.LastFightBattleTeam == BattleTeam.NONE &&
|
||
S0.BattlePasswords.isEmpty()
|
||
)
|
||
val Fight1 = ReduceProBattle(S0, Fight(10L, BattleTeam.TEAM_A))
|
||
Check(
|
||
"切片 ProBattle:Fight 写上一场 id + team",
|
||
Fight1.LastFightBattleId == 10L && Fight1.LastFightBattleTeam == BattleTeam.TEAM_A
|
||
)
|
||
val WithPass = ReduceProBattle(Fight1, SetBattlePassword(10L, "secret"))
|
||
Check(
|
||
"切片 ProBattle:SetPassword 非空时写入密码表",
|
||
WithPass.BattlePasswords == mapOf(10L to "secret")
|
||
)
|
||
Check(
|
||
"切片 ProBattle:SetPassword 空串时删除密码表项",
|
||
ReduceProBattle(WithPass, SetBattlePassword(10L, "")).BattlePasswords.isEmpty()
|
||
)
|
||
Check("切片 ProBattle:无关动作不改状态", ReduceProBattle(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, Fight(77L, BattleTeam.TEAM_B))
|
||
Check(
|
||
"根:Fight 只动 proBattle",
|
||
Root1.ProBattle.LastFightBattleId == 77L &&
|
||
Root1.ProBattle.LastFightBattleTeam == BattleTeam.TEAM_B &&
|
||
Root1.AccountSettings == Root0.AccountSettings
|
||
)
|
||
val W = RootWiring.ByField("proBattle")!!
|
||
Check(
|
||
"根:proBattle 委派到 Wtt / uv.wth 且 2 个动作",
|
||
W.SliceClass == "Wtt" && W.Reducer == "uv.wth" &&
|
||
W.Actions.toSet() == setOf("Fight", "SetPassword")
|
||
)
|
||
}
|
||
|
||
// ㉗ 切片 reducer:Language(枚举值 / m6().jtg,1 个 instanceof 臂)
|
||
run {
|
||
Check("切片 Language:根状态默认 EN", RootState().Language == Language.EN)
|
||
Check(
|
||
"切片 Language:SetLanguage 写回枚举",
|
||
ReduceLanguage(Language.EN, SetLanguage(Language.ZH)) == Language.ZH
|
||
)
|
||
Check(
|
||
"切片 Language:无关动作保留当前语言",
|
||
ReduceLanguage(Language.ZH, AddFrags(1)) == Language.ZH
|
||
)
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetLanguage(Language.DE))
|
||
Check(
|
||
"根:SetLanguage 只动 language",
|
||
Root1.Language == Language.DE && Root1.Announcement == Root0.Announcement
|
||
)
|
||
val W = RootWiring.ByField("language")!!
|
||
Check(
|
||
"根:language 委派到 m6().jtg 且动作具名",
|
||
W.SliceClass == "?" && W.Reducer == "m6().jtg" &&
|
||
W.Actions == listOf("SetLanguage")
|
||
)
|
||
}
|
||
|
||
// ㉘ 切片 reducer:Announcement(lC / PC().rq5,5 字段;4 个 instanceof 臂)
|
||
run {
|
||
val S0 = AnnouncementStateSlice()
|
||
Check(
|
||
"切片 Announcement:默认值完整",
|
||
S0.IsEnabled && !S0.ModelLoaded && S0.Items.isEmpty() &&
|
||
!S0.WasInBattle && !S0.SkipNextPromotedAnnounce
|
||
)
|
||
val S1 = ReduceAnnouncement(
|
||
ReduceAnnouncement(
|
||
ReduceAnnouncement(
|
||
ReduceAnnouncement(S0, DisableAnnouncement),
|
||
SetAnnouncementModelLoaded,
|
||
),
|
||
SetAnnouncementItems(listOf("a", "b")),
|
||
),
|
||
SetSkipNextPromotedAnnounce(true),
|
||
)
|
||
Check(
|
||
"切片 Announcement:4 个臂分别写 enabled/modelLoaded/items/skip",
|
||
!S1.IsEnabled && S1.ModelLoaded && S1.Items == listOf<Any?>("a", "b") &&
|
||
S1.SkipNextPromotedAnnounce
|
||
)
|
||
Check(
|
||
"切片 Announcement:无关动作不改状态",
|
||
ReduceAnnouncement(S0, AddFrags(1)) == S0
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, DisableAnnouncement)
|
||
Check(
|
||
"根:DisableAnnouncement 只动 announcement",
|
||
!Root1.Announcement.IsEnabled && Root1.AccountSettings == Root0.AccountSettings
|
||
)
|
||
val W = RootWiring.ByField("announcement")!!
|
||
Check(
|
||
"根:announcement 委派到 lC / PC().rq5 且 4 个动作全部具名",
|
||
W.SliceClass == "lC" && W.Reducer == "PC().rq5" &&
|
||
W.Actions.toSet() == setOf(
|
||
"DisableAnnouncement", "SetAnnouncementItems",
|
||
"SetAnnouncementModelLoaded", "SetSkipNextPromotedAnnounce",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㉙ 切片 reducer:News(ZX / E_.asd,4 字段;7 个分支)
|
||
run {
|
||
val S0 = NewsStateSlice()
|
||
Check(
|
||
"切片 News:默认值完整",
|
||
!S0.IsOpen && S0.News.isEmpty() && S0.UnreadNewsCounter == 0 && !S0.P
|
||
)
|
||
val N1 = NewsItemData(10, "d1", 0, "h1", 1L, "")
|
||
val N2 = NewsItemData(30, "d2", 0, "h2", 2L, "")
|
||
val N3 = NewsItemData(20, "d3", 0, "h3", 3L, "")
|
||
Check(
|
||
"切片 News:SetNewsItemAction 按 dateInSeconds 降序",
|
||
ReduceNews(S0, SetNewsItemAction(listOf(N1, N2, N3))).News.map { it.id } ==
|
||
listOf(2L, 3L, 1L)
|
||
)
|
||
val S1 = ReduceNews(S0, SetNewsItemAction(listOf(N1, N2)))
|
||
Check(
|
||
"切片 News:AddNewsItemAction 追加后重新排序",
|
||
ReduceNews(S1, AddNewsItemAction(N3)).News.map { it.id } == listOf(2L, 3L, 1L)
|
||
)
|
||
Check(
|
||
"切片 News:RemoveNewsItemAction 按 id 删除",
|
||
ReduceNews(S1, RemoveNewsItemAction(2L)).News.map { it.id } == listOf(1L)
|
||
)
|
||
Check(
|
||
"切片 News:SetUnreadNewsCounter 写计数",
|
||
ReduceNews(S0, SetUnreadNewsCounter(5)).UnreadNewsCounter == 5
|
||
)
|
||
Check(
|
||
"切片 News:OpenNews/CloseNews 开关面板、SetNewsFlag 置 p",
|
||
ReduceNews(ReduceNews(ReduceNews(S0, OpenNews), CloseNews), SetNewsFlag) ==
|
||
NewsStateSlice(IsOpen = false, P = true)
|
||
)
|
||
Check("切片 News:无关动作不改状态", ReduceNews(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, OpenNews)
|
||
Check(
|
||
"根:OpenNews 只动 news",
|
||
Root1.News.IsOpen && Root1.Notifications == Root0.Notifications
|
||
)
|
||
val W = RootWiring.ByField("news")!!
|
||
Check(
|
||
"根:news 委派到 ZX / E_.asd 且 5 个动作全部具名",
|
||
W.SliceClass == "ZX" && W.Reducer == "E_.asd" &&
|
||
W.Actions.toSet() == setOf(
|
||
"AddNewsItemAction", "RemoveNewsItemAction", "SetNewsFlag",
|
||
"SetNewsItemAction", "SetUnreadNewsCounter",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㉚ 切片 reducer:Notifications(jJ / B_.ise,2 字段;4 个 instanceof 臂)
|
||
run {
|
||
val S0 = NotificationsStateSlice()
|
||
Check(
|
||
"切片 Notifications:默认值完整",
|
||
S0.Notifications.isEmpty() && S0.NotificationSound == null
|
||
)
|
||
val S1 = ReduceNotifications(S0, InitNotificationSound("ding"))
|
||
Check(
|
||
"切片 Notifications:InitNotificationSound 写音效",
|
||
S1.NotificationSound == "ding" && S1.Notifications.isEmpty()
|
||
)
|
||
val S2 = ReduceNotifications(
|
||
ReduceNotifications(S1, AddNotification("n1")),
|
||
AddNotification("n2"),
|
||
)
|
||
Check(
|
||
"切片 Notifications:AddNotification 追加到末尾",
|
||
S2.Notifications == listOf<Any?>("n1", "n2")
|
||
)
|
||
Check(
|
||
"切片 Notifications:RemoveNotifications 按谓词过滤",
|
||
ReduceNotifications(S2, RemoveNotifications { it == "n1" }).Notifications ==
|
||
listOf<Any?>("n2")
|
||
)
|
||
Check(
|
||
"切片 Notifications:RemoveFirstNotification 丢掉第一条",
|
||
ReduceNotifications(S2, RemoveFirstNotification).Notifications ==
|
||
listOf<Any?>("n2")
|
||
)
|
||
Check("切片 Notifications:无关动作不改状态", ReduceNotifications(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddNotification("x"))
|
||
Check(
|
||
"根:AddNotification 只动 notifications",
|
||
Root1.Notifications.Notifications == listOf<Any?>("x") && Root1.News == Root0.News
|
||
)
|
||
val W = RootWiring.ByField("notifications")!!
|
||
Check(
|
||
"根:notifications 委派到 jJ / B_.ise 且 4 个动作全部具名",
|
||
W.SliceClass == "jJ" && W.Reducer == "B_.ise" &&
|
||
W.Actions.toSet() == setOf(
|
||
"AddNotification", "InitNotificationSound",
|
||
"RemoveFirstNotification", "RemoveNotifications",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㉛ 切片 reducer:Quests(Frt / Yrt.otg,3 字段;GoTo 按目标屏幕切分区)
|
||
run {
|
||
val S0 = QuestsStateSlice()
|
||
Check(
|
||
"切片 Quests:默认值完整",
|
||
S0.CurrentSection == QuestSection.SPECIAL && S0.CurrentCategory == null &&
|
||
!S0.IsNotCompletedQuests
|
||
)
|
||
Check(
|
||
"切片 Quests:SetCurrentCategory 写分类",
|
||
ReduceQuests(S0, SetCurrentCategory("cat-1")).CurrentCategory == "cat-1"
|
||
)
|
||
Check(
|
||
"切片 Quests:SetNotCompletedQuests 读了不存在的字段,实际是 no-op",
|
||
ReduceQuests(QuestsStateSlice(IsNotCompletedQuests = true), SetNotCompletedQuests)
|
||
.IsNotCompletedQuests
|
||
)
|
||
Check(
|
||
"切片 Quests:屏幕名按原包拼接",
|
||
QuestCategoryScreen(QuestSection.SPECIAL, 7L).name ==
|
||
"Lobby.Quests.QuestCategory.SPECIAL.7" &&
|
||
ChallengeScreen(null).name == "Lobby.Quests.Challenge"
|
||
)
|
||
val S1 = ReduceQuests(S0, GoTo(QuestCategoryScreen(QuestSection.CONTRACT, 42L), false))
|
||
Check(
|
||
"切片 Quests:GoTo(QuestCategory) 切到目标分区",
|
||
S1.CurrentSection == QuestSection.CONTRACT
|
||
)
|
||
val S2 = ReduceQuests(S1, GoTo(ChallengeScreen(null), false))
|
||
Check(
|
||
"切片 Quests:GoTo(Challenge) 切到 CHALLENGE",
|
||
S2.CurrentSection == QuestSection.CHALLENGE
|
||
)
|
||
Check(
|
||
"切片 Quests:其他屏幕的 GoTo 不改状态",
|
||
ReduceQuests(S2, GoTo(NamedScreen("Lobby.Home"), true)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, GoTo(QuestCategoryScreen(QuestSection.CHALLENGE, null), false))
|
||
Check(
|
||
"根:GoTo 只动 quests 的分区",
|
||
Root1.Quests.CurrentSection == QuestSection.CHALLENGE &&
|
||
Root1.Notifications == Root0.Notifications
|
||
)
|
||
val W = RootWiring.ByField("quests")!!
|
||
Check(
|
||
"根:quests 委派到 Frt / Yrt.otg,屏幕标识类不算动作",
|
||
W.SliceClass == "Frt" && W.Reducer == "Yrt.otg" &&
|
||
W.Actions == listOf("GoTo", "SetCurrentCategory", "SetNotCompletedQuests")
|
||
)
|
||
}
|
||
|
||
// ㉜ 切片 reducer:LobbyLoader(KW / VW.es1,2 字段;FW/GW 两个匿名类)
|
||
run {
|
||
val S0 = LobbyLoaderStateSlice()
|
||
Check("切片 LobbyLoader:默认不显示、计数 0", !S0.IsShowLoader && S0.ActiveLoadersCount == 0)
|
||
val S1 = ReduceLobbyLoader(ReduceLobbyLoader(S0, ShowLoader), ShowLoader)
|
||
Check(
|
||
"切片 LobbyLoader:ShowLoader 计数 +1 并显示",
|
||
S1.IsShowLoader && S1.ActiveLoadersCount == 2
|
||
)
|
||
val S2 = ReduceLobbyLoader(S1, HideLoader)
|
||
Check(
|
||
"切片 LobbyLoader:HideLoader 计数 -1,仍大于 0 时继续显示",
|
||
S2.IsShowLoader && S2.ActiveLoadersCount == 1
|
||
)
|
||
val S3 = ReduceLobbyLoader(ReduceLobbyLoader(S2, HideLoader), HideLoader)
|
||
Check(
|
||
"切片 LobbyLoader:计数夹到 0 并隐藏",
|
||
!S3.IsShowLoader && S3.ActiveLoadersCount == 0
|
||
)
|
||
Check("切片 LobbyLoader:无关动作不改状态", ReduceLobbyLoader(S1, AddFrags(1)) == S1)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ShowLoader)
|
||
Check(
|
||
"根:ShowLoader 只动 lobbyLoader",
|
||
Root1.LobbyLoader.IsShowLoader && Root1.PurchaseDialogState == Root0.PurchaseDialogState
|
||
)
|
||
val W = RootWiring.ByField("lobbyLoader")!!
|
||
Check(
|
||
"根:lobbyLoader 委派到 KW / Bs.es1 且 2 个动作全部具名",
|
||
W.SliceClass == "KW" && W.Reducer == "Bs.es1" &&
|
||
W.Actions.toSet() == setOf("ShowLoader", "HideLoader")
|
||
)
|
||
}
|
||
|
||
// ㉝ 切片 reducer:PurchaseDialogState(CO / DO.ur2,4 字段;NO + 2 个实例臂)
|
||
run {
|
||
val S0 = PurchaseDialogStateSlice()
|
||
Check(
|
||
"切片 PurchaseDialog:默认值完整",
|
||
!S0.WithEmail && S0.Goods.isEmpty() && S0.EmailStatus == null && S0.Time == null
|
||
)
|
||
val S1 = ReducePurchaseDialog(S0, ChangeEmailStatus(PurchaseEmailStatus.BUSY))
|
||
Check(
|
||
"切片 PurchaseDialog:ChangeEmailStatus 只写邮箱状态",
|
||
S1.EmailStatus == PurchaseEmailStatus.BUSY && S1.Goods.isEmpty() && S1.Time == null
|
||
)
|
||
val S2 = ReducePurchaseDialog(S1, SuccessfullyPurchase(listOf("g1"), true, 123L))
|
||
Check(
|
||
"切片 PurchaseDialog:SuccessfullyPurchase 追加商品并写时间",
|
||
S2.WithEmail && S2.Goods == listOf<Any?>("g1") && S2.Time == 123L &&
|
||
S2.EmailStatus == PurchaseEmailStatus.BUSY
|
||
)
|
||
val S3 = ReducePurchaseDialog(S2, SuccessfullyPurchase(listOf("g2"), false, 456L))
|
||
Check(
|
||
"切片 PurchaseDialog:商品是追加而不是替换",
|
||
S3.Goods == listOf<Any?>("g1", "g2") && !S3.WithEmail && S3.Time == 456L
|
||
)
|
||
Check(
|
||
"切片 PurchaseDialog:ResetPurchaseDialog 复位四个字段",
|
||
ReducePurchaseDialog(S3, ResetPurchaseDialog) == PurchaseDialogStateSlice()
|
||
)
|
||
Check("切片 PurchaseDialog:无关动作不改状态", ReducePurchaseDialog(S3, AddFrags(1)) == S3)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ChangeEmailStatus(PurchaseEmailStatus.FREE))
|
||
Check(
|
||
"根:ChangeEmailStatus 只动 purchaseDialogState",
|
||
Root1.PurchaseDialogState.EmailStatus == PurchaseEmailStatus.FREE &&
|
||
Root1.LobbyLoader == Root0.LobbyLoader
|
||
)
|
||
val W = RootWiring.ByField("purchaseDialogState")!!
|
||
Check(
|
||
"根:purchaseDialogState 委派到 CO / je.ur2 且 3 个动作全部具名",
|
||
W.SliceClass == "CO" && W.Reducer == "je.ur2" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ChangeEmailStatus", "ResetPurchaseDialog", "SuccessfullyPurchase",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㉞ 切片 reducer:PromoCode(cZ / wZ.ysg,1 字段;2 个实例臂)
|
||
run {
|
||
val S0 = PromoCodeStateSlice()
|
||
Check("切片 PromoCode:默认 NONE", S0.ValidateStatus == PromoCodeStatus.NONE)
|
||
Check(
|
||
"切片 PromoCode:ActivatePromoCode 进入 WAIT",
|
||
ReducePromoCode(S0, ActivatePromoCode("CODE-1")).ValidateStatus == PromoCodeStatus.WAIT
|
||
)
|
||
Check(
|
||
"切片 PromoCode:SetCodeValidateStatus 写服务端状态",
|
||
ReducePromoCode(
|
||
PromoCodeStateSlice(PromoCodeStatus.WAIT),
|
||
SetCodeValidateStatus(PromoCodeStatus.ACTIVATED),
|
||
).ValidateStatus == PromoCodeStatus.ACTIVATED
|
||
)
|
||
Check("切片 PromoCode:无关动作不改状态", ReducePromoCode(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ActivatePromoCode("X"))
|
||
Check(
|
||
"根:ActivatePromoCode 只动 promoCode",
|
||
Root1.PromoCode.ValidateStatus == PromoCodeStatus.WAIT &&
|
||
Root1.SupportUrl == Root0.SupportUrl
|
||
)
|
||
val W = RootWiring.ByField("promoCode")!!
|
||
Check(
|
||
"根:promoCode 委派到 cZ / Q_.ysg 且 2 个动作",
|
||
W.SliceClass == "cZ" && W.Reducer == "Q_.ysg" &&
|
||
W.Actions == listOf("ActivatePromoCode", "SetCodeValidateStatus")
|
||
)
|
||
}
|
||
|
||
// ㉟ 切片 reducer:SupportUrl(I4 / P4.ct7,1 字段;A4 + R4)
|
||
run {
|
||
val S0 = SupportUrlStateSlice()
|
||
Check("切片 SupportUrl:默认为空串", S0.Url == "")
|
||
Check(
|
||
"切片 SupportUrl:SetUrl 写入链接",
|
||
ReduceSupportUrl(S0, SetUrl("https://support")).Url == "https://support"
|
||
)
|
||
Check(
|
||
"切片 SupportUrl:ClearSupportUrl 清空",
|
||
ReduceSupportUrl(SupportUrlStateSlice("https://support"), ClearSupportUrl).Url == ""
|
||
)
|
||
Check("切片 SupportUrl:无关动作不改状态", ReduceSupportUrl(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetUrl("u"))
|
||
Check(
|
||
"根:SetUrl 只动 supportUrl",
|
||
Root1.SupportUrl.Url == "u" && Root1.RulesUpdated == Root0.RulesUpdated
|
||
)
|
||
val W = RootWiring.ByField("supportUrl")!!
|
||
Check(
|
||
"根:supportUrl 委派到 I4 / Yl.ct7 且 2 个动作",
|
||
W.SliceClass == "I4" && W.Reducer == "Yl.ct7" &&
|
||
W.Actions.toSet() == setOf("ClearSupportUrl", "SetUrl")
|
||
)
|
||
}
|
||
|
||
// ㊱ 切片 reducer:RulesUpdated(i1 / u1.dsm,1 字段;1 个实例臂)
|
||
run {
|
||
val S0 = RulesUpdatedStateSlice()
|
||
Check("切片 RulesUpdated:默认 text = null", S0.Text == null)
|
||
Check(
|
||
"切片 RulesUpdated:UpdateRules 写入正文",
|
||
ReduceRulesUpdated(S0, UpdateRules("新规则")).Text == "新规则"
|
||
)
|
||
Check("切片 RulesUpdated:无关动作不改状态", ReduceRulesUpdated(S0, AddFrags(1)) == S0)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateRules("t"))
|
||
Check(
|
||
"根:UpdateRules 只动 rulesUpdated",
|
||
Root1.RulesUpdated.Text == "t" && Root1.NotificationByCountry == Root0.NotificationByCountry
|
||
)
|
||
val W = RootWiring.ByField("rulesUpdated")!!
|
||
Check(
|
||
"根:rulesUpdated 委派到 i1 / Ch.dsm",
|
||
W.SliceClass == "i1" && W.Reducer == "Ch.dsm" && W.Actions == listOf("UpdateRules")
|
||
)
|
||
}
|
||
|
||
// ㊲ 切片 reducer:NotificationByCountry(gJ / $J.ssd,4 字段;Init + qJ)
|
||
run {
|
||
val S0 = NotificationByCountryStateSlice()
|
||
Check(
|
||
"切片 NotificationByCountry:默认三条消息为 null、弹窗未展示",
|
||
S0.DisableRegistrationMessage == null && S0.DisableShopMessage == null &&
|
||
S0.HomeScreenMessage == null && !S0.HomeScreenDialogShown
|
||
)
|
||
val S1 = ReduceNotificationByCountry(
|
||
S0,
|
||
InitNotificationByCountry("r", "s", "h"),
|
||
)
|
||
Check(
|
||
"切片 NotificationByCountry:Init 写三条消息且不动弹窗标志",
|
||
S1.DisableRegistrationMessage == "r" && S1.DisableShopMessage == "s" &&
|
||
S1.HomeScreenMessage == "h" && !S1.HomeScreenDialogShown
|
||
)
|
||
Check(
|
||
"切片 NotificationByCountry:qJ 只把弹窗标志置 true",
|
||
ReduceNotificationByCountry(S1, MarkHomeScreenDialogShown) ==
|
||
S1.copy(HomeScreenDialogShown = true)
|
||
)
|
||
Check(
|
||
"切片 NotificationByCountry:无关动作不改状态",
|
||
ReduceNotificationByCountry(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, MarkHomeScreenDialogShown)
|
||
Check(
|
||
"根:qJ 只动 notificationByCountry",
|
||
Root1.NotificationByCountry.HomeScreenDialogShown &&
|
||
Root1.RulesUpdated == Root0.RulesUpdated
|
||
)
|
||
val W = RootWiring.ByField("notificationByCountry")!!
|
||
Check(
|
||
"根:notificationByCountry 委派到 gJ / R_.ssd 且 2 个动作",
|
||
W.SliceClass == "gJ" && W.Reducer == "R_.ssd" &&
|
||
W.Actions.toSet() == setOf("Init", "MarkHomeScreenDialogShown")
|
||
)
|
||
}
|
||
|
||
// ㊳ 切片 reducer:AvailabilityByRank(tet / set.xth,1 字段;1 个实例臂)
|
||
run {
|
||
val S0 = AvailabilityByRankStateSlice()
|
||
Check("切片 AvailabilityByRank:默认空表", S0.Data.isEmpty())
|
||
val S1 = ReduceAvailabilityByRank(S0, SetLoadedData(mapOf(1 to "a", 2 to "b")))
|
||
Check(
|
||
"切片 AvailabilityByRank:SetLoadedData 写整张表",
|
||
S1.Data == mapOf<Any?, Any?>(1 to "a", 2 to "b")
|
||
)
|
||
Check("切片 AvailabilityByRank:无关动作不改状态", ReduceAvailabilityByRank(S1, AddFrags(1)) == S1)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetLoadedData(mapOf(3 to "c")))
|
||
Check(
|
||
"根:SetLoadedData 只动 availabilityByRank",
|
||
Root1.AvailabilityByRank.Data == mapOf<Any?, Any?>(3 to "c") &&
|
||
Root1.PromoCode == Root0.PromoCode
|
||
)
|
||
val W = RootWiring.ByField("availabilityByRank")!!
|
||
Check(
|
||
"根:availabilityByRank 委派到 tet / \$p.xth",
|
||
W.SliceClass == "tet" && W.Reducer == "\$p.xth" &&
|
||
W.Actions == listOf("SetLoadedData")
|
||
)
|
||
}
|
||
|
||
// ㊴ 切片 reducer:Icons(oH / vs.kru,1 字段;_H → SetIcons)
|
||
run {
|
||
val S0 = IconsStateSlice()
|
||
Check("切片 Icons:默认空表", S0.Icons.isEmpty())
|
||
val S1 = ReduceIcons(
|
||
S0,
|
||
SetIcons(listOf(IconData("rank", "r1"), IconData("gold", "r2"))),
|
||
)
|
||
Check(
|
||
"切片 Icons:SetIcons 建成 icon→resource 表",
|
||
S1.Icons == mapOf<String, Any?>("rank" to "r1", "gold" to "r2")
|
||
)
|
||
Check("切片 Icons:无关动作不改状态", ReduceIcons(S1, AddFrags(1)) == S1)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetIcons(listOf(IconData("a", null))))
|
||
Check(
|
||
"根:SetIcons 只动 icons",
|
||
Root1.Icons.Icons.keys == setOf("a") &&
|
||
Root1.QuestCategories == Root0.QuestCategories
|
||
)
|
||
val W = RootWiring.ByField("icons")!!
|
||
Check(
|
||
"根:icons 委派到 oH / vs.kru 且 1 个动作",
|
||
W.SliceClass == "oH" && W.Reducer == "vs.kru" && W.Actions == listOf("SetIcons")
|
||
)
|
||
}
|
||
|
||
// ㊵ 切片 reducer:QuestCategories(zZ / th.lsh,3 字段;4 个实例臂)
|
||
run {
|
||
fun Cat(Id: Long, Index: Int): QuestCategoryData = QuestCategoryData(
|
||
color = "", description = "", endDate = null, hideIfEmpty = false, id = Id,
|
||
image = null, index = Index, name = "c$Id", rankRange = "", min = "",
|
||
startDate = null, type = "",
|
||
)
|
||
|
||
val A = Cat(1L, 2)
|
||
val B = Cat(2L, 1)
|
||
val S0 = QuestCategoriesStateSlice()
|
||
Check(
|
||
"切片 QuestCategories:默认空表、加载时间 0",
|
||
S0.Categories.isEmpty() && S0.Statistic.isEmpty() && S0.StatisticLoadedTime == 0L
|
||
)
|
||
val S1 = ReduceQuestCategories(S0, SetQuestCategories(listOf(A, B)))
|
||
Check(
|
||
"切片 QuestCategories:SetQuestCategories 按 index 升序",
|
||
S1.Categories == listOf(B, A)
|
||
)
|
||
Check(
|
||
"切片 QuestCategories:RemoveQuestCategory 按对象删除",
|
||
ReduceQuestCategories(S1, RemoveQuestCategory(B)).Categories == listOf(A)
|
||
)
|
||
Check(
|
||
"切片 QuestCategories:隐藏刷新不改加载时间",
|
||
ReduceQuestCategories(S1, RequestQuestCategoriesStatistic(true)) == S1
|
||
)
|
||
Check(
|
||
"切片 QuestCategories:强制刷新把加载时间推到 Long.MAX_VALUE",
|
||
ReduceQuestCategories(S1, RequestQuestCategoriesStatistic(false))
|
||
.StatisticLoadedTime == Long.MAX_VALUE
|
||
)
|
||
val S2 = ReduceQuestCategories(
|
||
S1,
|
||
SetQuestCategoriesStatistic(
|
||
listOf(
|
||
QuestCategoryStatistic(nearEndTime = null, questCategoryId = 1L, quests = 3),
|
||
QuestCategoryStatistic(nearEndTime = 5L, questCategoryId = 2L, quests = 4),
|
||
)
|
||
),
|
||
)
|
||
Check(
|
||
"切片 QuestCategories:统计按 questCategoryId 建索引并写当前时间",
|
||
S2.Statistic.keys == setOf(1L, 2L) && S2.StatisticLoadedTime > 0L
|
||
)
|
||
Check("切片 QuestCategories:无关动作不改状态", ReduceQuestCategories(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetQuestCategories(listOf(A)))
|
||
Check(
|
||
"根:SetQuestCategories 只动 questCategories",
|
||
Root1.QuestCategories.Categories == listOf(A) && Root1.MainQuest == Root0.MainQuest
|
||
)
|
||
val W = RootWiring.ByField("questCategories")!!
|
||
Check(
|
||
"根:questCategories 委派到 zZ / th.lsh 且 4 个动作",
|
||
W.SliceClass == "zZ" && W.Reducer == "th.lsh" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetQuestCategories", "RemoveQuestCategory",
|
||
"RequestQuestCategoriesStatistic", "SetQuestCategoriesStatistic",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊶ 切片 reducer:MainQuest(WZ / ih.bsk,5 字段;7 个动作,含 VZ→ResetQuestsLoaded)
|
||
run {
|
||
fun Quest(
|
||
Id: Long,
|
||
Name: String = "q$Id",
|
||
Completed: Boolean = false,
|
||
Count: Int = 0,
|
||
): MainQuestInfo = MainQuestInfo(
|
||
action = "", actionParams = null, activateTime = null, active = true,
|
||
battlePass = false, categoryId = 1L, completed = Completed, completedCount = Count,
|
||
conditions = null, battleMode = null, contextItemsInfo = null, id = Id, name = Name,
|
||
quantity = 1, skipForShowProgress = false, type = "", depends = emptyList(),
|
||
description = "", durationInMinutes = 0, eventPass = false,
|
||
extendedPeriodicalEndTime = null, extendedPeriodicalStartTime = null, icon = null,
|
||
minRank = 0, oneTime = false, pinned = false, premium = false, progress = null,
|
||
rewardExpireTime = null, rewards = "", count = 1, preview = null,
|
||
skipNewNotification = false, sortedIndex = 0, startTime = 0L, COMPLETE_QUEST = "",
|
||
)
|
||
|
||
val Q1 = Quest(1L)
|
||
val Q2 = Quest(2L, "q2", Completed = true, Count = 2)
|
||
val S0 = MainQuestStateSlice()
|
||
Check(
|
||
"切片 MainQuest:默认未加载、三个列表为空、活动商品 id 为 null",
|
||
!S0.IsLoaded && S0.Quests.isEmpty() && S0.RewardedQuests.isEmpty() &&
|
||
S0.EventPassShopItemId == null && S0.LastBattleUpdatedQuests.isEmpty()
|
||
)
|
||
val L1 = ReduceMainQuest(S0, QuestsLoaded(listOf(Q1)))
|
||
Check("切片 MainQuest:QuestsLoaded 只置 isLoaded", L1.IsLoaded && L1.Quests.isEmpty())
|
||
Check(
|
||
"切片 MainQuest:ResetQuestsLoaded 复位 isLoaded",
|
||
!ReduceMainQuest(L1, ResetQuestsLoaded).IsLoaded
|
||
)
|
||
|
||
val S1 = ReduceMainQuest(S0, SetQuests(listOf(Q1, Q2)))
|
||
Check(
|
||
"切片 MainQuest:SetQuests 按完成次数展开并清空已领奖",
|
||
S1.Quests.size == 3 &&
|
||
S1.Quests[0].completed && S1.Quests[0].completedCount == 1 &&
|
||
S1.Quests[1].completed && S1.Quests[1].completedCount == 2 &&
|
||
S1.Quests[2].id == 1L && !S1.Quests[2].completed &&
|
||
S1.RewardedQuests.isEmpty()
|
||
)
|
||
|
||
val S2 = ReduceMainQuest(S1, UpdateQuests(listOf(Quest(1L, "q1-new"), Quest(3L))))
|
||
Check(
|
||
"切片 MainQuest:UpdateQuests 按 id 就地替换、缺失追加",
|
||
S2.Quests.size == 4 && S2.Quests[2].name == "q1-new" && S2.Quests[3].id == 3L
|
||
)
|
||
|
||
val S3 = ReduceMainQuest(S2, AddRewardedQuest(Q1))
|
||
Check(
|
||
"切片 MainQuest:AddRewardedQuest 追加到已领奖列表",
|
||
S3.RewardedQuests == listOf(Q1)
|
||
)
|
||
Check(
|
||
"切片 MainQuest:SetEventPassShopItemId 写商品 id",
|
||
ReduceMainQuest(S3, SetEventPassShopItemId(77L)).EventPassShopItemId == 77L
|
||
)
|
||
Check(
|
||
"切片 MainQuest:SetBattleUpdatedQuests 写战斗增量",
|
||
ReduceMainQuest(S3, SetBattleUpdatedQuests(listOf("x")))
|
||
.LastBattleUpdatedQuests == listOf<Any?>("x")
|
||
)
|
||
Check("切片 MainQuest:无关动作不改状态", ReduceMainQuest(S3, AddFrags(1)) == S3)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetEventPassShopItemId(9L))
|
||
Check(
|
||
"根:SetEventPassShopItemId 只动 mainQuest",
|
||
Root1.MainQuest.EventPassShopItemId == 9L && Root1.Icons == Root0.Icons
|
||
)
|
||
val W = RootWiring.ByField("mainQuest")!!
|
||
Check(
|
||
"根:mainQuest 委派到 WZ / ih.bsk 且 7 个动作(含 ResetQuestsLoaded)",
|
||
W.SliceClass == "WZ" && W.Reducer == "ih.bsk" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ResetQuestsLoaded", "QuestsLoaded", "SetQuests", "UpdateQuests",
|
||
"AddRewardedQuest", "SetEventPassShopItemId", "SetBattleUpdatedQuests",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊷ 切片 reducer:SecuritySettings(V2 / pc.qsv,1 字段;K2 → SetProfileBlockStatus)
|
||
run {
|
||
val S0 = SecuritySettingsStateSlice()
|
||
Check("切片 SecuritySettings:默认隐藏个人资料", S0.IsProfileBlocked)
|
||
Check(
|
||
"切片 SecuritySettings:SetProfileBlockStatus 写布尔",
|
||
!ReduceSecuritySettings(S0, SetProfileBlockStatus(false)).IsProfileBlocked
|
||
)
|
||
Check(
|
||
"切片 SecuritySettings:无关动作不改状态",
|
||
ReduceSecuritySettings(S0, AddFrags(1)) == S0
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetProfileBlockStatus(false))
|
||
Check(
|
||
"根:SetProfileBlockStatus 只动 securitySettings",
|
||
!Root1.SecuritySettings.IsProfileBlocked && Root1.ReferralEvent == Root0.ReferralEvent
|
||
)
|
||
val W = RootWiring.ByField("securitySettings")!!
|
||
Check(
|
||
"根:securitySettings 委派到 V2 / pc.qsv",
|
||
W.SliceClass == "V2" && W.Reducer == "pc.qsv" &&
|
||
W.Actions == listOf("SetProfileBlockStatus")
|
||
)
|
||
}
|
||
|
||
// ㊸ 切片 reducer:ReferralEvent(pQ / lh.osk,14 字段;4 个实例臂)
|
||
run {
|
||
val S0 = ReferralEventStateSlice()
|
||
Check(
|
||
"切片 ReferralEvent:默认值完整",
|
||
S0.Referrer == null && S0.ReferrerUid == null && S0.ReferralsCount == 0 &&
|
||
S0.ReferralsData.isEmpty() && S0.Code == null && S0.ReferrerPanelTitle == "" &&
|
||
S0.ReferralQuestsSize == 0 && S0.HideReferrerCategory &&
|
||
S0.HideReferralCategory && S0.HidedAnnounce == null
|
||
)
|
||
val S1 = ReduceReferralEvent(S0, SetReferrer("rid", "ruid"))
|
||
Check(
|
||
"切片 ReferralEvent:SetReferrer 写前两字段",
|
||
S1.Referrer == "rid" && S1.ReferrerUid == "ruid" && S1.ReferralsCount == 0
|
||
)
|
||
val S2 = ReduceReferralEvent(S1, SetReferrals(5, mapOf<Any?, Any?>("a" to 1)))
|
||
Check(
|
||
"切片 ReferralEvent:SetReferrals 写人数与明细",
|
||
S2.ReferralsCount == 5 && S2.ReferralsData == mapOf<Any?, Any?>("a" to 1)
|
||
)
|
||
Check(
|
||
"切片 ReferralEvent:SetPromoCode 写邀请码",
|
||
ReduceReferralEvent(S2, SetPromoCode("CODE")).Code == "CODE"
|
||
)
|
||
val Data = ReferralEventUserModelCC(
|
||
hideReferralCategory = false, hideReferrerCategory = false, hidedAnnounce = 3L,
|
||
promoCodeDialogBackGround = "bg", promoCodeDialogImage = "img",
|
||
referralCategory = 7L, referralQuestsSize = 4, referrerCategory = 8L,
|
||
referrerPanelTitle = "title",
|
||
)
|
||
val S3 = ReduceReferralEvent(S2, SetReferralEventData(Data))
|
||
Check(
|
||
"切片 ReferralEvent:SetReferralEventData 写后 9 个字段",
|
||
S3.ReferrerPanelTitle == "title" && S3.PromoCodeDialogImage == "img" &&
|
||
S3.PromoCodeDialogBackGround == "bg" && S3.ReferrerCategoryId == 8L &&
|
||
S3.ReferralCategoryId == 7L && S3.ReferralQuestsSize == 4 &&
|
||
!S3.HideReferrerCategory && !S3.HideReferralCategory &&
|
||
S3.HidedAnnounce == 3L && S3.Referrer == "rid" && S3.ReferralsCount == 5
|
||
)
|
||
Check(
|
||
"切片 ReferralEvent:无关动作不改状态",
|
||
ReduceReferralEvent(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetPromoCode("X"))
|
||
Check(
|
||
"根:SetPromoCode 只动 referralEvent",
|
||
Root1.ReferralEvent.Code == "X" && Root1.SecuritySettings == Root0.SecuritySettings
|
||
)
|
||
val W = RootWiring.ByField("referralEvent")!!
|
||
Check(
|
||
"根:referralEvent 委派到 pQ / lh.osk 且 4 个动作",
|
||
W.SliceClass == "pQ" && W.Reducer == "lh.osk" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetReferrer", "SetReferrals", "SetPromoCode", "SetReferralEventData",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊹ 切片 reducer:TipsState(dst / Hw.vth,1 字段;mst → UpdateTips)
|
||
run {
|
||
val S0 = TipsStateSlice()
|
||
Check("切片 TipsState:默认空列表", S0.Tips.isEmpty())
|
||
val S1 = ReduceTips(S0, UpdateTips(listOf("a", "b")))
|
||
Check("切片 TipsState:UpdateTips 整体替换", S1.Tips == listOf<Any?>("a", "b"))
|
||
Check("切片 TipsState:无关动作不改状态", ReduceTips(S1, AddFrags(1)) == S1)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateTips(listOf("x")))
|
||
Check(
|
||
"根:UpdateTips 只动 tipsState",
|
||
Root1.TipsState.Tips == listOf<Any?>("x") && Root1.BattleResult == Root0.BattleResult
|
||
)
|
||
val W = RootWiring.ByField("tipsState")!!
|
||
Check(
|
||
"根:tipsState 委派到 dst / Hw.vth",
|
||
W.SliceClass == "dst" && W.Reducer == "Hw.vth" && W.Actions == listOf("UpdateTips")
|
||
)
|
||
}
|
||
|
||
// ㊺ 切片 reducer:RankUpDialog(LO / Se.br3,1 字段;UO + FO)
|
||
run {
|
||
val S0 = RankUpDialogStateSlice()
|
||
Check("切片 RankUpDialog:默认空表", S0.AvailableItems.isEmpty())
|
||
val S1 = ReduceRankUpDialog(S0, ShowAvailableItems(listOf("a")))
|
||
Check(
|
||
"切片 RankUpDialog:ShowAvailableItems 追加整表",
|
||
S1.AvailableItems == listOf<Any?>("a")
|
||
)
|
||
val S2 = ReduceRankUpDialog(S1, ShowAvailableItems(listOf("b", "c")))
|
||
Check(
|
||
"切片 RankUpDialog:再次追加是拼接",
|
||
S2.AvailableItems == listOf<Any?>("a", "b", "c")
|
||
)
|
||
Check(
|
||
"切片 RankUpDialog:ResetRankUpDialog 清空列表",
|
||
ReduceRankUpDialog(S2, ResetRankUpDialog).AvailableItems.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 RankUpDialog:无关动作不改状态",
|
||
ReduceRankUpDialog(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ShowAvailableItems(listOf("x")))
|
||
Check(
|
||
"根:ShowAvailableItems 只动 rankUpDialogState",
|
||
Root1.RankUpDialogState.AvailableItems == listOf<Any?>("x") &&
|
||
Root1.TipsState == Root0.TipsState
|
||
)
|
||
val W = RootWiring.ByField("rankUpDialogState")!!
|
||
Check(
|
||
"根:rankUpDialogState 委派到 LO / Se.br3 且 2 个动作",
|
||
W.SliceClass == "LO" && W.Reducer == "Se.br3" &&
|
||
W.Actions.toSet() == setOf("ShowAvailableItems", "ResetRankUpDialog")
|
||
)
|
||
}
|
||
|
||
// ㊻ 切片 reducer:BattleResult(sR / Fi.aqp,5 字段;6 个动作)
|
||
run {
|
||
val Item = ContainerGivenItem(
|
||
category = "cat", count = 1, garageItemCategory = "hull", garageItemId = 2,
|
||
image = null, name = "n", rewardType = "t",
|
||
)
|
||
val S0 = BattleResultStateSlice()
|
||
Check(
|
||
"切片 BattleResult:默认值完整",
|
||
!S0.HasBattleResult && S0.Rewards.isEmpty() &&
|
||
S0.InitialCurrentUserXP == null && S0.IncreasedReward == 0 &&
|
||
S0.SimpleReward == 0
|
||
)
|
||
val S1 = ReduceBattleResult(S0, SetRewards(listOf(Item)))
|
||
Check("切片 BattleResult:SetRewards 写奖励表", S1.Rewards == listOf(Item))
|
||
val S2 = ReduceBattleResult(S1, SetHasBattleResult(true))
|
||
Check("切片 BattleResult:SetHasBattleResult 写布尔", S2.HasBattleResult)
|
||
val S3 = ReduceBattleResult(
|
||
ReduceBattleResult(S2, SetIncreasedReward(15)),
|
||
SetSimpleReward(10),
|
||
)
|
||
Check(
|
||
"切片 BattleResult:cR / lR 分别写两个奖励数值",
|
||
S3.IncreasedReward == 15 && S3.SimpleReward == 10
|
||
)
|
||
Check(
|
||
"切片 BattleResult:ResetBattleRewards 只把两个数值归零",
|
||
ReduceBattleResult(S3, ResetBattleRewards) ==
|
||
S3.copy(IncreasedReward = 0, SimpleReward = 0)
|
||
)
|
||
val S4 = ReduceBattleResult(S3, DoInit(500))
|
||
Check(
|
||
"切片 BattleResult:DoInit 重建状态只带初始 XP",
|
||
!S4.HasBattleResult && S4.Rewards.isEmpty() &&
|
||
S4.InitialCurrentUserXP == 500 && S4.IncreasedReward == 0 &&
|
||
S4.SimpleReward == 0
|
||
)
|
||
Check(
|
||
"切片 BattleResult:无关动作不改状态",
|
||
ReduceBattleResult(S4, AddFrags(1)) == S4
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetHasBattleResult(true))
|
||
Check(
|
||
"根:SetHasBattleResult 只动 battleResult",
|
||
Root1.BattleResult.HasBattleResult && Root1.CaptchaState == Root0.CaptchaState
|
||
)
|
||
val W = RootWiring.ByField("battleResult")!!
|
||
Check(
|
||
"根:battleResult 委派到 sR / Fi.aqp 且 6 个动作",
|
||
W.SliceClass == "sR" && W.Reducer == "Fi.aqp" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetRewards", "SetHasBattleResult", "SetIncreasedReward",
|
||
"SetSimpleReward", "ResetBattleRewards", "DoInit",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊼ 切片 reducer:Captcha(jP / ar.iqt,4 字段;7 个动作)
|
||
run {
|
||
val S0 = CaptchaStateSlice()
|
||
Check(
|
||
"切片 Captcha:默认不造假、三个集合为空",
|
||
!S0.UseFake && S0.EnabledLocations.isEmpty() &&
|
||
S0.VerifiedLocations.isEmpty() && S0.LocationsToReset.isEmpty()
|
||
)
|
||
val S1 = ReduceCaptcha(S0, ShowFakeCaptcha)
|
||
Check("切片 Captcha:ShowFakeCaptcha 置 useFake", S1.UseFake)
|
||
val S2 = ReduceCaptcha(S1, SetLocations(listOf("a", "b")))
|
||
Check(
|
||
"切片 Captcha:SetLocations 收成集合",
|
||
S2.EnabledLocations == setOf<Any?>("a", "b")
|
||
)
|
||
val S3 = ReduceCaptcha(
|
||
ReduceCaptcha(S2, AddVerifiedCaptchaLocation("a")),
|
||
AddVerifiedCaptchaLocation("b"),
|
||
)
|
||
Check(
|
||
"切片 Captcha:已验证集合加元素",
|
||
S3.VerifiedLocations == setOf<Any?>("a", "b")
|
||
)
|
||
Check(
|
||
"切片 Captcha:已验证集合删元素",
|
||
ReduceCaptcha(S3, RemoveVerifiedCaptchaLocation("a"))
|
||
.VerifiedLocations == setOf<Any?>("b")
|
||
)
|
||
val S4 = ReduceCaptcha(
|
||
ReduceCaptcha(S3, AddCaptchaLocationToReset("c")),
|
||
RemoveCaptchaLocationToReset("c"),
|
||
)
|
||
Check("切片 Captcha:待重置集合加减后为空", S4.LocationsToReset.isEmpty())
|
||
Check(
|
||
"切片 Captcha:LocationsUpdated 追加到启用集合",
|
||
ReduceCaptcha(S4, LocationsUpdated("d"))
|
||
.EnabledLocations == setOf<Any?>("a", "b", "d")
|
||
)
|
||
Check("切片 Captcha:无关动作不改状态", ReduceCaptcha(S4, AddFrags(1)) == S4)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ShowFakeCaptcha)
|
||
Check(
|
||
"根:ShowFakeCaptcha 只动 captchaState",
|
||
Root1.CaptchaState.UseFake && Root1.ModalScreen == Root0.ModalScreen
|
||
)
|
||
val W = RootWiring.ByField("captchaState")!!
|
||
Check(
|
||
"根:captchaState 委派到 jP / ar.iqt 且 7 个动作",
|
||
W.SliceClass == "jP" && W.Reducer == "ar.iqt" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ShowFakeCaptcha", "SetLocations", "AddVerifiedCaptchaLocation",
|
||
"RemoveVerifiedCaptchaLocation", "AddCaptchaLocationToReset",
|
||
"RemoveCaptchaLocationToReset", "LocationsUpdated",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊽ 切片 reducer:ModalScreen(nV / Fs.js1,2 字段;4 个动作)
|
||
run {
|
||
val S0 = ModalScreenStateSlice()
|
||
Check(
|
||
"切片 ModalScreen:默认无当前窗口、队列为空",
|
||
S0.Current == null && S0.ModalQueue.isEmpty()
|
||
)
|
||
val S1 = ReduceModalScreen(S0, AddModal(true))
|
||
Check(
|
||
"切片 ModalScreen:AddModal 写 current 并入队",
|
||
S1.Current == true && S1.ModalQueue == listOf<Any?>(true)
|
||
)
|
||
Check(
|
||
"切片 ModalScreen:同名窗口不重复入队",
|
||
ReduceModalScreen(S1, AddModal(true)) == S1
|
||
)
|
||
val S2 = ReduceModalScreen(S1, AddModal(false))
|
||
Check(
|
||
"切片 ModalScreen:第二个窗口排到队尾",
|
||
S2.Current == false && S2.ModalQueue == listOf<Any?>(true, false)
|
||
)
|
||
val S3 = ReduceModalScreen(S2, RemoveModal(true))
|
||
Check(
|
||
"切片 ModalScreen:RemoveModal 出队并把 current 交给队首",
|
||
S3.Current == false && S3.ModalQueue == listOf<Any?>(false)
|
||
)
|
||
Check(
|
||
"切片 ModalScreen:CloseCurrentModal 清掉当前窗口",
|
||
ReduceModalScreen(S3, CloseCurrentModal) == ModalScreenStateSlice()
|
||
)
|
||
Check(
|
||
"切片 ModalScreen:RemoveCurrentByName 名称不符时不动",
|
||
ReduceModalScreen(S3, RemoveCurrentByName("other")) == S3
|
||
)
|
||
Check(
|
||
"切片 ModalScreen:RemoveCurrentByName 名称匹配时关闭",
|
||
ReduceModalScreen(S3, RemoveCurrentByName("false")) == ModalScreenStateSlice()
|
||
)
|
||
Check(
|
||
"切片 ModalScreen:无关动作不改状态",
|
||
ReduceModalScreen(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddModal(true))
|
||
Check(
|
||
"根:AddModal 只动 modalScreen",
|
||
Root1.ModalScreen.Current == true &&
|
||
Root1.EventGoods == Root0.EventGoods
|
||
)
|
||
val W = RootWiring.ByField("modalScreen")!!
|
||
Check(
|
||
"根:modalScreen 委派到 nV / Fs.js1 且 4 个动作",
|
||
W.SliceClass == "nV" && W.Reducer == "Fs.js1" &&
|
||
W.Actions.toSet() == setOf(
|
||
"AddModal", "RemoveModal", "CloseCurrentModal", "RemoveCurrentByName",
|
||
)
|
||
)
|
||
}
|
||
|
||
// ㊾ 切片 reducer:DroppableGold(Vet / Wp.kth,1 字段;Ket/Yet 两个单例)
|
||
run {
|
||
val S0 = DroppableGoldStateSlice()
|
||
Check("切片 DroppableGold:默认显示作者署名", S0.ShowDroppableGoldAuthor)
|
||
Check(
|
||
"切片 DroppableGold:Hide 置 false、Show 置 true",
|
||
!ReduceDroppableGold(S0, HideDroppableGoldAuthor).ShowDroppableGoldAuthor &&
|
||
ReduceDroppableGold(S0, ShowDroppableGoldAuthor).ShowDroppableGoldAuthor
|
||
)
|
||
Check(
|
||
"切片 DroppableGold:无关动作不改状态",
|
||
ReduceDroppableGold(S0, AddFrags(1)) == S0
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, HideDroppableGoldAuthor)
|
||
Check(
|
||
"根:HideDroppableGoldAuthor 只动 droppableGold",
|
||
!Root1.DroppableGold.ShowDroppableGoldAuthor &&
|
||
Root1.CreatorTagState == Root0.CreatorTagState
|
||
)
|
||
val W = RootWiring.ByField("droppableGold")!!
|
||
Check(
|
||
"根:droppableGold 委派到 Vet / Wp.kth 且 2 个动作",
|
||
W.SliceClass == "Vet" && W.Reducer == "Wp.kth" &&
|
||
W.Actions.toSet() == setOf("ShowDroppableGoldAuthor", "HideDroppableGoldAuthor")
|
||
)
|
||
}
|
||
|
||
// ㊿ 切片 reducer:EventGoods(W7 / Wc.esx,1 字段;V7 → InitEventGoods)
|
||
run {
|
||
val S0 = EventGoodsStateSlice()
|
||
Check("切片 EventGoods:默认空表", S0.EventGoods.isEmpty())
|
||
val S1 = ReduceEventGoods(S0, InitEventGoods("spectatorMode", true))
|
||
Check(
|
||
"切片 EventGoods:InitEventGoods 写键值",
|
||
S1.EventGoods == mapOf<Any?, Any?>("spectatorMode" to true)
|
||
)
|
||
val S2 = ReduceEventGoods(S1, InitEventGoods("spectatorMode", null))
|
||
Check("切片 EventGoods:goods 为空时删键", S2.EventGoods.isEmpty())
|
||
Check(
|
||
"切片 EventGoods:无关动作不改状态",
|
||
ReduceEventGoods(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, InitEventGoods("k", 1))
|
||
Check(
|
||
"根:InitEventGoods 只动 eventGoods",
|
||
Root1.EventGoods.EventGoods == mapOf<Any?, Any?>("k" to 1) &&
|
||
Root1.RewardedVideosState == Root0.RewardedVideosState
|
||
)
|
||
val W = RootWiring.ByField("eventGoods")!!
|
||
Check(
|
||
"根:eventGoods 委派到 W7 / Wc.esx",
|
||
W.SliceClass == "W7" && W.Reducer == "Wc.esx" &&
|
||
W.Actions == listOf("InitEventGoods")
|
||
)
|
||
}
|
||
|
||
// 51 切片 reducer:CreatorTag(qO / ze.er2,2 字段;4 个动作)
|
||
run {
|
||
val S0 = CreatorTagStateSlice()
|
||
Check("切片 CreatorTag:默认两个字段都为 null", S0.CreatorTag == null && S0.CreatorTags == null)
|
||
val S1 = ReduceCreatorTag(S0, SetCreatorTags(listOf("a")))
|
||
Check("切片 CreatorTag:SetCreatorTags 写列表", S1.CreatorTags == listOf<Any?>("a"))
|
||
Check(
|
||
"切片 CreatorTag:SetCreatorTag 写当前标签",
|
||
ReduceCreatorTag(S1, SetCreatorTag("t1")).CreatorTag == "t1"
|
||
)
|
||
val S2 = ReduceCreatorTag(S1, SetAttachedCreatorTag("t2"))
|
||
Check(
|
||
"切片 CreatorTag:SetAttachedCreatorTag 覆盖当前标签",
|
||
S2.CreatorTag == "t2" && S2.CreatorTags == listOf<Any?>("a")
|
||
)
|
||
val S3 = ReduceCreatorTag(S2, ClearCreatorTag)
|
||
Check(
|
||
"切片 CreatorTag:ClearCreatorTag 只清当前标签",
|
||
S3.CreatorTag == null && S3.CreatorTags == listOf<Any?>("a")
|
||
)
|
||
Check("切片 CreatorTag:无关动作不改状态", ReduceCreatorTag(S3, AddFrags(1)) == S3)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetCreatorTag("x"))
|
||
Check(
|
||
"根:SetCreatorTag 只动 creatorTagState",
|
||
Root1.CreatorTagState.CreatorTag == "x" && Root1.EventGoods == Root0.EventGoods
|
||
)
|
||
val W = RootWiring.ByField("creatorTagState")!!
|
||
Check(
|
||
"根:creatorTagState 委派到 qO / ze.er2 且 4 个动作",
|
||
W.SliceClass == "qO" && W.Reducer == "ze.er2" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetCreatorTags", "SetCreatorTag", "SetAttachedCreatorTag", "ClearCreatorTag",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 52 切片 reducer:RewardedVideos(JQ / xh.zsl,1 字段;t1 → SetDisplayState)
|
||
run {
|
||
val S0 = RewardedVideosStateSlice()
|
||
Check("切片 RewardedVideos:默认 NONE", S0.DisplayState == RewardVideoDisplayState.NONE)
|
||
Check(
|
||
"切片 RewardedVideos:SetDisplayState 写枚举",
|
||
ReduceRewardedVideos(S0, SetDisplayState(RewardVideoDisplayState.DISPLAY))
|
||
.DisplayState == RewardVideoDisplayState.DISPLAY
|
||
)
|
||
Check(
|
||
"切片 RewardedVideos:无关动作不改状态",
|
||
ReduceRewardedVideos(S0, AddFrags(1)) == S0
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetDisplayState(RewardVideoDisplayState.LOADING))
|
||
Check(
|
||
"根:SetDisplayState 只动 rewardedVideosState",
|
||
Root1.RewardedVideosState.DisplayState == RewardVideoDisplayState.LOADING &&
|
||
Root1.PasswordChangeState == Root0.PasswordChangeState
|
||
)
|
||
val W = RootWiring.ByField("rewardedVideosState")!!
|
||
Check(
|
||
"根:rewardedVideosState 委派到 JQ / xh.zsl",
|
||
W.SliceClass == "JQ" && W.Reducer == "xh.zsl" &&
|
||
W.Actions == listOf("SetDisplayState")
|
||
)
|
||
}
|
||
|
||
// 53 切片 reducer:PasswordChange(VD / uu.pr6,3 字段;UD + HD)
|
||
run {
|
||
val S0 = PasswordChangeStateSlice()
|
||
Check(
|
||
"切片 PasswordChange:默认三个空串",
|
||
S0.Hash == "" && S0.Uid == "" && S0.Email == ""
|
||
)
|
||
val S1 = ReducePasswordChange(S0, SetPasswordChangeHash("h"))
|
||
Check(
|
||
"切片 PasswordChange:SetPasswordChangeHash 只写 hash",
|
||
S1.Hash == "h" && S1.Uid == "" && S1.Email == ""
|
||
)
|
||
val S2 = ReducePasswordChange(S1, ShowEmailAndPasswordChangeDialog("e", "u", "r"))
|
||
Check(
|
||
"切片 PasswordChange:ShowEmailAndPasswordChangeDialog 写 uid/email 但保留 hash",
|
||
S2.Hash == "h" && S2.Uid == "u" && S2.Email == "e"
|
||
)
|
||
Check(
|
||
"切片 PasswordChange:无关动作不改状态",
|
||
ReducePasswordChange(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetPasswordChangeHash("x"))
|
||
Check(
|
||
"根:SetPasswordChangeHash 只动 passwordChangeState",
|
||
Root1.PasswordChangeState.Hash == "x" && Root1.TipsState == Root0.TipsState
|
||
)
|
||
val W = RootWiring.ByField("passwordChangeState")!!
|
||
Check(
|
||
"根:passwordChangeState 委派到 VD / uu.pr6 且 2 个动作",
|
||
W.SliceClass == "VD" && W.Reducer == "uu.pr6" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetPasswordChangeHash", "ShowEmailAndPasswordChangeDialog",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 54 切片 reducer:System(_9 / w9().xt9,3 字段;2 个动作)
|
||
run {
|
||
val S0 = SystemStateSlice()
|
||
Check(
|
||
"切片 System:默认值完整",
|
||
S0.Message == null && !S0.Restart && S0.FatalError == null
|
||
)
|
||
val S1 = ReduceSystem(S0, DoShowMessage("m", true))
|
||
Check(
|
||
"切片 System:DoShowMessage 写 message/restart",
|
||
S1.Message == "m" && S1.Restart && S1.FatalError == null
|
||
)
|
||
val S2 = ReduceSystem(S1, SetFatalError("boom"))
|
||
Check(
|
||
"切片 System:SetFatalError 写 fatalError",
|
||
S2.FatalError == "boom" && S2.Message == "m"
|
||
)
|
||
Check("切片 System:无关动作不改状态", ReduceSystem(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetFatalError("x"))
|
||
Check(
|
||
"根:SetFatalError 只动 system",
|
||
Root1.System.FatalError == "x" && Root1.ContextTutorial == Root0.ContextTutorial
|
||
)
|
||
val W = RootWiring.ByField("system")!!
|
||
Check(
|
||
"根:system 委派到 _9 / w9().xt9",
|
||
W.SliceClass == "_9" && W.Reducer == "w9().xt9" &&
|
||
W.Actions.toSet() == setOf("DoShowMessage", "SetFatalError")
|
||
)
|
||
}
|
||
|
||
// 55 切片 reducer:ContextTutorial(Mst / Jst().tth,4 字段;5 个动作)
|
||
run {
|
||
val S0 = ContextTutorialStateSlice()
|
||
Check(
|
||
"切片 ContextTutorial:默认空表空 Map",
|
||
S0.CurrentElement == null && S0.LoadedElements.isEmpty() &&
|
||
S0.UnViewedElements.isEmpty() && S0.MissionToElement.isEmpty()
|
||
)
|
||
val S1 = ReduceContextTutorial(S0, LoadCurrentElement("e1"))
|
||
Check("切片 ContextTutorial:LoadCurrentElement 写当前元素", S1.CurrentElement == "e1")
|
||
val S2 = ReduceContextTutorial(
|
||
ReduceContextTutorial(S1, SetUnViewedElements(listOf("a", "b"))),
|
||
MarkElementAsViewed("a"),
|
||
)
|
||
Check(
|
||
"切片 ContextTutorial:MarkElementAsViewed 从待看列表删一个",
|
||
S2.UnViewedElements == listOf<Any?>("b")
|
||
)
|
||
val S3 = ReduceContextTutorial(
|
||
ReduceContextTutorial(S2, SetLoadedElements(mapOf<Any?, Any?>("k" to 1))),
|
||
SetMissionToElement(mapOf("m" to "e")),
|
||
)
|
||
Check(
|
||
"切片 ContextTutorial:loadedElements / missionToElement 整体替换",
|
||
S3.LoadedElements == mapOf<Any?, Any?>("k" to 1) &&
|
||
S3.MissionToElement == mapOf("m" to "e")
|
||
)
|
||
Check(
|
||
"切片 ContextTutorial:无关动作不改状态",
|
||
ReduceContextTutorial(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, LoadCurrentElement("z"))
|
||
Check(
|
||
"根:LoadCurrentElement 只动 contextTutorial",
|
||
Root1.ContextTutorial.CurrentElement == "z" && Root1.System == Root0.System
|
||
)
|
||
val W = RootWiring.ByField("contextTutorial")!!
|
||
Check(
|
||
"根:contextTutorial 委派到 Mst / Jst().tth 且 5 个动作",
|
||
W.SliceClass == "Mst" && W.Reducer == "Jst().tth" &&
|
||
W.Actions.toSet() == setOf(
|
||
"LoadCurrentElement", "SetUnViewedElements", "MarkElementAsViewed",
|
||
"SetLoadedElements", "SetMissionToElement",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 56 切片 reducer:BattleTutorial(rE / Q.sq8,3 字段;4 个动作)
|
||
run {
|
||
val S0 = BattleTutorialStateSlice()
|
||
Check(
|
||
"切片 BattleTutorial:默认值完整",
|
||
S0.Message == null && S0.FragsCount == 0 && !S0.MovementHasStarted
|
||
)
|
||
val S1 = ReduceBattleTutorial(S0, ShowMessage("m"))
|
||
Check("切片 BattleTutorial:ShowMessage 写 message", S1.Message == "m")
|
||
Check(
|
||
"切片 BattleTutorial:SetFragsCount 写击杀数",
|
||
ReduceBattleTutorial(S1, SetFragsCount(3)).FragsCount == 3
|
||
)
|
||
val S2 = ReduceBattleTutorial(S1, MarkMovementStarted)
|
||
Check("切片 BattleTutorial:MarkMovementStarted 置位", S2.MovementHasStarted)
|
||
Check(
|
||
"切片 BattleTutorial:ResetBattleTutorial 整片复位",
|
||
ReduceBattleTutorial(S2, ResetBattleTutorial) == BattleTutorialStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleTutorial:无关动作不改状态",
|
||
ReduceBattleTutorial(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, MarkMovementStarted)
|
||
Check(
|
||
"根:MarkMovementStarted 只动 battleTutorial",
|
||
Root1.BattleTutorial.MovementHasStarted && Root1.Spectator == Root0.Spectator
|
||
)
|
||
val W = RootWiring.ByField("battleTutorial")!!
|
||
Check(
|
||
"根:battleTutorial 委派到 rE / Q.sq8 且 4 个动作",
|
||
W.SliceClass == "rE" && W.Reducer == "Q.sq8" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ShowMessage", "SetFragsCount", "MarkMovementStarted", "ResetBattleTutorial",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 57 切片 reducer:Spectator(HA / Li.jqm,4 字段;6 个动作)
|
||
run {
|
||
val S0 = SpectatorStateSlice()
|
||
Check(
|
||
"切片 Spectator:默认非观战、列表可见",
|
||
!S0.IsSpectator && S0.Spectators.isEmpty() && S0.SpectatorsListVisible
|
||
)
|
||
val S1 = ReduceSpectator(S0, SetSpectator(true))
|
||
Check("切片 Spectator:SetSpectator 写布尔", S1.IsSpectator)
|
||
val S2 = ReduceSpectator(S1, AddSpectator(1L, "uid1", true, false))
|
||
Check(
|
||
"切片 Spectator:AddSpectator 追加 SpectatorInfo",
|
||
S2.Spectators.size == 1 &&
|
||
(S2.Spectators[0] as SpectatorInfo).userId == 1L
|
||
)
|
||
val S3 = ReduceSpectator(S2, AddSpectator(2L, "uid2", false, false))
|
||
Check("切片 Spectator:第二个观战者入列", S3.Spectators.size == 2)
|
||
Check(
|
||
"切片 Spectator:RemoveSpectator 按 userId 删",
|
||
ReduceSpectator(S3, RemoveSpectator(1L)).Spectators.size == 1
|
||
)
|
||
Check(
|
||
"切片 Spectator:SetSpectators 整表替换",
|
||
ReduceSpectator(S3, SetSpectators(listOf("x"))).Spectators == listOf<Any?>("x")
|
||
)
|
||
Check(
|
||
"切片 Spectator:SetSpectatorsListVisible 写可见性",
|
||
!ReduceSpectator(S3, SetSpectatorsListVisible(false)).SpectatorsListVisible
|
||
)
|
||
Check(
|
||
"切片 Spectator:UpdateHudLayout 写布局",
|
||
ReduceSpectator(S3, UpdateHudLayout("hud")).SpectatorHudLayout == "hud"
|
||
)
|
||
Check("切片 Spectator:无关动作不改状态", ReduceSpectator(S3, AddFrags(1)) == S3)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetSpectator(true))
|
||
Check(
|
||
"根:SetSpectator 只动 spectator",
|
||
Root1.Spectator.IsSpectator && Root1.BattleTutorial == Root0.BattleTutorial
|
||
)
|
||
val W = RootWiring.ByField("spectator")!!
|
||
Check(
|
||
"根:spectator 委派到 HA / Li.jqm 且 6 个动作",
|
||
W.SliceClass == "HA" && W.Reducer == "Li.jqm" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetSpectator", "UpdateHudLayout", "SetSpectators", "AddSpectator",
|
||
"RemoveSpectator", "SetSpectatorsListVisible",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 58 切片 reducer:TwoStepVerification(x9 / _f.atb,6 字段;5 个动作)
|
||
run {
|
||
val S0 = TwoStepVerificationStateSlice()
|
||
Check(
|
||
"切片 TwoStepVerification:默认值完整",
|
||
!S0.IsUse2FA && !S0.IsActivated2FA && S0.QrCodeDataURI == null &&
|
||
S0.SharedSecret == null && S0.VerificationStatus == null &&
|
||
!S0.Enable2FaRequest
|
||
)
|
||
val S1 = ReduceTwoStepVerification(S0, SetUse2FA(true))
|
||
Check("切片 TwoStepVerification:SetUse2FA 写开关", S1.IsUse2FA)
|
||
val S2 = ReduceTwoStepVerification(S1, SetSecretAndQRCode("secret", "data"))
|
||
Check(
|
||
"切片 TwoStepVerification:SetSecretAndQRCode 同时写两字段",
|
||
S2.SharedSecret == "secret" && S2.QrCodeDataURI == "data"
|
||
)
|
||
Check(
|
||
"切片 TwoStepVerification:VerificationStatus(false) 写失败",
|
||
ReduceTwoStepVerification(S2, VerificationStatus(false)).VerificationStatus == false
|
||
)
|
||
Check(
|
||
"切片 TwoStepVerification:VerificationStatus(true) 视为已启用",
|
||
ReduceTwoStepVerification(S2, VerificationStatus(true)).IsActivated2FA
|
||
)
|
||
Check(
|
||
"切片 TwoStepVerification:VerificationStatus(null) 清空状态",
|
||
ReduceTwoStepVerification(
|
||
ReduceTwoStepVerification(S2, VerificationStatus(false)),
|
||
VerificationStatus(null),
|
||
).VerificationStatus == null
|
||
)
|
||
Check(
|
||
"切片 TwoStepVerification:ActivationStatus2FA / Enable2FaRequest",
|
||
!ReduceTwoStepVerification(S2, ActivationStatus2FA(false)).IsActivated2FA &&
|
||
ReduceTwoStepVerification(S2, Enable2FaRequest(true)).Enable2FaRequest
|
||
)
|
||
Check(
|
||
"切片 TwoStepVerification:无关动作不改状态",
|
||
ReduceTwoStepVerification(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetUse2FA(true))
|
||
Check(
|
||
"根:SetUse2FA 只动 twoStepVerificationState",
|
||
Root1.TwoStepVerificationState.IsUse2FA && Root1.Spectator == Root0.Spectator
|
||
)
|
||
val W = RootWiring.ByField("twoStepVerificationState")!!
|
||
Check(
|
||
"根:twoStepVerificationState 委派到 x9 / _f.atb 且 5 个动作",
|
||
W.SliceClass == "x9" && W.Reducer == "_f.atb" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetUse2FA", "ActivationStatus2FA", "SetSecretAndQRCode",
|
||
"VerificationStatus", "Enable2FaRequest",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 59 切片 reducer:AntiAddiction(UC / W.iq8,8 字段;5 个动作)
|
||
run {
|
||
val S0 = AntiAddictionStateSlice()
|
||
Check(
|
||
"切片 AntiAddiction:默认值完整",
|
||
!S0.IsEnableRealNameCheck && !S0.UnknownUserId && S0.UrlForEnterUserId == null &&
|
||
S0.PlayingTimeInMinutes == 0 && S0.AlertMessage == "" &&
|
||
S0.IdNumber == "" && S0.RealName == "" && S0.ValidateStatus == null
|
||
)
|
||
val S1 = ReduceAntiAddiction(S0, EnableRealNameCheck)
|
||
Check("切片 AntiAddiction:EnableRealNameCheck 置位", S1.IsEnableRealNameCheck)
|
||
val S2 = ReduceAntiAddiction(S1, RequireUserIdVerification("https://u"))
|
||
Check(
|
||
"切片 AntiAddiction:RequireUserIdVerification 写 unknown+url",
|
||
S2.UnknownUserId && S2.UrlForEnterUserId == "https://u"
|
||
)
|
||
val S3 = ReduceAntiAddiction(S2, UpdatePlayingTime(180, "休息"))
|
||
Check(
|
||
"切片 AntiAddiction:UpdatePlayingTime 写时长与提示",
|
||
S3.PlayingTimeInMinutes == 180 && S3.AlertMessage == "休息"
|
||
)
|
||
val S4 = ReduceAntiAddiction(S3, SetRealNameAndIdNumber("张三", "110"))
|
||
Check(
|
||
"切片 AntiAddiction:SetRealNameAndIdNumber 写实名与证件号",
|
||
S4.RealName == "张三" && S4.IdNumber == "110"
|
||
)
|
||
Check(
|
||
"切片 AntiAddiction:实名参数为 null 时保留旧值",
|
||
ReduceAntiAddiction(S4, SetRealNameAndIdNumber(null, null)) == S4
|
||
)
|
||
Check(
|
||
"切片 AntiAddiction:SetValidateAntiAddictionUserDataStatus 写状态",
|
||
ReduceAntiAddiction(S4, SetValidateAntiAddictionUserDataStatus("ok"))
|
||
.ValidateStatus == "ok"
|
||
)
|
||
Check("切片 AntiAddiction:无关动作不改状态", ReduceAntiAddiction(S4, AddFrags(1)) == S4)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, EnableRealNameCheck)
|
||
Check(
|
||
"根:EnableRealNameCheck 只动 antiAddiction",
|
||
Root1.AntiAddiction.IsEnableRealNameCheck && Root1.Moderation == Root0.Moderation
|
||
)
|
||
val W = RootWiring.ByField("antiAddiction")!!
|
||
Check(
|
||
"根:antiAddiction 委派到 UC / W.iq8 且 5 个动作",
|
||
W.SliceClass == "UC" && W.Reducer == "W.iq8" &&
|
||
W.Actions.toSet() == setOf(
|
||
"EnableRealNameCheck", "RequireUserIdVerification", "UpdatePlayingTime",
|
||
"SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 60 切片 reducer:Moderation(NI / Ei.oqh,11 字段;13 个动作)
|
||
run {
|
||
val S0 = ModerationStateSlice()
|
||
Check(
|
||
"切片 Moderation:默认值完整",
|
||
!S0.ModeratorMode && !S0.BattleModeratorMode && !S0.EventOrganizerMode &&
|
||
S0.ModerationToolBattleUrlTemplate == "" &&
|
||
S0.ModerationToolUserUrlTemplate == "" && S0.BattleInfo == null &&
|
||
!S0.ForceShowBattleInfo && S0.SystemMessage == "" &&
|
||
!S0.ForceShowSystemMessage && !S0.ForceShowBlockUserMenu &&
|
||
S0.SelectedUsers.isEmpty()
|
||
)
|
||
val S1 = ReduceModeration(S0, SetModeratorModes(true, false, true))
|
||
Check(
|
||
"切片 Moderation:SetModeratorModes 写三个开关",
|
||
S1.ModeratorMode && !S1.BattleModeratorMode && S1.EventOrganizerMode
|
||
)
|
||
Check(
|
||
"切片 Moderation:SetModerationUrlTemplates 写两个模板",
|
||
ReduceModeration(S1, SetModerationUrlTemplates("b", "u"))
|
||
.ModerationToolBattleUrlTemplate == "b"
|
||
)
|
||
val S2 = ReduceModeration(S1, ShowBattleInfo("info"))
|
||
Check(
|
||
"切片 Moderation:ShowBattleInfo 写面板并强制显示",
|
||
S2.BattleInfo == "info" && S2.ForceShowBattleInfo
|
||
)
|
||
Check(
|
||
"切片 Moderation:HideBattleInfo 取消强制显示",
|
||
!ReduceModeration(S2, HideBattleInfo).ForceShowBattleInfo
|
||
)
|
||
val S3 = ReduceModeration(
|
||
ReduceModeration(S2, AddSelectedUser(1L, 5, "a")),
|
||
AddSelectedUserFromChat(1L, 9, "a2"),
|
||
)
|
||
Check(
|
||
"切片 Moderation:选中用户按 userId 去重后追加",
|
||
S3.SelectedUsers.size == 1 &&
|
||
(S3.SelectedUsers[0] as ModerationUserRecord).userRank == 9
|
||
)
|
||
Check(
|
||
"切片 Moderation:RemoveSelectedUser 按 userId 删",
|
||
ReduceModeration(
|
||
ReduceModeration(S3, AddSelectedUser(2L, 1, "b")),
|
||
RemoveSelectedUser(1L),
|
||
).SelectedUsers.size == 1
|
||
)
|
||
Check(
|
||
"切片 Moderation:ClearSelectedUsers / ResetSelectedUsers 清空",
|
||
ReduceModeration(S3, ClearSelectedUsers).SelectedUsers.isEmpty() &&
|
||
ReduceModeration(S3, ResetSelectedUsers).SelectedUsers.isEmpty()
|
||
)
|
||
val S4 = ReduceModeration(S3, ShowModerationSystemMessage("hello"))
|
||
Check(
|
||
"切片 Moderation:ShowModerationSystemMessage 写消息并强制显示",
|
||
S4.SystemMessage == "hello" && S4.ForceShowSystemMessage
|
||
)
|
||
Check(
|
||
"切片 Moderation:HideModerationSystemMessage 取消强制显示",
|
||
!ReduceModeration(S4, HideModerationSystemMessage).ForceShowSystemMessage
|
||
)
|
||
Check(
|
||
"切片 Moderation:ShowBlockUserMenu / HideBlockUserMenu 切换",
|
||
ReduceModeration(S4, ShowBlockUserMenu).ForceShowBlockUserMenu &&
|
||
!ReduceModeration(S4, HideBlockUserMenu).ForceShowBlockUserMenu
|
||
)
|
||
Check("切片 Moderation:无关动作不改状态", ReduceModeration(S4, AddFrags(1)) == S4)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ShowBlockUserMenu)
|
||
Check(
|
||
"根:ShowBlockUserMenu 只动 moderation",
|
||
Root1.Moderation.ForceShowBlockUserMenu && Root1.AntiAddiction == Root0.AntiAddiction
|
||
)
|
||
val W = RootWiring.ByField("moderation")!!
|
||
Check(
|
||
"根:moderation 委派到 NI / Ei.oqh 且 13 个动作",
|
||
W.SliceClass == "NI" && W.Reducer == "Ei.oqh" && W.Actions.size == 13
|
||
)
|
||
}
|
||
|
||
// 61 切片 reducer:MigrationState($it / Uv.rtg,7 字段;11 个动作)
|
||
run {
|
||
val S0 = MigrationStateSlice()
|
||
Check(
|
||
"切片 MigrationState:默认值完整",
|
||
!S0.RequestLogin && S0.Login == "" && S0.ForceLogin == null &&
|
||
S0.Password == "" &&
|
||
S0.LoginStatus == MigrationLoginStatus.UNKNOWN &&
|
||
S0.RegisterStatus == MigrationRegisterStatus.UNKNOWN &&
|
||
S0.AdvisedUids.isEmpty()
|
||
)
|
||
val S1 = ReduceMigrationState(S0, SetUid("alice"))
|
||
Check(
|
||
"切片 MigrationState:SetUid 写 login 并复位状态",
|
||
S1.Login == "alice" &&
|
||
S1.LoginStatus == MigrationLoginStatus.UNKNOWN &&
|
||
S1.RegisterStatus == MigrationRegisterStatus.UNKNOWN
|
||
)
|
||
Check(
|
||
"切片 MigrationState:ForceSetUid 写 forceLogin",
|
||
ReduceMigrationState(S1, ForceSetUid("forced")).ForceLogin == "forced"
|
||
)
|
||
Check(
|
||
"切片 MigrationState:SetPassword 写 password",
|
||
ReduceMigrationState(S1, SetPassword("pwd")).Password == "pwd"
|
||
)
|
||
Check(
|
||
"切片 MigrationState:UidIsFree/UidIsIncorrect/UidTooShort 写 loginStatus",
|
||
ReduceMigrationState(S1, UidIsFree).LoginStatus == MigrationLoginStatus.FREE &&
|
||
ReduceMigrationState(S1, UidIsIncorrect).LoginStatus ==
|
||
MigrationLoginStatus.INCORRECT &&
|
||
ReduceMigrationState(S1, UidTooShort).LoginStatus ==
|
||
MigrationLoginStatus.SHORT_NAME
|
||
)
|
||
val S2 = ReduceMigrationState(S1, EnteredUidIsBusy(listOf("u1")))
|
||
Check(
|
||
"切片 MigrationState:EnteredUidIsBusy 写 BUSY 与 advisedUids",
|
||
S2.LoginStatus == MigrationLoginStatus.BUSY && S2.AdvisedUids == listOf<Any?>("u1")
|
||
)
|
||
Check(
|
||
"切片 MigrationState:注册状态两支",
|
||
ReduceMigrationState(S1, RegisterPasswordIncorrect).RegisterStatus ==
|
||
MigrationRegisterStatus.PASSWORD_INCORRECT &&
|
||
ReduceMigrationState(S1, RegisterFailed).RegisterStatus ==
|
||
MigrationRegisterStatus.FAILED
|
||
)
|
||
Check(
|
||
"切片 MigrationState:ResetUidStatus / ResetAuthenticationForm",
|
||
ReduceMigrationState(S2, ResetUidStatus).LoginStatus ==
|
||
MigrationLoginStatus.UNKNOWN &&
|
||
ReduceMigrationState(S2, ResetAuthenticationForm).Login == ""
|
||
)
|
||
Check(
|
||
"切片 MigrationState:无关动作不改状态",
|
||
ReduceMigrationState(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetUid("bob"))
|
||
Check(
|
||
"根:SetUid 同时动 migrationState 与 register",
|
||
Root1.MigrationState.Login == "bob" && Root1.Register.Login == "bob" &&
|
||
Root1.System == Root0.System
|
||
)
|
||
val W = RootWiring.ByField("migrationState")!!
|
||
Check(
|
||
"根:migrationState 委派到 \$it / Uv.rtg 且 11 个动作",
|
||
W.SliceClass == "\$it" && W.Reducer == "Uv.rtg" && W.Actions.size == 11
|
||
)
|
||
}
|
||
|
||
// 62 切片 reducer:Register(Cit / cp.stg,16 字段;19 个动作)
|
||
run {
|
||
val S0 = RegisterStateSlice()
|
||
Check(
|
||
"切片 Register:默认值完整",
|
||
!S0.IsDisabled && S0.Login == "" && S0.ForceLogin == null && S0.Password == "" &&
|
||
S0.UidValidationStatus == UidStatus.UNKNOWN &&
|
||
S0.RegisterStatus == RegistrationStatus.UNKNOWN &&
|
||
S0.AdvisedUids.isEmpty() && S0.SocialNetworkAvailability.isEmpty() &&
|
||
S0.SocialNetworkClientIds == emptyMap<String, Any?>() &&
|
||
S0.SocialNetworkOAuthData == null && S0.MinPasswordLength == 6 &&
|
||
S0.MaxPasswordLength == 20 && !S0.PasswordNeedLower &&
|
||
!S0.PasswordNeedUpper && !S0.PasswordNeedDigit && S0.OpenedPassword
|
||
)
|
||
val S1 = ReduceRegister(S0, DisableRegister)
|
||
Check("切片 Register:DisableRegister 置灰", S1.IsDisabled)
|
||
val S2 = ReduceRegister(S1, SetUid("alice"))
|
||
Check(
|
||
"切片 Register:SetUid 写 login 并复位 uid 状态",
|
||
S2.Login == "alice" && S2.UidValidationStatus == UidStatus.UNKNOWN
|
||
)
|
||
Check(
|
||
"切片 Register:原状态为 INCORRECT 时保留 registerStatus,否则复位",
|
||
ReduceRegister(
|
||
ReduceRegister(
|
||
ReduceRegister(S2, RegisterFailed),
|
||
UidIsIncorrect,
|
||
),
|
||
SetUid("alice2"),
|
||
).RegisterStatus == RegistrationStatus.FAILED &&
|
||
ReduceRegister(
|
||
ReduceRegister(
|
||
ReduceRegister(S2, RegisterFailed),
|
||
ResetUidStatus,
|
||
),
|
||
SetUid("alice2"),
|
||
).RegisterStatus == RegistrationStatus.UNKNOWN
|
||
)
|
||
val S3 = ReduceRegister(S2, EnteredUidIsBusy(listOf("u1")))
|
||
Check(
|
||
"切片 Register:EnteredUidIsBusy 写 BUSY + advisedUids",
|
||
S3.UidValidationStatus == UidStatus.BUSY && S3.AdvisedUids == listOf<Any?>("u1")
|
||
)
|
||
Check(
|
||
"切片 Register:uid 三个状态臂",
|
||
ReduceRegister(S2, UidIsFree).UidValidationStatus == UidStatus.FREE &&
|
||
ReduceRegister(S2, UidIsIncorrect).UidValidationStatus == UidStatus.INCORRECT &&
|
||
ReduceRegister(S2, UidTooShort).UidValidationStatus == UidStatus.SHORT_NAME
|
||
)
|
||
Check(
|
||
"切片 Register:注册状态四臂",
|
||
ReduceRegister(S2, RegisterPasswordCorrect).RegisterStatus ==
|
||
RegistrationStatus.PASSWORD_CORRECT &&
|
||
ReduceRegister(S2, RegisterPasswordIncorrect).RegisterStatus ==
|
||
RegistrationStatus.PASSWORD_INCORRECT &&
|
||
ReduceRegister(S2, RegisterFailed).RegisterStatus == RegistrationStatus.FAILED &&
|
||
ReduceRegister(S2, ResetRegisterStatus).RegisterStatus ==
|
||
RegistrationStatus.UNKNOWN
|
||
)
|
||
val S4 = ReduceRegister(
|
||
ReduceRegister(S2, SetPasswordRestrictions(8, 32, true, false, true)),
|
||
SetOpenedPassword(false),
|
||
)
|
||
Check(
|
||
"切片 Register:密码规则与展开开关",
|
||
S4.MinPasswordLength == 8 && S4.MaxPasswordLength == 32 &&
|
||
S4.PasswordNeedLower && !S4.PasswordNeedUpper && S4.PasswordNeedDigit &&
|
||
!S4.OpenedPassword
|
||
)
|
||
Check(
|
||
"切片 Register:社媒三支",
|
||
ReduceRegister(S2, SocialNetworkAvailability(mapOf("vk" to true)))
|
||
.SocialNetworkAvailability == mapOf("vk" to true) &&
|
||
ReduceRegister(S2, SocialNetworkClientIds("cid"))
|
||
.SocialNetworkClientIds == "cid" &&
|
||
ReduceRegister(S2, ReceiveOAuthUri("oauth"))
|
||
.SocialNetworkOAuthData == "oauth"
|
||
)
|
||
run {
|
||
val R = ReduceRegister(S3, ResetAuthenticationForm)
|
||
Check(
|
||
"切片 Register:ResetAuthenticationForm 清 login 与两个状态",
|
||
R.Login == "" && R.UidValidationStatus == UidStatus.UNKNOWN &&
|
||
R.RegisterStatus == RegistrationStatus.UNKNOWN &&
|
||
R.AdvisedUids == S3.AdvisedUids
|
||
)
|
||
}
|
||
Check("切片 Register:无关动作不改状态", ReduceRegister(S4, AddFrags(1)) == S4)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, RegisterFailed)
|
||
Check(
|
||
"根:RegisterFailed 同时动 migrationState 与 register",
|
||
Root1.Register.RegisterStatus == RegistrationStatus.FAILED &&
|
||
Root1.MigrationState.RegisterStatus ==
|
||
MigrationRegisterStatus.FAILED
|
||
)
|
||
val W = RootWiring.ByField("register")!!
|
||
Check(
|
||
"根:register 委派到 Cit / cp.stg 且 19 个动作",
|
||
W.SliceClass == "Cit" && W.Reducer == "cp.stg" && W.Actions.size == 19
|
||
)
|
||
}
|
||
|
||
// 63 切片 reducer:UserReports(Zst / qot.zth,5 字段;6 个动作)
|
||
run {
|
||
val S0 = UserReportsStateSlice()
|
||
Check(
|
||
"切片 UserReports:默认值完整",
|
||
!S0.IsEnabled && !S0.HelpdeskTicketsEnabled && S0.AvailableReports == 0 &&
|
||
S0.AvailableGameReports == 0 && S0.Reports.isEmpty()
|
||
)
|
||
val S1 = ReduceUserReports(S0, InitUserReports(true, true, 5, 3))
|
||
Check(
|
||
"切片 UserReports:InitUserReports 写四个开关/额度",
|
||
S1.IsEnabled && S1.HelpdeskTicketsEnabled &&
|
||
S1.AvailableReports == 5 && S1.AvailableGameReports == 3
|
||
)
|
||
Check(
|
||
"切片 UserReports:SetAvailableReports / SetAvailableGameReports",
|
||
ReduceUserReports(S1, SetAvailableReports(7)).AvailableReports == 7 &&
|
||
ReduceUserReports(S1, SetAvailableGameReports(9)).AvailableGameReports == 9
|
||
)
|
||
val S2 = ReduceUserReports(S1, ReportProBattleName("b1"))
|
||
Check(
|
||
"切片 UserReports:ReportProBattleName 追加 BATTLE_NAME 记录",
|
||
S2.Reports.size == 1 &&
|
||
(S2.Reports[0] as ReportEntry).kind == ReportKind.BATTLE_NAME &&
|
||
(S2.Reports[0] as ReportEntry).targetId == "b1" &&
|
||
(S2.Reports[0] as ReportEntry).expireTime > 0.0
|
||
)
|
||
val S3 = ReduceUserReports(
|
||
ReduceUserReports(S2, ReportLobbyChatMessage("m1")),
|
||
ReportBattleChatMessage("m2"),
|
||
)
|
||
Check(
|
||
"切片 UserReports:两种聊天举报各追加一条",
|
||
S3.Reports.size == 3 &&
|
||
(S3.Reports[1] as ReportEntry).kind == ReportKind.LOBBY_CHAT_MESSAGE &&
|
||
(S3.Reports[2] as ReportEntry).kind == ReportKind.BATTLE_CHAT_MESSAGE
|
||
)
|
||
Check(
|
||
"切片 UserReports:无关动作不改状态",
|
||
ReduceUserReports(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ReportProBattleName("b"))
|
||
Check(
|
||
"根:ReportProBattleName 只动 userReportsState",
|
||
Root1.UserReportsState.Reports.size == 1 &&
|
||
Root1.Register == Root0.Register
|
||
)
|
||
val W = RootWiring.ByField("userReportsState")!!
|
||
Check(
|
||
"根:userReportsState 委派到 Zst / qot.zth 且 6 个动作",
|
||
W.SliceClass == "Zst" && W.Reducer == "qot.zth" && W.Actions.size == 6
|
||
)
|
||
}
|
||
|
||
// 64 切片 reducer:Friends(KD / NL().cr9,8 字段;13 个动作)
|
||
run {
|
||
val S0 = FriendsStateSlice()
|
||
Check(
|
||
"切片 Friends:默认值完整",
|
||
S0.UserSearchState == FriendSearchState.HIDDEN && S0.ContextMenuUser == null &&
|
||
S0.AcceptedLimit == 0 && S0.IncomingLimit == 0 &&
|
||
S0.Accepted.isEmpty() && S0.NewAccepted.isEmpty() &&
|
||
S0.Incoming.isEmpty() && S0.NewIncoming.isEmpty()
|
||
)
|
||
val S1 = ReduceFriends(S0, ChangeFriendAddFindState(FriendSearchState.SEARCHING))
|
||
Check(
|
||
"切片 Friends:ChangeFriendAddFindState 写搜索状态",
|
||
S1.UserSearchState == FriendSearchState.SEARCHING
|
||
)
|
||
Check(
|
||
"切片 Friends:SetValidationStatus 命中即 FOUND,null 即 NOT_FOUND",
|
||
ReduceFriends(S1, SetValidationStatus("ok")).UserSearchState ==
|
||
FriendSearchState.FOUND &&
|
||
ReduceFriends(S1, SetValidationStatus(null)).UserSearchState ==
|
||
FriendSearchState.NOT_FOUND
|
||
)
|
||
Check(
|
||
"切片 Friends:SetLimits 写两个上限",
|
||
ReduceFriends(S1, SetLimits(50, 30)).let {
|
||
it.AcceptedLimit == 50 && it.IncomingLimit == 30
|
||
}
|
||
)
|
||
Check(
|
||
"切片 Friends:右键菜单设置 / 清除",
|
||
ReduceFriends(S1, SetContextMenuUser("u1")).ContextMenuUser == "u1" &&
|
||
ReduceFriends(S1, ClearContextMenuUser).ContextMenuUser == null
|
||
)
|
||
val S2 = ReduceFriends(S1, AddUsersToAccepted(listOf(1L, 2L)))
|
||
Check(
|
||
"切片 Friends:AddUsersToAccepted 整批入集合",
|
||
S2.Accepted == setOf<Any?>(1L, 2L)
|
||
)
|
||
Check(
|
||
"切片 Friends:RemoveUserFromAccepted 删单值",
|
||
ReduceFriends(S2, RemoveUserFromAccepted(1L)).Accepted == setOf<Any?>(2L)
|
||
)
|
||
val S3 = ReduceFriends(S2, AddUsersToNewAccepted(clanId = 0, userIds = listOf(3L, 4L)))
|
||
Check(
|
||
"切片 Friends:AddUsersToNewAccepted 读 userIds",
|
||
S3.NewAccepted == setOf<Any?>(3L, 4L)
|
||
)
|
||
Check(
|
||
"切片 Friends:RemoveUserFromNewAccepted / AddUsersToIncoming",
|
||
ReduceFriends(S3, RemoveUserFromNewAccepted(3L)).NewAccepted == setOf<Any?>(4L) &&
|
||
ReduceFriends(S3, AddUsersToIncoming(listOf(5L))).Incoming == setOf<Any?>(5L)
|
||
)
|
||
Check(
|
||
"切片 Friends:RemoveUsersFromNewIncoming 按集合减",
|
||
ReduceFriends(
|
||
ReduceFriends(S3, AddUsersToNewIncoming(listOf(6L, 7L))),
|
||
RemoveUsersFromNewIncoming(listOf(6L)),
|
||
).NewIncoming == setOf<Any?>(7L)
|
||
)
|
||
Check(
|
||
"切片 Friends:无关动作不改状态",
|
||
ReduceFriends(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ChangeFriendAddFindState(FriendSearchState.FOUND))
|
||
Check(
|
||
"根:ChangeFriendAddFindState 只动 friends",
|
||
Root1.Friends.UserSearchState == FriendSearchState.FOUND &&
|
||
Root1.Partner == Root0.Partner
|
||
)
|
||
val W = RootWiring.ByField("friends")!!
|
||
Check(
|
||
"根:friends 委派到 KD / NL().cr9 且 13 个动作",
|
||
W.SliceClass == "KD" && W.Reducer == "NL().cr9" && W.Actions.size == 13
|
||
)
|
||
}
|
||
|
||
// 65 切片 reducer:Supplies(wE / $t.fq9,1 字段;SetSupplyCount)
|
||
run {
|
||
val S0 = SuppliesStateSlice()
|
||
Check("切片 Supplies:默认空 Map", S0.Count.isEmpty())
|
||
val S1 = ReduceSupplies(S0, SetSupplyCount("mine", 3))
|
||
Check(
|
||
"切片 Supplies:SetSupplyCount 写键值",
|
||
S1.Count == mapOf<Any?, Any?>("mine" to 3)
|
||
)
|
||
Check(
|
||
"切片 Supplies:再次写入覆盖旧值",
|
||
ReduceSupplies(S1, SetSupplyCount("mine", 1)).Count ==
|
||
mapOf<Any?, Any?>("mine" to 1)
|
||
)
|
||
Check("切片 Supplies:无关动作不改状态", ReduceSupplies(S1, AddFrags(1)) == S1)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetSupplyCount("k", 9))
|
||
Check(
|
||
"根:SetSupplyCount 只动 suppliesState",
|
||
Root1.SuppliesState.Count == mapOf<Any?, Any?>("k" to 9) &&
|
||
Root1.ClanUserInfo == Root0.ClanUserInfo
|
||
)
|
||
val W = RootWiring.ByField("suppliesState")!!
|
||
Check(
|
||
"根:suppliesState 委派到 wE / \$t.fq9",
|
||
W.SliceClass == "wE" && W.Reducer == "\$t.fq9" &&
|
||
W.Actions == listOf("SetSupplyCount")
|
||
)
|
||
}
|
||
|
||
// 66 切片 reducer:ClanUserInfo(hO / de.gr1,1 字段;Loaded 合并)
|
||
run {
|
||
val S0 = ClanUserInfoStateSlice()
|
||
Check("切片 ClanUserInfo:默认空 Map", S0.Users.isEmpty())
|
||
val S1 = ReduceClanUserInfo(S0, Loaded(mapOf("u1" to ClanNotifierData(1L, null, 11L))))
|
||
Check(
|
||
"切片 ClanUserInfo:Loaded 写入用户表",
|
||
S1.Users.keys == setOf<Any?>("u1") &&
|
||
(S1.Users["u1"] as ClanNotifierData).userId == 11L
|
||
)
|
||
Check(
|
||
"切片 ClanUserInfo:第二次 Loaded 是合并不是覆盖",
|
||
ReduceClanUserInfo(S1, Loaded(mapOf("u2" to ClanNotifierData(2L, null, 22L)))).Users.keys ==
|
||
setOf<Any?>("u1", "u2")
|
||
)
|
||
Check(
|
||
"切片 ClanUserInfo:无关动作不改状态",
|
||
ReduceClanUserInfo(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, Loaded(mapOf("x" to ClanNotifierData(3L, null, 33L))))
|
||
Check(
|
||
"根:Loaded 只动 clanUserInfo",
|
||
Root1.ClanUserInfo.Users.keys == setOf<Any?>("x") &&
|
||
Root1.SuppliesState == Root0.SuppliesState
|
||
)
|
||
val W = RootWiring.ByField("clanUserInfo")!!
|
||
Check(
|
||
"根:clanUserInfo 委派到 hO / de.gr1",
|
||
W.SliceClass == "hO" && W.Reducer == "de.gr1" &&
|
||
W.Actions == listOf("Loaded")
|
||
)
|
||
}
|
||
|
||
// 67 切片 reducer:BattleInvite(att / Ttt().nth,3 字段;6 个动作)
|
||
run {
|
||
val S0 = BattleInviteStateSlice()
|
||
Check(
|
||
"切片 BattleInvite:默认开启、两个集合为空",
|
||
S0.IsEnabled && S0.InvitedUsers.isEmpty() && S0.SelectedForInvite.isEmpty()
|
||
)
|
||
val S1 = ReduceBattleInvite(S0, AddInvitedUser(1L))
|
||
Check("切片 BattleInvite:AddInvitedUser 入已邀请集合", S1.InvitedUsers == setOf<Any?>(1L))
|
||
Check(
|
||
"切片 BattleInvite:InvitationResponse 从已邀请集合删",
|
||
ReduceBattleInvite(S1, InvitationResponse(1L, "ok")).InvitedUsers.isEmpty()
|
||
)
|
||
val S2 = ReduceBattleInvite(S1, SelectUserToInvite(2L))
|
||
Check(
|
||
"切片 BattleInvite:SelectUserToInvite 入待邀请集合",
|
||
S2.SelectedForInvite == setOf<Any?>(2L)
|
||
)
|
||
Check(
|
||
"切片 BattleInvite:RemoveUserFromInvited 从待邀请集合删",
|
||
ReduceBattleInvite(S2, RemoveUserFromInvited(2L)).SelectedForInvite.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 BattleInvite:ClearSelectedForInvite 清空",
|
||
ReduceBattleInvite(S2, ClearSelectedForInvite).SelectedForInvite.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 BattleInvite:EnableSetBattleInviteNotifications 写开关",
|
||
!ReduceBattleInvite(S2, EnableSetBattleInviteNotifications(false)).IsEnabled
|
||
)
|
||
Check(
|
||
"切片 BattleInvite:无关动作不改状态",
|
||
ReduceBattleInvite(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddInvitedUser(9L))
|
||
Check(
|
||
"根:AddInvitedUser 只动 battleInviteState",
|
||
Root1.BattleInviteState.InvitedUsers == setOf<Any?>(9L) &&
|
||
Root1.UserRenameState == Root0.UserRenameState
|
||
)
|
||
val W = RootWiring.ByField("battleInviteState")!!
|
||
Check(
|
||
"根:battleInviteState 委派到 att / Ttt().nth 且 6 个动作",
|
||
W.SliceClass == "att" && W.Reducer == "Ttt().nth" &&
|
||
W.Actions.toSet() == setOf(
|
||
"AddInvitedUser", "InvitationResponse", "SelectUserToInvite",
|
||
"RemoveUserFromInvited", "ClearSelectedForInvite",
|
||
"EnableSetBattleInviteNotifications",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 68 切片 reducer:Partner(UJ / (inline@partner),15 字段;11 个动作)
|
||
run {
|
||
val S0 = PartnerStateSlice()
|
||
Check(
|
||
"切片 Partner:默认值完整",
|
||
S0.PartnerId == null && S0.LoginParams.isEmpty() && S0.Params.isEmpty() &&
|
||
!S0.HasOwnPaymentSystem && !S0.HasSocialFunction &&
|
||
!S0.HasClientPaymentAction && S0.PaymentAction == null &&
|
||
S0.InviteAction == null && !S0.CanOpenExternalLinks &&
|
||
!S0.HasAccountBinding && S0.FailedRedirectUrl == null &&
|
||
S0.LoginForBindingStatus == ByPasswordStatus.NONE &&
|
||
!S0.IsAccountAlreadyLinked && S0.ItemsEnabledForPartner.isEmpty() &&
|
||
!S0.IsPartnerReady
|
||
)
|
||
val S1 = ReducePartner(S0, SetPartnerId("p1", "https://fail"))
|
||
Check(
|
||
"切片 Partner:SetPartnerId 同时写失败跳转",
|
||
S1.PartnerId == "p1" && S1.FailedRedirectUrl == "https://fail"
|
||
)
|
||
val S2 = ReducePartner(
|
||
S1,
|
||
InitSocialNetworkParameters(
|
||
hasOwnPaymentSystem = true, hasSocialFunction = true,
|
||
hasClientPaymentAction = false, failedRedirectUrl = null,
|
||
canOpenExternalLinks = true, hasAccountBinding = true,
|
||
),
|
||
)
|
||
Check(
|
||
"切片 Partner:InitSocialNetworkParameters 写 6 个字段",
|
||
S2.HasOwnPaymentSystem && S2.HasSocialFunction && !S2.HasClientPaymentAction &&
|
||
S2.CanOpenExternalLinks && S2.HasAccountBinding
|
||
)
|
||
Check(
|
||
"切片 Partner:loginParams / params 分别写入",
|
||
ReducePartner(S2, SetLoginParams(mapOf("a" to 1))).LoginParams ==
|
||
mapOf<Any?, Any?>("a" to 1) &&
|
||
ReducePartner(S2, SetPartnerParameters(mapOf("b" to 2))).Params ==
|
||
mapOf<Any?, Any?>("b" to 2)
|
||
)
|
||
Check(
|
||
"切片 Partner:两种邀请动作都写 inviteAction",
|
||
ReducePartner(S2, InitPartnerInviteAction("i1")).InviteAction == "i1" &&
|
||
ReducePartner(S2, UpdatePartnerInviteAction("i2")).InviteAction == "i2"
|
||
)
|
||
Check(
|
||
"切片 Partner:SetLoginStatusForBinding 写绑定状态",
|
||
ReducePartner(S2, SetLoginStatusForBinding(ByPasswordStatus.WRONG_PASSWORD))
|
||
.LoginForBindingStatus == ByPasswordStatus.WRONG_PASSWORD
|
||
)
|
||
Check(
|
||
"切片 Partner:已绑定标记两态",
|
||
ReducePartner(S2, MarkAccountLinked).IsAccountAlreadyLinked &&
|
||
!ReducePartner(S2, MarkAccountNotLinked).IsAccountAlreadyLinked
|
||
)
|
||
Check(
|
||
"切片 Partner:SetPartnerItemEnabled 写物品开关 Map",
|
||
ReducePartner(S2, SetPartnerItemEnabled("item", true))
|
||
.ItemsEnabledForPartner == mapOf<Any?, Any?>("item" to true)
|
||
)
|
||
Check(
|
||
"切片 Partner:SetPartnerReady 写就绪位",
|
||
ReducePartner(S2, SetPartnerReady(true)).IsPartnerReady
|
||
)
|
||
Check("切片 Partner:无关动作不改状态", ReducePartner(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetPartnerReady(true))
|
||
Check(
|
||
"根:SetPartnerReady 只动 partner",
|
||
Root1.Partner.IsPartnerReady && Root1.Friends == Root0.Friends
|
||
)
|
||
val W = RootWiring.ByField("partner")!!
|
||
Check(
|
||
"根:partner 是内联 reducer 且 11 个动作",
|
||
W.SliceClass == "UJ" && W.Reducer == "(inline@partner)" && W.Actions.size == 11
|
||
)
|
||
}
|
||
|
||
// 69 切片 reducer:UserRename(O8 / Rf.pte,10 字段;11 个动作)
|
||
run {
|
||
val S0 = UserRenameStateSlice()
|
||
Check(
|
||
"切片 UserRename:默认值完整",
|
||
!S0.IsFreeRenameAvailable && S0.UserRenameShopItemId == null &&
|
||
S0.UidInputState == RenameUidState.EMPTY && S0.RenameSuccessful == null &&
|
||
S0.RubiesForRename == Int.MAX_VALUE && !S0.IsResponseDelivered &&
|
||
!S0.IsResponseActual && S0.ChangeUidHash == null && S0.Email == null &&
|
||
S0.ShowLinkErrorWarning
|
||
)
|
||
val S1 = ReduceUserRename(S0, ChangeUidInputState(RenameUidState.SHORT))
|
||
Check(
|
||
"切片 UserRename:ChangeUidInputState 写输入状态",
|
||
S1.UidInputState == RenameUidState.SHORT
|
||
)
|
||
Check(
|
||
"切片 UserRename:免费改名 / 改名成功 / 改名失败",
|
||
ReduceUserRename(S1, MarkFreeRenameAvailable).IsFreeRenameAvailable &&
|
||
ReduceUserRename(S1, MarkRenameSucceeded).let {
|
||
!it.IsFreeRenameAvailable && it.RenameSuccessful == true
|
||
} &&
|
||
ReduceUserRename(S1, MarkRenameFailed).RenameSuccessful == false
|
||
)
|
||
Check(
|
||
"切片 UserRename:SetValidationStatus 按 ordinal 映射输入状态",
|
||
ReduceUserRename(S1, SetValidationStatus(RenameUidState.FREE)).let {
|
||
it.UidInputState == RenameUidState.FREE && it.IsResponseDelivered
|
||
} &&
|
||
ReduceUserRename(S1, SetValidationStatus(RenameUidState.SHORT)).UidInputState ==
|
||
RenameUidState.NOT_VALID &&
|
||
ReduceUserRename(S1, SetValidationStatus(RenameUidState.NOT_VALID))
|
||
.UidInputState == RenameUidState.FORBIDDEN
|
||
)
|
||
Check(
|
||
"切片 UserRename:SetRubiesForRename 写价格",
|
||
ReduceUserRename(S1, SetRubiesForRename(99)).RubiesForRename == 99
|
||
)
|
||
val S2 = ReduceUserRename(
|
||
ReduceUserRename(
|
||
ReduceUserRename(S1, SetChangeUidParams("hash", "mail")),
|
||
ResponseDelivered(true),
|
||
),
|
||
ResponseActual(true),
|
||
)
|
||
Check(
|
||
"切片 UserRename:改绑参数与两个 response 位",
|
||
S2.ChangeUidHash == "hash" && S2.Email == "mail" &&
|
||
S2.IsResponseDelivered && S2.IsResponseActual
|
||
)
|
||
Check(
|
||
"切片 UserRename:ResetRenameDialog 复位弹窗字段",
|
||
ReduceUserRename(S2, ResetRenameDialog).let {
|
||
it.UserRenameShopItemId == null &&
|
||
it.UidInputState == RenameUidState.EMPTY &&
|
||
it.RenameSuccessful == null && !it.IsResponseDelivered &&
|
||
!it.IsResponseActual && it.ChangeUidHash == "hash"
|
||
}
|
||
)
|
||
Check(
|
||
"切片 UserRename:HideLinkErrorWarning 关提示",
|
||
!ReduceUserRename(S2, HideLinkErrorWarning).ShowLinkErrorWarning
|
||
)
|
||
Check(
|
||
"切片 UserRename:无关动作不改状态",
|
||
ReduceUserRename(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetRubiesForRename(5))
|
||
Check(
|
||
"根:SetRubiesForRename 只动 userRenameState",
|
||
Root1.UserRenameState.RubiesForRename == 5 &&
|
||
Root1.Partner == Root0.Partner
|
||
)
|
||
val W = RootWiring.ByField("userRenameState")!!
|
||
Check(
|
||
"根:userRenameState 委派到 O8 / Rf.pte 且 11 个动作",
|
||
W.SliceClass == "O8" && W.Reducer == "Rf.pte" && W.Actions.size == 11
|
||
)
|
||
}
|
||
|
||
// 70 切片 reducer:BattlePause(dT / NT().mqc,3 字段;4 个动作)
|
||
run {
|
||
val S0 = BattlePauseStateSlice()
|
||
Check(
|
||
"切片 BattlePause:默认不暂停、无剩余时间",
|
||
!S0.InAutoPauseMode && S0.TimeForAutoLeave == null && S0.IdleKickPeriodInMsec == 0
|
||
)
|
||
val S1 = ReduceBattlePause(S0, OnPauseMode("30s"))
|
||
Check(
|
||
"切片 BattlePause:OnPauseMode 置暂停并写剩余时间",
|
||
S1.InAutoPauseMode && S1.TimeForAutoLeave == "30s"
|
||
)
|
||
Check(
|
||
"切片 BattlePause:CancelAutoPause 只清暂停位",
|
||
ReduceBattlePause(S1, CancelAutoPause).let {
|
||
!it.InAutoPauseMode && it.TimeForAutoLeave == "30s"
|
||
}
|
||
)
|
||
Check(
|
||
"切片 BattlePause:SetIdleKickPeriod 写周期",
|
||
ReduceBattlePause(S1, SetIdleKickPeriod(120)).IdleKickPeriodInMsec == 120
|
||
)
|
||
Check(
|
||
"切片 BattlePause:ResetBattlePause 保留挂机周期",
|
||
ReduceBattlePause(
|
||
ReduceBattlePause(S1, SetIdleKickPeriod(120)),
|
||
ResetBattlePause,
|
||
).let { !it.InAutoPauseMode && it.IdleKickPeriodInMsec == 120 }
|
||
)
|
||
Check(
|
||
"切片 BattlePause:无关动作不改状态",
|
||
ReduceBattlePause(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, CancelAutoPause)
|
||
Check(
|
||
"根:CancelAutoPause 只动 battlePauseState",
|
||
!Root1.BattlePauseState.InAutoPauseMode &&
|
||
Root1.BattleViewState == Root0.BattleViewState
|
||
)
|
||
val W = RootWiring.ByField("battlePauseState")!!
|
||
Check(
|
||
"根:battlePauseState 委派到 dT / NT().mqc 且 4 个动作",
|
||
W.SliceClass == "dT" && W.Reducer == "NT().mqc" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ResetBattlePause", "OnPauseMode", "CancelAutoPause", "SetIdleKickPeriod",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 71 切片 reducer:BattleView(DT / sI.jqd,7 字段;14 个动作)
|
||
run {
|
||
val S0 = BattleViewStateSlice()
|
||
Check(
|
||
"切片 BattleView:默认在后台、隐藏、输入开",
|
||
S0.InBackgroundState && S0.IsHidden && S0.InvisibleElements.isEmpty() &&
|
||
S0.InputEnabled && !S0.BattleStatisticsActive && !S0.BattleMenuActive &&
|
||
S0.JoystickPosition == null
|
||
)
|
||
val S1 = ReduceBattleView(S0, SetVisibility(true, "e1"))
|
||
Check(
|
||
"切片 BattleView:SetVisibility(true) 从隐藏集合删元素",
|
||
S1.InvisibleElements.isEmpty()
|
||
)
|
||
val S2 = ReduceBattleView(S1, SetVisibility(false, "e1"))
|
||
Check(
|
||
"切片 BattleView:SetVisibility(false) 加入隐藏集合",
|
||
S2.InvisibleElements == setOf<Any?>("e1")
|
||
)
|
||
val S3 = ReduceBattleView(
|
||
S2,
|
||
SetElementsVisibility(true, listOf("e1", "e2")),
|
||
)
|
||
Check(
|
||
"切片 BattleView:SetElementsVisibility 按集合增删",
|
||
S3.InvisibleElements.isEmpty() &&
|
||
ReduceBattleView(S3, SetElementsVisibility(false, listOf("x", "y")))
|
||
.InvisibleElements == setOf<Any?>("x", "y")
|
||
)
|
||
Check(
|
||
"切片 BattleView:ClearInvisibleElements 清空",
|
||
ReduceBattleView(S2, ClearInvisibleElements).InvisibleElements.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 BattleView:输入开关两态",
|
||
ReduceBattleView(S2, DisableBattleInput).let { !it.InputEnabled } &&
|
||
ReduceBattleView(S2, EnableBattleInput).InputEnabled
|
||
)
|
||
Check(
|
||
"切片 BattleView:统计与菜单开关两态",
|
||
ReduceBattleView(S2, EnableBattleStatistics).BattleStatisticsActive &&
|
||
!ReduceBattleView(S2, DisableBattleStatistics).BattleStatisticsActive &&
|
||
ReduceBattleView(S2, EnableBattleMenu).BattleMenuActive &&
|
||
!ReduceBattleView(S2, DisableBattleMenu).BattleMenuActive
|
||
)
|
||
Check(
|
||
"切片 BattleView:显示 / 后台开关两态",
|
||
!ReduceBattleView(S2, ShowBattleView).IsHidden &&
|
||
ReduceBattleView(S2, HideBattleView).IsHidden &&
|
||
!ReduceBattleView(S2, LeaveBackground).InBackgroundState &&
|
||
ReduceBattleView(S2, EnterBackground).InBackgroundState
|
||
)
|
||
Check(
|
||
"切片 BattleView:ResetBattleView 整片复位",
|
||
ReduceBattleView(S2, ResetBattleView) == BattleViewStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleView:无关动作不改状态",
|
||
ReduceBattleView(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, DisableBattleInput)
|
||
Check(
|
||
"根:DisableBattleInput 只动 battleViewState",
|
||
!Root1.BattleViewState.InputEnabled &&
|
||
Root1.BattlePauseState == Root0.BattlePauseState
|
||
)
|
||
val W = RootWiring.ByField("battleViewState")!!
|
||
Check(
|
||
"根:battleViewState 委派到 DT / sI.jqd 且 14 个动作",
|
||
W.SliceClass == "DT" && W.Reducer == "sI.jqd" && W.Actions.size == 14
|
||
)
|
||
}
|
||
|
||
// 72 切片 reducer:TankActionsLog(vI / wi.gqe,2 字段;4 个动作)
|
||
run {
|
||
val S0 = TankActionsLogStateSlice()
|
||
Check(
|
||
"切片 TankActionsLog:默认两个集合为空",
|
||
S0.Logs.isEmpty() && S0.Noticed.isEmpty()
|
||
)
|
||
val S1 = ReduceTankActionsLog(S0, OnUserJoinTheBattle(1L))
|
||
Check(
|
||
"切片 TankActionsLog:OnUserJoinTheBattle 记入 noticed",
|
||
S1.Noticed == setOf<Any?>(1L)
|
||
)
|
||
Check(
|
||
"切片 TankActionsLog:OnUserLeaveTheBattle 移除 noticed",
|
||
ReduceTankActionsLog(S1, OnUserLeaveTheBattle(1L)).Noticed.isEmpty()
|
||
)
|
||
var S2 = S1
|
||
for (I in 1..12) S2 = ReduceTankActionsLog(S2, UpdateLog("log$I"))
|
||
Check(
|
||
"切片 TankActionsLog:超过 10 条从头截断",
|
||
S2.Logs.size == 10 && S2.Logs.first() == "log3" && S2.Logs.last() == "log12"
|
||
)
|
||
Check(
|
||
"切片 TankActionsLog:ResetTankActionsLog 整片复位",
|
||
ReduceTankActionsLog(S2, ResetTankActionsLog) == TankActionsLogStateSlice()
|
||
)
|
||
Check(
|
||
"切片 TankActionsLog:无关动作不改状态",
|
||
ReduceTankActionsLog(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateLog("x"))
|
||
Check(
|
||
"根:UpdateLog 只动 tankActionsLogState",
|
||
Root1.TankActionsLogState.Logs == listOf<Any?>("x") &&
|
||
Root1.SuppliesTutorial == Root0.SuppliesTutorial
|
||
)
|
||
val W = RootWiring.ByField("tankActionsLogState")!!
|
||
Check(
|
||
"根:tankActionsLogState 委派到 vI / wi.gqe 且 4 个动作",
|
||
W.SliceClass == "vI" && W.Reducer == "wi.gqe" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ResetTankActionsLog", "OnUserJoinTheBattle", "OnUserLeaveTheBattle",
|
||
"UpdateLog",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 73 切片 reducer:SuppliesTutorial(ust / Uw.uth,2 字段;2 个动作)
|
||
run {
|
||
val S0 = SuppliesTutorialStateSlice()
|
||
Check(
|
||
"切片 SuppliesTutorial:默认未加载、未激活",
|
||
S0.LoadedElement == null && !S0.ActivatedInCurrentBattle
|
||
)
|
||
val S1 = ReduceSuppliesTutorial(S0, SetLoadedElement("hull"))
|
||
Check("切片 SuppliesTutorial:SetLoadedElement 写元素", S1.LoadedElement == "hull")
|
||
Check(
|
||
"切片 SuppliesTutorial:ActivatedInCurrentBattle 写激活位",
|
||
ReduceSuppliesTutorial(S1, ActivatedInCurrentBattle(true))
|
||
.ActivatedInCurrentBattle
|
||
)
|
||
Check(
|
||
"切片 SuppliesTutorial:无关动作不改状态",
|
||
ReduceSuppliesTutorial(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetLoadedElement("x"))
|
||
Check(
|
||
"根:SetLoadedElement 只动 suppliesTutorial",
|
||
Root1.SuppliesTutorial.LoadedElement == "x" &&
|
||
Root1.TankActionsLogState == Root0.TankActionsLogState
|
||
)
|
||
val W = RootWiring.ByField("suppliesTutorial")!!
|
||
Check(
|
||
"根:suppliesTutorial 委派到 ust / Uw.uth",
|
||
W.SliceClass == "ust" && W.Reducer == "Uw.uth" &&
|
||
W.Actions.toSet() == setOf("ActivatedInCurrentBattle", "SetLoadedElement")
|
||
)
|
||
}
|
||
|
||
// 74 切片 reducer:ClanChat(qnt / yv.qtg,3 字段;5 个动作)
|
||
run {
|
||
val S0 = ClanChatStateSlice()
|
||
Check(
|
||
"切片 ClanChat:默认关闭、无消息、无配置",
|
||
!S0.Open && S0.Messages.isEmpty() && S0.Config == null
|
||
)
|
||
val S1 = ReduceClanChat(S0, OpenClanChat)
|
||
Check("切片 ClanChat:OpenClanChat 打开", S1.Open)
|
||
Check(
|
||
"切片 ClanChat:CloseClanChat 关闭",
|
||
!ReduceClanChat(S1, CloseClanChat).Open
|
||
)
|
||
val S2 = ReduceClanChat(S1, DoAddMessages(listOf("m1", "m2")))
|
||
Check(
|
||
"切片 ClanChat:DoAddMessages 追加消息",
|
||
S2.Messages == listOf<Any?>("m1", "m2")
|
||
)
|
||
var S3 = S2
|
||
for (I in 1..120) S3 = ReduceClanChat(S3, DoAddMessages(listOf("x$I")))
|
||
Check(
|
||
"切片 ClanChat:超过 100 条只保留最后 100",
|
||
S3.Messages.size == 100 && S3.Messages.last() == "x120"
|
||
)
|
||
Check(
|
||
"切片 ClanChat:Configure 写配置",
|
||
ReduceClanChat(S1, Configure(emptyList(), emptyList(), false, "", null, null, "", 0, 0, false, null, ""))
|
||
.Config != null
|
||
)
|
||
Check(
|
||
"切片 ClanChat:ResetClanChat 整片复位",
|
||
ReduceClanChat(S3, ResetClanChat) == ClanChatStateSlice()
|
||
)
|
||
Check(
|
||
"切片 ClanChat:无关动作不改状态",
|
||
ReduceClanChat(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, OpenClanChat)
|
||
Check(
|
||
"根:OpenClanChat 只动 clanChatState",
|
||
Root1.ClanChatState.Open &&
|
||
Root1.BattleSearchState == Root0.BattleSearchState
|
||
)
|
||
val W = RootWiring.ByField("clanChatState")!!
|
||
Check(
|
||
"根:clanChatState 委派到 qnt / yv.qtg 且 5 个动作",
|
||
W.SliceClass == "qnt" && W.Reducer == "yv.qtg" &&
|
||
W.Actions.toSet() == setOf(
|
||
"OpenClanChat", "CloseClanChat", "Configure", "DoAddMessages",
|
||
"ResetClanChat",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 75 切片 reducer:BattleSearch(Itt / Htt().vtg,5 字段;9 个动作)
|
||
run {
|
||
val Format = BattleFormat("fmt", "name", "desc", null, null)
|
||
val S0 = BattleSearchStateSlice()
|
||
Check(
|
||
"切片 BattleSearch:默认值完整",
|
||
S0.Filter == null && S0.SelectedFormat == null && S0.Search == null &&
|
||
!S0.SuspiciousBattlesOnly && S0.SelectedBattleId == null
|
||
)
|
||
val S1 = ReduceBattleSearch(S0, SetFilter(true))
|
||
Check("切片 BattleSearch:SetFilter 写过滤位", S1.Filter == true)
|
||
val S2 = ReduceBattleSearch(S1, SetSearch("abc"))
|
||
Check("切片 BattleSearch:SetSearch 写搜索串", S2.Search == "abc")
|
||
Check(
|
||
"切片 BattleSearch:SetSuspiciousFilter 写布尔",
|
||
ReduceBattleSearch(S2, SetSuspiciousFilter(true)).SuspiciousBattlesOnly
|
||
)
|
||
Check(
|
||
"切片 BattleSearch:EnterToBattle 清空搜索串",
|
||
ReduceBattleSearch(S2, EnterToBattle(7L)).Search == null
|
||
)
|
||
Check(
|
||
"切片 BattleSearch:SelectBattle / SelectBattleFormat",
|
||
ReduceBattleSearch(S2, SelectBattle(7L)).SelectedBattleId == 7L &&
|
||
ReduceBattleSearch(S2, SelectBattleFormat(Format)).SelectedFormat == Format
|
||
)
|
||
Check(
|
||
"切片 BattleSearch:RemoveBattle 只清匹配的那条",
|
||
ReduceBattleSearch(ReduceBattleSearch(S2, SelectBattle(7L)), RemoveBattle(7L))
|
||
.SelectedBattleId == null &&
|
||
ReduceBattleSearch(
|
||
ReduceBattleSearch(S2, SelectBattle(7L)),
|
||
RemoveBattle(8L),
|
||
).SelectedBattleId == 7L
|
||
)
|
||
Check(
|
||
"切片 BattleSearch:ClearSelectedBattle 清空选中",
|
||
ReduceBattleSearch(S2, ClearSelectedBattle).SelectedBattleId == null
|
||
)
|
||
Check("切片 BattleSearch:Fight 不改状态", ReduceBattleSearch(S2, Fight(1L, BattleTeam.NONE)) == S2)
|
||
Check(
|
||
"切片 BattleSearch:无关动作不改状态",
|
||
ReduceBattleSearch(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetSearch("q"))
|
||
Check(
|
||
"根:SetSearch 只动 battleSearchState",
|
||
Root1.BattleSearchState.Search == "q" &&
|
||
Root1.ClanChatState == Root0.ClanChatState
|
||
)
|
||
val W = RootWiring.ByField("battleSearchState")!!
|
||
Check(
|
||
"根:battleSearchState 委派到 Itt / Htt().vtg 且 9 个动作",
|
||
W.SliceClass == "Itt" && W.Reducer == "Htt().vtg" && W.Actions.size == 9
|
||
)
|
||
}
|
||
|
||
// 76 切片 reducer:BattleStatistics(jR / rr.fqr,27 字段;22 个动作)
|
||
run {
|
||
val S0 = BattleStatisticsStateSlice()
|
||
Check(
|
||
"切片 BattleStatistics:默认值完整",
|
||
S0.BattleId == null && S0.MapName == "" && S0.Mode == null &&
|
||
S0.IsMMBattle && !S0.IsParkourMode && !S0.IsTutorial &&
|
||
S0.ValuableBattle && S0.IsReArmorEnabled && S0.RankRange == null &&
|
||
!S0.IsContinueBattle && S0.RoundRestartTime == null && S0.Score.isEmpty() &&
|
||
S0.Fund == 0 && S0.GameModeLevelsCount == 0 && S0.FlagOrBallOwner == null &&
|
||
!S0.StatVisible && !S0.RoundStarted && S0.ScoreLimit == 0 &&
|
||
!S0.MenuVisible && S0.RemainingTimeInSec == -1 && !S0.BattleLoaded &&
|
||
S0.Options.isEmpty() && S0.CustomBattleName == null &&
|
||
S0.BattleFormat == null && !S0.IsMatchmakingMark &&
|
||
S0.MapNameWithoutMode == "" && S0.DisplayClientInfo
|
||
)
|
||
val S1 = ReduceBattleStatistics(
|
||
S0,
|
||
InitBattle(
|
||
battleId = "b1", remainingTime = 180, battleName = "Desert",
|
||
matchBattle = true, isParkourMode = false, isTutorial = false,
|
||
valuableBattle = true, scoreLimit = 100, fund = 50,
|
||
options = listOf("o"), customBattleName = "custom",
|
||
battleFormat = "fmt", isMatchmakingMark = true, mapNameWithoutMode = "Desert",
|
||
),
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:InitBattle 灌入元信息并清分数 / 菜单",
|
||
S1.MapName == "Desert" && S1.IsMMBattle && S1.Fund == 50 &&
|
||
S1.ScoreLimit == 100 && S1.RemainingTimeInSec == 180 &&
|
||
S1.CustomBattleName == "custom" && S1.IsMatchmakingMark &&
|
||
S1.MapNameWithoutMode == "Desert" && S1.Options == listOf<Any?>("o") &&
|
||
S1.Score.isEmpty() && !S1.MenuVisible && !S1.IsContinueBattle
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:模式 / 护甲 / 段位 / 客户端信息",
|
||
ReduceBattleStatistics(S1, SetBattleMode("DM")).Mode == "DM" &&
|
||
!ReduceBattleStatistics(S1, SetReArmor(false)).IsReArmorEnabled &&
|
||
ReduceBattleStatistics(S1, SetRankRange(Range(10, 1))).RankRange ==
|
||
Range(10, 1) &&
|
||
!ReduceBattleStatistics(S1, SetDisplayClientInfo(false)).DisplayClientInfo
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:UpdateScore / UpdateFund 写表与资金",
|
||
ReduceBattleStatistics(S1, UpdateScore("A", 3)).Score ==
|
||
mapOf<Any?, Any?>("A" to 3) &&
|
||
ReduceBattleStatistics(S1, UpdateFund(0L, 9)).Fund == 9
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:SetRemainingTime 时长转秒",
|
||
ReduceBattleStatistics(S1, SetRemainingTime(90)).RemainingTimeInSec == 90
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:旗主设置 / 清空",
|
||
ReduceBattleStatistics(S1, SetFlagOrBallOwner(7L)).FlagOrBallOwner == 7L &&
|
||
ReduceBattleStatistics(
|
||
ReduceBattleStatistics(S1, SetFlagOrBallOwner(7L)),
|
||
ClearFlagOrBallOwner,
|
||
).FlagOrBallOwner == null
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:ShowStat / HideStat / 菜单两态",
|
||
ReduceBattleStatistics(S1, ShowStat).StatVisible &&
|
||
!ReduceBattleStatistics(S1, HideStat).StatVisible &&
|
||
!ReduceBattleStatistics(S1, CloseStatMenu).MenuVisible &&
|
||
ReduceBattleStatistics(S1, ToggleStatMenu).MenuVisible
|
||
)
|
||
val S2 = ReduceBattleStatistics(S1, BeginRound)
|
||
Check(
|
||
"切片 BattleStatistics:BeginRound 开回合并清分数",
|
||
S2.RoundStarted && !S2.StatVisible && !S2.MenuVisible &&
|
||
S2.Score.isEmpty() && S2.FlagOrBallOwner == null
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:ContinueBattle / ResetRoundState",
|
||
ReduceBattleStatistics(S2, ContinueBattle).IsContinueBattle &&
|
||
!ReduceBattleStatistics(S2, ResetRoundState).RoundStarted
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:BattleLoaded 两态与 levelsCount",
|
||
ReduceBattleStatistics(S2, MarkBattleLoaded).BattleLoaded &&
|
||
!ReduceBattleStatistics(S2, MarkBattleUnloaded).BattleLoaded &&
|
||
ReduceBattleStatistics(S2, InitBattleModeWithLevels(4))
|
||
.GameModeLevelsCount == 4
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:ResetBattleStatistics 整片复位",
|
||
ReduceBattleStatistics(S2, ResetBattleStatistics) == BattleStatisticsStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleStatistics:无关动作不改状态",
|
||
ReduceBattleStatistics(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, MarkBattleLoaded)
|
||
Check(
|
||
"根:MarkBattleLoaded 只动 battleStatistics",
|
||
Root1.BattleStatistics.BattleLoaded &&
|
||
Root1.BattleSearchState == Root0.BattleSearchState
|
||
)
|
||
val W = RootWiring.ByField("battleStatistics")!!
|
||
Check(
|
||
"根:battleStatistics 委派到 jR / rr.fqr 且 22 个动作",
|
||
W.SliceClass == "jR" && W.Reducer == "rr.fqr" && W.Actions.size == 22
|
||
)
|
||
}
|
||
|
||
// 77 切片 reducer:SoundSettings(Det / Wet().jth,10 字段;4 个动作)
|
||
run {
|
||
val S0 = SoundSettingsStateSlice()
|
||
Check(
|
||
"切片 SoundSettings:默认音量与开关",
|
||
S0.MasterVolume == 100.0 && S0.UiVolume == 100.0 &&
|
||
S0.MusicVolume == 50.0 && S0.AmbientVolume == 50.0 &&
|
||
S0.SfxVolume == 50.0 && S0.HitmarkPlayerVolume == 50.0 &&
|
||
S0.HitmarkEnemyVolume == 50.0 && !S0.Mute &&
|
||
!S0.MuteInBackground && S0.IsEntranceSoundOn
|
||
)
|
||
val Custom = SoundSettingsStateSlice(
|
||
MasterVolume = 10.0, UiVolume = 20.0, MusicVolume = 30.0,
|
||
AmbientVolume = 40.0, SfxVolume = 25.0, HitmarkPlayerVolume = 15.0,
|
||
HitmarkEnemyVolume = 5.0, Mute = true, MuteInBackground = true,
|
||
IsEntranceSoundOn = false,
|
||
)
|
||
Check(
|
||
"切片 SoundSettings:ApplySoundSettings 整体替换",
|
||
ReduceSoundSettings(S0, ApplySoundSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 SoundSettings:SetEntranceSoundSettings 整体替换",
|
||
ReduceSoundSettings(S0, SetEntranceSoundSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 SoundSettings:EnableEntranceSound 只开入口音效",
|
||
ReduceSoundSettings(Custom, EnableEntranceSound)
|
||
.let { it.IsEntranceSoundOn && it.Mute }
|
||
)
|
||
Check(
|
||
"切片 SoundSettings:ResetSettingsFromStorage 不做 IO、保留状态",
|
||
ReduceSoundSettings(Custom, ResetSettingsFromStorage) == Custom
|
||
)
|
||
Check(
|
||
"切片 SoundSettings:无关动作不改状态",
|
||
ReduceSoundSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, EnableEntranceSound)
|
||
Check(
|
||
"根:EnableEntranceSound 只动 soundSettings",
|
||
Root1.SoundSettings.IsEntranceSoundOn &&
|
||
Root1.GameSettings == Root0.GameSettings
|
||
)
|
||
val W = RootWiring.ByField("soundSettings")!!
|
||
Check(
|
||
"根:soundSettings 委派到 Det / Wet().jth 且 4 个动作",
|
||
W.SliceClass == "Det" && W.Reducer == "Wet().jth" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ApplySoundSettings", "SetEntranceSoundSettings", "EnableEntranceSound",
|
||
"ResetSettingsFromStorage",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 78 切片 reducer:GameSettings(c2 / g2().zss,6 字段;2 个动作)
|
||
run {
|
||
val S0 = GameSettingsStateSlice()
|
||
Check(
|
||
"切片 GameSettings:默认五个开关全开、padding 为 null",
|
||
S0.ShowDropZones && S0.ShowYourDrone && S0.ShowChat &&
|
||
S0.ShowNotifications && S0.ShowOtherDrone && S0.MobilePadding == null
|
||
)
|
||
val Custom = GameSettingsStateSlice(
|
||
ShowDropZones = false, ShowYourDrone = false, ShowChat = false,
|
||
ShowNotifications = false, ShowOtherDrone = false, MobilePadding = 32,
|
||
)
|
||
Check(
|
||
"切片 GameSettings:ApplyGameSettings 整体替换",
|
||
ReduceGameSettings(S0, ApplyGameSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 GameSettings:ResetSettingsFromStorage 保留状态",
|
||
ReduceGameSettings(Custom, ResetSettingsFromStorage) == Custom
|
||
)
|
||
Check(
|
||
"切片 GameSettings:无关动作不改状态",
|
||
ReduceGameSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ApplyGameSettings(Custom))
|
||
Check(
|
||
"根:ApplyGameSettings 只动 gameSettings",
|
||
Root1.GameSettings == Custom && Root1.KeyboardSettings == Root0.KeyboardSettings
|
||
)
|
||
val W = RootWiring.ByField("gameSettings")!!
|
||
Check(
|
||
"根:gameSettings 委派到 c2 / g2().zss 且 2 个动作",
|
||
W.SliceClass == "c2" && W.Reducer == "g2().zss" &&
|
||
W.Actions.toSet() == setOf("ApplyGameSettings", "ResetSettingsFromStorage")
|
||
)
|
||
}
|
||
|
||
// 79 切片 reducer:KeyboardSettings(Sut / bw.oth,1 字段;5 个动作)
|
||
run {
|
||
val S0 = KeyboardSettingsStateSlice()
|
||
Check("切片 KeyboardSettings:默认空绑定表", S0.Mapping.isEmpty())
|
||
val S1 = ReduceKeyboardSettings(S0, ChangeAction("fire", 0, "KeyF"))
|
||
Check(
|
||
"切片 KeyboardSettings:ChangeAction 写入 action→index→keyState",
|
||
S1.Mapping == mapOf<Any?, Any?>(
|
||
"fire" to mapOf<Any?, Any?>(0 to "KeyF"),
|
||
)
|
||
)
|
||
val S2 = ReduceKeyboardSettings(S1, ChangeAction("fire", 1, "KeyG"))
|
||
Check(
|
||
"切片 KeyboardSettings:同一 action 不同 index 追加绑定",
|
||
S2.Mapping == mapOf<Any?, Any?>(
|
||
"fire" to mapOf<Any?, Any?>(0 to "KeyF", 1 to "KeyG"),
|
||
)
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:同一 keyState 从别的 action 上解绑",
|
||
ReduceKeyboardSettings(
|
||
ReduceKeyboardSettings(S0, ChangeAction("fire", 0, "KeyF")),
|
||
ChangeAction("jump", 0, "KeyF"),
|
||
).Mapping == mapOf<Any?, Any?>(
|
||
"fire" to emptyMap<Any?, Any?>(),
|
||
"jump" to mapOf<Any?, Any?>(0 to "KeyF"),
|
||
)
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:RemoveAction 解绑一个 index",
|
||
ReduceKeyboardSettings(S2, RemoveAction("fire", 1)).Mapping ==
|
||
mapOf<Any?, Any?>("fire" to mapOf<Any?, Any?>(0 to "KeyF"))
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:RemoveAction 对未知 action 是 no-op",
|
||
ReduceKeyboardSettings(S2, RemoveAction("nope", 0)) == S2
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:ResetAction 保留状态(外部 helper)",
|
||
ReduceKeyboardSettings(S2, ResetAction("fire")) == S2
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:两个默认复位都清空绑定表",
|
||
ReduceKeyboardSettings(S2, ResetSettingsFromStorage) ==
|
||
KeyboardSettingsStateSlice() &&
|
||
ReduceKeyboardSettings(S2, ResetKeyMappingFromDefaults) ==
|
||
KeyboardSettingsStateSlice()
|
||
)
|
||
Check(
|
||
"切片 KeyboardSettings:无关动作不改状态",
|
||
ReduceKeyboardSettings(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, RemoveAction("fire", 1))
|
||
Check(
|
||
"根:RemoveAction 只动 keyboardSettings",
|
||
Root1.KeyboardSettings == KeyboardSettingsStateSlice() &&
|
||
Root1.TouchscreenHudSettings == Root0.TouchscreenHudSettings
|
||
)
|
||
val W = RootWiring.ByField("keyboardSettings")!!
|
||
Check(
|
||
"根:keyboardSettings 委派到 Sut / bw.oth 且 5 个动作",
|
||
W.SliceClass == "Sut" && W.Reducer == "bw.oth" && W.Actions.size == 5
|
||
)
|
||
}
|
||
|
||
// 80 切片 reducer:TouchscreenHudSettings(j7 / N7().xsv,1 字段;3 个动作)
|
||
run {
|
||
val S0 = TouchscreenHudSettingsStateSlice()
|
||
Check("切片 TouchscreenHudSettings:默认空布局", S0.Mapping.isEmpty())
|
||
val Custom = TouchscreenHudSettingsStateSlice(mapOf<Any?, Any?>("fire" to 1))
|
||
Check(
|
||
"切片 TouchscreenHudSettings:ApplyTouchscreenHudSettings 整体替换",
|
||
ReduceTouchscreenHudSettings(S0, ApplyTouchscreenHudSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 TouchscreenHudSettings:ResetTouchscreenHudMapping 恢复默认",
|
||
ReduceTouchscreenHudSettings(Custom, ResetTouchscreenHudMapping) ==
|
||
TouchscreenHudSettingsStateSlice()
|
||
)
|
||
Check(
|
||
"切片 TouchscreenHudSettings:ResetSettingsFromStorage 恢复默认",
|
||
ReduceTouchscreenHudSettings(Custom, ResetSettingsFromStorage) ==
|
||
TouchscreenHudSettingsStateSlice()
|
||
)
|
||
Check(
|
||
"切片 TouchscreenHudSettings:无关动作不改状态",
|
||
ReduceTouchscreenHudSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ResetTouchscreenHudMapping)
|
||
Check(
|
||
"根:ResetTouchscreenHudMapping 只动 touchscreenHudSettings",
|
||
Root1.TouchscreenHudSettings == TouchscreenHudSettingsStateSlice() &&
|
||
Root1.SoundSettings == Root0.SoundSettings
|
||
)
|
||
val W = RootWiring.ByField("touchscreenHudSettings")!!
|
||
Check(
|
||
"根:touchscreenHudSettings 委派到 j7 / N7().xsv 且 3 个动作",
|
||
W.SliceClass == "j7" && W.Reducer == "N7().xsv" &&
|
||
W.Actions.toSet() == setOf(
|
||
"ApplyTouchscreenHudSettings", "ResetTouchscreenHudMapping",
|
||
"ResetSettingsFromStorage",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 81 切片 reducer:GraphicsSettings(L2 / lc.isu,17 字段;1 个动作)
|
||
run {
|
||
val S0 = GraphicsSettingsStateSlice()
|
||
Check(
|
||
"切片 GraphicsSettings:默认值完整",
|
||
S0.QualityDetectByCardCompleted == null && !S0.Extended && S0.FpsLimit == 0 &&
|
||
S0.ShowFPSAndPing && S0.ShowSkybox && !S0.DynamicShadows &&
|
||
!S0.DynamicLighting && !S0.SoftParticles && S0.Fog && !S0.Antialiasing &&
|
||
S0.Vsync && S0.DecalsProfile == null && !S0.DustEnabled &&
|
||
S0.AnisotropicFilteringLevel == 0 && S0.TreesQuality == null &&
|
||
S0.EffectsQuality == null && S0.ResolutionScale == null
|
||
)
|
||
val Custom = GraphicsSettingsStateSlice(
|
||
QualityDetectByCardCompleted = true, Extended = true, FpsLimit = 144,
|
||
ShowFPSAndPing = false, ShowSkybox = false, DynamicShadows = true,
|
||
DynamicLighting = true, SoftParticles = true, Fog = false,
|
||
Antialiasing = true, Vsync = false, DecalsProfile = "high", DustEnabled = true,
|
||
AnisotropicFilteringLevel = 8, TreesQuality = "high",
|
||
EffectsQuality = "high", ResolutionScale = "1.0",
|
||
)
|
||
Check(
|
||
"切片 GraphicsSettings:ApplyGraphicsSettings 整体替换",
|
||
ReduceGraphicsSettings(S0, ApplyGraphicsSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 GraphicsSettings:无关动作不改状态",
|
||
ReduceGraphicsSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ApplyGraphicsSettings(Custom))
|
||
Check(
|
||
"根:ApplyGraphicsSettings 只动 graphicsSettings",
|
||
Root1.GraphicsSettings == Custom &&
|
||
Root1.ControlSettings == Root0.ControlSettings
|
||
)
|
||
val W = RootWiring.ByField("graphicsSettings")!!
|
||
Check(
|
||
"根:graphicsSettings 委派到 L2 / lc.isu",
|
||
W.SliceClass == "L2" && W.Reducer == "lc.isu" &&
|
||
W.Actions == listOf("ApplyGraphicsSettings")
|
||
)
|
||
}
|
||
|
||
// 82 切片 reducer:ControlSettings(i2 / _2().ess,7 字段;2 个动作)
|
||
run {
|
||
val S0 = ControlSettingsStateSlice()
|
||
Check(
|
||
"切片 ControlSettings:默认值完整",
|
||
S0.AvailableInputModes.isEmpty() && S0.InputMode == null &&
|
||
!S0.InversedBackwardMoving && !S0.InvertShaftVerticalAxis &&
|
||
S0.Mouse == ControlMouseSettings(50.0, true) &&
|
||
S0.Gamepad == ControlGamepadSettings(true, 13, 13) &&
|
||
S0.Touchscreen ==
|
||
ControlTouchscreenSettings(50.0, true, false, true, true, true, true)
|
||
)
|
||
val Custom = ControlSettingsStateSlice(
|
||
AvailableInputModes = setOf<Any?>("mouse", "touch"),
|
||
InputMode = "touch",
|
||
InversedBackwardMoving = true,
|
||
InvertShaftVerticalAxis = true,
|
||
Mouse = ControlMouseSettings(30.0, false),
|
||
Gamepad = ControlGamepadSettings(false, 20, 25),
|
||
Touchscreen = ControlTouchscreenSettings(70.0, false, true, false, false, false, false),
|
||
)
|
||
Check(
|
||
"切片 ControlSettings:ApplyControlSettings 整体替换",
|
||
ReduceControlSettings(S0, ApplyControlSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 ControlSettings:ResetSettingsFromStorage 保留状态",
|
||
ReduceControlSettings(Custom, ResetSettingsFromStorage) == Custom
|
||
)
|
||
Check(
|
||
"切片 ControlSettings:无关动作不改状态",
|
||
ReduceControlSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ApplyControlSettings(Custom))
|
||
Check(
|
||
"根:ApplyControlSettings 只动 controlSettings",
|
||
Root1.ControlSettings == Custom && Root1.BattleSettings == Root0.BattleSettings
|
||
)
|
||
val W = RootWiring.ByField("controlSettings")!!
|
||
Check(
|
||
"根:controlSettings 委派到 i2 / _2().ess 且 2 个动作",
|
||
W.SliceClass == "i2" && W.Reducer == "_2().ess" &&
|
||
W.Actions.toSet() == setOf("ApplyControlSettings", "ResetSettingsFromStorage")
|
||
)
|
||
}
|
||
|
||
// 83 切片 reducer:BattleSettings(W0 / X0().esq,11 字段;2 个动作)
|
||
run {
|
||
val S0 = BattleSettingsStateSlice()
|
||
Check(
|
||
"切片 BattleSettings:默认值完整",
|
||
S0.ShowAimHelperDot && S0.ShowDamage && S0.ShowTankPointers &&
|
||
S0.ShowButtonLabels && S0.AutoSuicide && S0.ShowStatusName &&
|
||
!S0.IsCenteredLayout && S0.BattleUISize == 50.0 &&
|
||
S0.AimhelperDotOpacity == 50.0 && S0.AimhelperDotScale == 1.0 &&
|
||
S0.AutoSupply
|
||
)
|
||
val Custom = BattleSettingsStateSlice(
|
||
ShowAimHelperDot = false, ShowDamage = false, ShowTankPointers = false,
|
||
ShowButtonLabels = false, AutoSuicide = false, ShowStatusName = false,
|
||
IsCenteredLayout = true, BattleUISize = 80.0, AimhelperDotOpacity = 20.0,
|
||
AimhelperDotScale = 2.0, AutoSupply = false,
|
||
)
|
||
Check(
|
||
"切片 BattleSettings:ApplyBattleSettings 整体替换",
|
||
ReduceBattleSettings(S0, ApplyBattleSettings(Custom)) == Custom
|
||
)
|
||
Check(
|
||
"切片 BattleSettings:ResetSettingsFromStorage 保留状态",
|
||
ReduceBattleSettings(Custom, ResetSettingsFromStorage) == Custom
|
||
)
|
||
Check(
|
||
"切片 BattleSettings:无关动作不改状态",
|
||
ReduceBattleSettings(Custom, AddFrags(1)) == Custom
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, ApplyBattleSettings(Custom))
|
||
Check(
|
||
"根:ApplyBattleSettings 只动 battleSettings",
|
||
Root1.BattleSettings == Custom && Root1.GraphicsSettings == Root0.GraphicsSettings
|
||
)
|
||
val W = RootWiring.ByField("battleSettings")!!
|
||
Check(
|
||
"根:battleSettings 委派到 W0 / X0().esq 且 2 个动作",
|
||
W.SliceClass == "W0" && W.Reducer == "X0().esq" &&
|
||
W.Actions.toSet() == setOf("ApplyBattleSettings", "ResetSettingsFromStorage")
|
||
)
|
||
}
|
||
|
||
// 84 切片 reducer:ContractState(Ont / xv.bti,3 字段;7 个动作)
|
||
run {
|
||
fun Contract(Id: Any?, Progress: Any? = 0, Rewards: List<Any?> = emptyList()) =
|
||
ContractEntry(ContractId = Id, Progress = Progress, Rewards = Rewards)
|
||
|
||
val S0 = ContractStateSlice()
|
||
Check(
|
||
"切片 ContractState:默认值完整",
|
||
S0.ActiveContractId == null && S0.Contracts.isEmpty() &&
|
||
S0.LastBattleProgress == 0
|
||
)
|
||
val S1 = ReduceContractState(S0, SetActiveContract("c1"))
|
||
Check("切片 ContractState:SetActiveContract 写 id", S1.ActiveContractId == "c1")
|
||
val S2 = ReduceContractState(S1, SetContract(listOf(Contract("c1"), Contract("c2"))))
|
||
Check(
|
||
"切片 ContractState:SetContract 按合同 id 合并",
|
||
S2.Contracts.keys == setOf<Any?>("c1", "c2")
|
||
)
|
||
Check(
|
||
"切片 ContractState:UpdateProgress 复制该合同",
|
||
ReduceContractState(S2, UpdateProgress("c1", 5))
|
||
.let { (it.Contracts["c1"] as ContractEntry).Progress == 5 }
|
||
)
|
||
Check(
|
||
"切片 ContractState:SetBattleProgress 写战斗进度",
|
||
ReduceContractState(S2, SetBattleProgress(7)).LastBattleProgress == 7
|
||
)
|
||
val S3 = ReduceContractState(
|
||
ReduceContractState(S2, SetContract(listOf(Contract("c2", 1, listOf("r"))))),
|
||
ClaimContractReward("c2"),
|
||
)
|
||
Check(
|
||
"切片 ContractState:ClaimContractReward 清空该合同奖励",
|
||
(S3.Contracts["c2"] as ContractEntry).Rewards.isEmpty() &&
|
||
(S3.Contracts["c1"] as ContractEntry).Rewards.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ContractState:RemoveCompletedContracts 只留还有奖励的",
|
||
ReduceContractState(
|
||
ReduceContractState(
|
||
S2,
|
||
SetContract(listOf(Contract("c1", 2, listOf("r")))),
|
||
),
|
||
RemoveCompletedContracts,
|
||
).Contracts.keys == setOf<Any?>("c1")
|
||
)
|
||
Check(
|
||
"切片 ContractState:InitBattle 只把战斗进度归零",
|
||
ReduceContractState(S3, InitBattle()).let {
|
||
it.LastBattleProgress == 0 && it.Contracts.keys == setOf<Any?>("c1", "c2")
|
||
}
|
||
)
|
||
Check(
|
||
"切片 ContractState:无关动作不改状态",
|
||
ReduceContractState(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetActiveContract("x"))
|
||
Check(
|
||
"根:SetActiveContract 只动 contractState",
|
||
Root1.ContractState.ActiveContractId == "x" &&
|
||
Root1.BattleSettings == Root0.BattleSettings
|
||
)
|
||
val W = RootWiring.ByField("contractState")!!
|
||
Check(
|
||
"根:contractState 委派到 Ont / xv.bti 且 7 个动作",
|
||
W.SliceClass == "Ont" && W.Reducer == "xv.bti" && W.Actions.size == 7
|
||
)
|
||
}
|
||
|
||
// 85 切片 reducer:TankBuilder(brt / wp.ati,6 字段;4 个动作)
|
||
run {
|
||
val S0 = TankBuilderStateSlice()
|
||
Check(
|
||
"切片 TankBuilder:默认值完整",
|
||
S0.Hulls.isEmpty() && S0.Weapons.isEmpty() && S0.Paints.isEmpty() &&
|
||
S0.PaintInfo.isEmpty() && S0.PartInfo.isEmpty() && S0.Kode.isEmpty()
|
||
)
|
||
val S1 = ReduceTankBuilder(S0, SetKode(listOf("k1")))
|
||
Check("切片 TankBuilder:SetKode 写 kode", S1.Kode == listOf<Any?>("k1"))
|
||
val S2 = ReduceTankBuilder(
|
||
S1,
|
||
SetParts(
|
||
parts = listOf(
|
||
TankBuilderPart("hull1", listOf("w1")),
|
||
TankBuilderPart("hull2", listOf("w2", "w3")),
|
||
),
|
||
paints = listOf("p1"),
|
||
),
|
||
)
|
||
Check(
|
||
"切片 TankBuilder:SetParts 拆出 hulls / weapons / paints",
|
||
S2.Hulls == listOf<Any?>("hull1", "hull2") &&
|
||
S2.Weapons == mapOf<Any?, Any?>(
|
||
0 to listOf("w1"),
|
||
1 to listOf("w2", "w3"),
|
||
) &&
|
||
S2.Paints == listOf<Any?>("p1")
|
||
)
|
||
val S3 = ReduceTankBuilder(
|
||
S2,
|
||
SetPartsInfo(
|
||
partsInfo = listOf(TankBuilderPartInfo("part1", "info1")),
|
||
paintsInfo = listOf(TankBuilderPaintInfo("paint1", "info2")),
|
||
),
|
||
)
|
||
Check(
|
||
"切片 TankBuilder:SetPartsInfo 按各自 id 建索引",
|
||
S3.PartInfo == mapOf<Any?, Any?>("part1" to "info1") &&
|
||
S3.PaintInfo == mapOf<Any?, Any?>("paint1" to "info2")
|
||
)
|
||
Check(
|
||
"切片 TankBuilder:ResetTankBuilder 整片复位",
|
||
ReduceTankBuilder(S3, ResetTankBuilder) == TankBuilderStateSlice()
|
||
)
|
||
Check(
|
||
"切片 TankBuilder:无关动作不改状态",
|
||
ReduceTankBuilder(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetKode(listOf("x")))
|
||
Check(
|
||
"根:SetKode 只动 tankBuilderState",
|
||
Root1.TankBuilderState.Kode == listOf<Any?>("x") &&
|
||
Root1.ContractState == Root0.ContractState
|
||
)
|
||
val W = RootWiring.ByField("tankBuilderState")!!
|
||
Check(
|
||
"根:tankBuilderState 委派到 brt / wp.ati 且 4 个动作",
|
||
W.SliceClass == "brt" && W.Reducer == "wp.ati" &&
|
||
W.Actions.toSet() == setOf(
|
||
"SetKode", "SetParts", "SetPartsInfo", "ResetTankBuilder",
|
||
)
|
||
)
|
||
}
|
||
|
||
// 86 切片 reducer:BattleMessage(FE / vT().rqb,1 字段;7 个动作)
|
||
run {
|
||
val S0 = BattleMessageStateSlice()
|
||
Check("切片 BattleMessage:默认空消息表", S0.Messages.isEmpty())
|
||
val S1 = ReduceBattleMessage(S0, DoAddTextMessage("info", 100L, "hello"))
|
||
Check(
|
||
"切片 BattleMessage:DoAddTextMessage 追加 TextMessage",
|
||
S1.Messages.size == 1 &&
|
||
(S1.Messages[0] as TextMessage).Text == "hello" &&
|
||
(S1.Messages[0] as TextMessage).HideTimeMs == 100L
|
||
)
|
||
val S2 = ReduceBattleMessage(
|
||
ReduceBattleMessage(
|
||
ReduceBattleMessage(S1, DoAddLocalizedMessage("sys", 200L, "KILL", listOf("a"))),
|
||
DoAddLocalizedUserMessage("enemy", "user", 300L, "MSG", "uid1"),
|
||
),
|
||
DoAddLocalizedTeamMessage("team", 400L, "TEAM", "red"),
|
||
)
|
||
Check(
|
||
"切片 BattleMessage:四种消息按各自字段入表",
|
||
S2.Messages.size == 4 &&
|
||
(S2.Messages[1] as LocalizedMessage).Content == "KILL" &&
|
||
(S2.Messages[1] as LocalizedMessage).Arguments == listOf<Any?>("a") &&
|
||
(S2.Messages[2] as LocalizedUserMessage).UserTeamRelation == "enemy" &&
|
||
(S2.Messages[2] as LocalizedUserMessage).UserUid == "uid1" &&
|
||
(S2.Messages[3] as LocalizedTeamMessage).TeamRelation == "red"
|
||
)
|
||
val S3 = ReduceBattleMessage(S2, HideMessage(100L))
|
||
Check(
|
||
"切片 BattleMessage:HideMessage 只把匹配 hideTimeMs 的标隐藏",
|
||
(S3.Messages[0] as TextMessage).IsHidden &&
|
||
!(S3.Messages[1] as LocalizedMessage).IsHidden
|
||
)
|
||
val S4 = ReduceBattleMessage(S3, RemoveMessage(200L))
|
||
Check(
|
||
"切片 BattleMessage:RemoveMessage 删掉匹配的那条",
|
||
S4.Messages.size == 3 &&
|
||
S4.Messages.none { (it as BattleMessageEntry).HideTimeMs == 200L }
|
||
)
|
||
Check(
|
||
"切片 BattleMessage:ResetBattleMessages 整片复位",
|
||
ReduceBattleMessage(S4, ResetBattleMessages) == BattleMessageStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleMessage:无关动作不改状态",
|
||
ReduceBattleMessage(S4, AddFrags(1)) == S4
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, DoAddTextMessage("info", 1L, "x"))
|
||
Check(
|
||
"根:DoAddTextMessage 只动 battleMessage",
|
||
Root1.BattleMessage.Messages.size == 1 &&
|
||
Root1.TankBuilderState == Root0.TankBuilderState
|
||
)
|
||
val W = RootWiring.ByField("battleMessage")!!
|
||
Check(
|
||
"根:battleMessage 委派到 FE / vT().rqb 且 7 个动作",
|
||
W.SliceClass == "FE" && W.Reducer == "vT().rqb" && W.Actions.size == 7
|
||
)
|
||
}
|
||
|
||
// 87 切片 reducer:Maps(OA / Oi.sql,1 字段;AddMap)
|
||
run {
|
||
fun Info(Theme: String): MapInfoCC = MapInfoCC(
|
||
defaultTheme = "summer", enabled = true, mapId = 1L, mapName = "Desert",
|
||
matchmakingMark = true, maxPeople = 16, preview = null, rankLimit = "1-5",
|
||
min = "1", supportedModes = listOf("DM"), theme = Theme,
|
||
)
|
||
|
||
val S0 = MapsStateSlice()
|
||
Check("切片 Maps:默认空表", S0.MapDescriptionsById.isEmpty())
|
||
val S1 = ReduceMaps(S0, AddMap(0L, Info("summer")))
|
||
val Desc1 = S1.MapDescriptionsById[1L] as MapsDescriptionEntry
|
||
Check(
|
||
"切片 Maps:AddMap 新建地图描述并写入模式条目",
|
||
Desc1.Name == "Desert" && Desc1.DefaultTheme == "summer" &&
|
||
Desc1.Themes.keys == setOf<Any?>("summer") &&
|
||
(Desc1.Themes["summer"] as MapsThemeEntry).MapName == "Desert"
|
||
)
|
||
val S2 = ReduceMaps(S1, AddMap(1L, Info("winter")))
|
||
val Desc2 = S2.MapDescriptionsById[1L] as MapsDescriptionEntry
|
||
Check(
|
||
"切片 Maps:同地图新增主题是合并而不是覆盖",
|
||
Desc2.Themes.keys == setOf<Any?>("summer", "winter") &&
|
||
(Desc2.Themes["winter"] as MapsThemeEntry).Theme == "winter"
|
||
)
|
||
Check("切片 Maps:无关动作不改状态", ReduceMaps(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddMap(0L, Info("summer")))
|
||
Check(
|
||
"根:AddMap 只动 maps",
|
||
Root1.Maps.MapDescriptionsById.keys == setOf<Any?>(1L) &&
|
||
Root1.BattleMessage == Root0.BattleMessage
|
||
)
|
||
val W = RootWiring.ByField("maps")!!
|
||
Check(
|
||
"根:maps 委派到 OA / Oi.sql",
|
||
W.SliceClass == "OA" && W.Reducer == "Oi.sql" &&
|
||
W.Actions == listOf("AddMap")
|
||
)
|
||
}
|
||
|
||
// 88 切片 reducer:BattleChat(Q6 / Hf.wtg,6 字段;6 个动作)
|
||
run {
|
||
fun Msg(Id: Int): ChatMessage = ChatMessage(
|
||
addressMode = "", battleLinks = emptyList(), channel = "battle", id = Id,
|
||
links = emptyList(), messageType = "text", sourceUser = "u", ip = "",
|
||
uid = null, userId = "1", targetUser = null, text = "m$Id", timePassedInSec = 0,
|
||
)
|
||
|
||
val S0 = BattleChatStateSlice()
|
||
Check(
|
||
"切片 BattleChat:默认两个频道开、未加载",
|
||
S0.CommonBattleChatEnabled && S0.TeamBattleChatEnabled && !S0.Loaded &&
|
||
S0.Messages.isEmpty() && !S0.IsInputActive && !S0.IsHistoryOpen
|
||
)
|
||
val S1 = ReduceBattleChat(S0, ChatLoaded(false, true))
|
||
Check(
|
||
"切片 BattleChat:ChatLoaded 写频道开关并置 loaded",
|
||
!S1.CommonBattleChatEnabled && S1.TeamBattleChatEnabled && S1.Loaded
|
||
)
|
||
val S2 = ReduceBattleChat(S1, AddMessage(Msg(1)))
|
||
Check(
|
||
"切片 BattleChat:AddMessage 追加消息",
|
||
S2.Messages.size == 1 && (S2.Messages[0] as ChatMessage).id == 1
|
||
)
|
||
var S3 = S2
|
||
for (I in 2..130) S3 = ReduceBattleChat(S3, AddMessage(Msg(I)))
|
||
Check(
|
||
"切片 BattleChat:超过 120 条只保留最后 120",
|
||
S3.Messages.size == 120 &&
|
||
(S3.Messages.last() as ChatMessage).id == 130
|
||
)
|
||
Check(
|
||
"切片 BattleChat:输入框开关两态",
|
||
ReduceBattleChat(S3, OpenBattleChatInput).IsInputActive &&
|
||
!ReduceBattleChat(S3, CloseBattleChatInput).IsInputActive
|
||
)
|
||
Check(
|
||
"切片 BattleChat:ToggleBattleChatHistory 取反",
|
||
ReduceBattleChat(S3, ToggleBattleChatHistory).IsHistoryOpen
|
||
)
|
||
Check(
|
||
"切片 BattleChat:ResetBattleChat 整片复位",
|
||
ReduceBattleChat(S3, ResetBattleChat) == BattleChatStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleChat:无关动作不改状态",
|
||
ReduceBattleChat(S3, AddFrags(1)) == S3
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, OpenBattleChatInput)
|
||
Check(
|
||
"根:OpenBattleChatInput 只动 battleChat",
|
||
Root1.BattleChat.IsInputActive && Root1.Maps == Root0.Maps
|
||
)
|
||
val W = RootWiring.ByField("battleChat")!!
|
||
Check(
|
||
"根:battleChat 委派到 Q6 / Hf.wtg 且 6 个动作",
|
||
W.SliceClass == "Q6" && W.Reducer == "Hf.wtg" &&
|
||
W.Actions.size == 6
|
||
)
|
||
}
|
||
|
||
// 89 切片 reducer:RemoteUserDataStorage($Q / vh.isl,1 字段;8 个动作)
|
||
run {
|
||
val S0 = RemoteUserDataStorageStateSlice()
|
||
Check("切片 RemoteUserDataStorage:默认空缓存", S0.UserIdToData.isEmpty())
|
||
val S1 = ReduceRemoteUserDataStorage(S0, UpdateUids(mapOf("1" to "uid1")))
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UpdateUids 建条目并写 uid",
|
||
(S1.UserIdToData["1"] as RemoteUserData).uid == "uid1" &&
|
||
(S1.UserIdToData["1"] as RemoteUserData).userId == "1"
|
||
)
|
||
val S2 = ReduceRemoteUserDataStorage(S1, UpdateRanks(mapOf("1" to 5)))
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UpdateRanks 就地更新",
|
||
(S2.UserIdToData["1"] as RemoteUserData).rank == 5 &&
|
||
(S2.UserIdToData["1"] as RemoteUserData).uid == "uid1"
|
||
)
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UpdateClanTags / UpdateOnlineStatus",
|
||
(ReduceRemoteUserDataStorage(S2, UpdateClanTags(mapOf("1" to "TAG")))
|
||
.UserIdToData["1"] as RemoteUserData).clanTag == "TAG" &&
|
||
(ReduceRemoteUserDataStorage(S2, UpdateOnlineStatus(mapOf("1" to App.State.OnlineNotifierData("online"))))
|
||
.UserIdToData["1"] as RemoteUserData).onlineStatus == "online"
|
||
)
|
||
val S3 = ReduceRemoteUserDataStorage(
|
||
S2,
|
||
UserEnterBattle(mapOf("1" to BattleNotifierData(true, true, ""))),
|
||
)
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UserEnterBattle 写 battle",
|
||
(S3.UserIdToData["1"] as RemoteUserData).battle is BattleNotifierData
|
||
)
|
||
val S4 = ReduceRemoteUserDataStorage(S3, UserLeaveBattle("1"))
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UserLeaveBattle 保留组标记、清战斗位",
|
||
(S4.UserIdToData["1"] as RemoteUserData).battle ==
|
||
BattleNotifierData(false, true, "")
|
||
)
|
||
Check(
|
||
"切片 RemoteUserDataStorage:UserLeaveGroup 清空 battle",
|
||
(ReduceRemoteUserDataStorage(S4, UserLeaveGroup("1"))
|
||
.UserIdToData["1"] as RemoteUserData).battle == null
|
||
)
|
||
Check(
|
||
"切片 RemoteUserDataStorage:未知用户不新建条目",
|
||
ReduceRemoteUserDataStorage(S0, UserLeaveBattle("9")).UserIdToData.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 RemoteUserDataStorage:无关动作不改状态",
|
||
ReduceRemoteUserDataStorage(S4, AddFrags(1)) == S4
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateUids(mapOf("2" to "u2")))
|
||
Check(
|
||
"根:UpdateUids 只动 remoteUserDataStorage",
|
||
(Root1.RemoteUserDataStorage.UserIdToData["2"] as RemoteUserData).uid == "u2" &&
|
||
Root1.BattleChat == Root0.BattleChat
|
||
)
|
||
val W = RootWiring.ByField("remoteUserDataStorage")!!
|
||
Check(
|
||
"根:remoteUserDataStorage 委派到 \$Q / vh.isl 且 8 个动作",
|
||
W.SliceClass == "\$Q" && W.Reducer == "vh.isl" && W.Actions.size == 8
|
||
)
|
||
}
|
||
|
||
// 90 切片 reducer:Matchmaking(TH / JH().qrx,19 字段;9 个动作)
|
||
run {
|
||
val S0 = MatchmakingStateSlice()
|
||
Check(
|
||
"切片 Matchmaking:默认值完整",
|
||
S0.MatchmakingModeRanks.isEmpty() && S0.SelectedMatchmakingMode == null &&
|
||
S0.DisabledModes.isEmpty() && !S0.MatchmakingAndLoadingBattleInProgress &&
|
||
!S0.StartLoadingBattle && !S0.HolidayEnabled && S0.HolidayTitle == "" &&
|
||
S0.HolidayIcon == null && S0.EventBigTile == null &&
|
||
S0.HolidayDescription == "" && S0.MinRankForProBattle == Int.MAX_VALUE &&
|
||
S0.AvgWaitTimeInSeconds == 0 && S0.Mode == null && S0.MaxUsersInGroup == 3 &&
|
||
!S0.GroupsInEventMode && S0.EventEndDate == null &&
|
||
S0.BorderColorForEventButton == null && !S0.RequestSent &&
|
||
S0.RankIndexToMinGS.isEmpty()
|
||
)
|
||
val S1 = ReduceMatchmaking(S0, Configure(
|
||
modes = listOf(MatchmakingModeRank("dm", 1)),
|
||
disabledModes = listOf("tdm"),
|
||
holidayEnabled = true,
|
||
holidayTitle = "NY",
|
||
holidayIcon = "icon",
|
||
eventBigTile = "tile",
|
||
holidayDescription = "desc",
|
||
minRankForProBattle = 5,
|
||
maxUsersInGroup = 4,
|
||
groupsInEventMode = true,
|
||
eventEndDate = "date",
|
||
borderColorForEventButton = "#fff",
|
||
))
|
||
Check(
|
||
"切片 Matchmaking:Configure 写 12 个字段",
|
||
S1.MatchmakingModeRanks == listOf(MatchmakingModeRank("dm", 1)) &&
|
||
S1.DisabledModes == setOf<Any?>("tdm") && S1.HolidayEnabled &&
|
||
S1.HolidayTitle == "NY" && S1.HolidayIcon == "icon" &&
|
||
S1.EventBigTile == "tile" && S1.HolidayDescription == "desc" &&
|
||
S1.MinRankForProBattle == 5 && S1.MaxUsersInGroup == 4 &&
|
||
S1.GroupsInEventMode && S1.EventEndDate == "date" &&
|
||
S1.BorderColorForEventButton == "#fff"
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:SelectMatchmakingMode 写选中模式",
|
||
ReduceMatchmaking(S1, SelectMatchmakingMode("dm")).SelectedMatchmakingMode == "dm"
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:Play 只置 requestSent",
|
||
ReduceMatchmaking(S1, Play(true, null, null)).RequestSent
|
||
)
|
||
val S2 = ReduceMatchmaking(S1, WasRegistered(42, "dm"))
|
||
Check(
|
||
"切片 Matchmaking:WasRegistered 写等待时间 / 模式并清 requestSent",
|
||
S2.MatchmakingAndLoadingBattleInProgress &&
|
||
S2.AvgWaitTimeInSeconds == 42 && S2.Mode == "dm" && !S2.RequestSent
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:EnterToBattleApply 同时置两个进行中位",
|
||
ReduceMatchmaking(S1, EnterToBattleApply(7L)).let {
|
||
it.MatchmakingAndLoadingBattleInProgress && it.StartLoadingBattle
|
||
}
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:ResetMatchmakingProgress 清三个位",
|
||
ReduceMatchmaking(S2, ResetMatchmakingProgress).let {
|
||
!it.MatchmakingAndLoadingBattleInProgress && !it.StartLoadingBattle &&
|
||
!it.RequestSent
|
||
}
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:读条开关两态",
|
||
ReduceMatchmaking(S1, StartLoadingBattle).StartLoadingBattle &&
|
||
!ReduceMatchmaking(S1, CancelLoadingBattle).StartLoadingBattle
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:SetRankIndexToMinGS 写表",
|
||
ReduceMatchmaking(S1, SetRankIndexToMinGS(mapOf("1" to 100)))
|
||
.RankIndexToMinGS == mapOf("1" to 100)
|
||
)
|
||
Check(
|
||
"切片 Matchmaking:无关动作不改状态",
|
||
ReduceMatchmaking(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, StartLoadingBattle)
|
||
Check(
|
||
"根:StartLoadingBattle 只动 matchmaking",
|
||
Root1.Matchmaking.StartLoadingBattle &&
|
||
Root1.RemoteUserDataStorage == Root0.RemoteUserDataStorage
|
||
)
|
||
val W = RootWiring.ByField("matchmaking")!!
|
||
Check(
|
||
"根:matchmaking 委派到 TH / JH().qrx 且 9 个动作",
|
||
W.SliceClass == "TH" && W.Reducer == "JH().qrx" && W.Actions.size == 9
|
||
)
|
||
}
|
||
|
||
// 91 切片 reducer:BattlesSelect(fA / Mi.vqk,15 字段;20 个动作)
|
||
run {
|
||
fun Params(): BattleCreateParameters = BattleCreateParameters(
|
||
battleMode = "DM", battleModeForMatchmaking = null,
|
||
battleOptions = listOf("VALUABLE"), crossPlayMode = "", formatMode = "fmt",
|
||
limits = "10", timeLimitInSec = "600", mapId = 1L, maxPeopleCount = 8,
|
||
name = "b1", proBattle = true, rankRange = "1-5", min = "1", theme = "2",
|
||
)
|
||
|
||
val S0 = BattlesSelectStateSlice()
|
||
Check("切片 BattlesSelect:默认值完整", !S0.Loaded && S0.Battles.isEmpty())
|
||
val S1 = ReduceBattlesSelect(S0, AddBattle(1L, 99L, Params(), "LOW"))
|
||
val Battle = S1.Battles[1L] as BattleParams
|
||
Check(
|
||
"切片 BattlesSelect:AddBattle 建列表项与四个子表",
|
||
Battle.name == "b1" && Battle.mapId == 1L && Battle.proBattle &&
|
||
Battle.parkourMode == false && S1.Users[1L] == emptyMap<Any?, Any?>() &&
|
||
S1.Score[1L] == emptyMap<Any?, Any?>() && S1.Creators[1L] == 99L
|
||
)
|
||
val S2 = ReduceBattlesSelect(S1, RoundStarted(1L, 60))
|
||
Check(
|
||
"切片 BattlesSelect:RoundStarted 写回合计时并清分数",
|
||
S2.RoundEndTimes[1L] == 60 && S2.Score[1L] == emptyMap<Any?, Any?>()
|
||
)
|
||
val S3 = ReduceBattlesSelect(S2, UpdateTeamScore(1L, "RED", 3))
|
||
Check(
|
||
"切片 BattlesSelect:UpdateTeamScore 写队伍分",
|
||
(S3.Score[1L] as Map<Any?, Any?>)["RED"] == 3
|
||
)
|
||
val S4 = ReduceBattlesSelect(
|
||
S3,
|
||
AddUsers(1L, listOf(BattleInfoUser(0L, 0, false, false, 7L)), "RED"),
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:AddUsers 写用户 / 队伍 / 个人分",
|
||
(S4.Users[1L] as Map<Any?, Any?>).keys == setOf<Any?>(7L) &&
|
||
(S4.UsersTeam[1L] as Map<Any?, Any?>)[7L] == "RED" &&
|
||
S4.UserScore[7L] == 0
|
||
)
|
||
val S5 = ReduceBattlesSelect(
|
||
ReduceBattlesSelect(S4, UpdateUserScore(1L, 7L, 9)),
|
||
UpdateUserSuspicion(1L, 7L, true, false),
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:个人分与可疑标记",
|
||
S5.UserScore[7L] == 9 && S5.UserSuspicion[7L] == true &&
|
||
S5.UserSuspicionByIp[7L] == false
|
||
)
|
||
val S6 = ReduceBattlesSelect(S5, SwapTeam(1L))
|
||
Check(
|
||
"切片 BattlesSelect:SwapTeam 换队并把该用户分数归零",
|
||
(S6.UsersTeam[1L] as Map<Any?, Any?>)[7L] == "BLUE" && S6.UserScore[7L] == 0
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:ChangeName / UpdateSuspicion",
|
||
(ReduceBattlesSelect(S1, ChangeName(1L, "renamed"))
|
||
.Battles[1L] as BattleParams).name == "renamed" &&
|
||
(ReduceBattlesSelect(S1, UpdateSuspicion(1L, "HIGH"))
|
||
.Battles[1L] as BattleParams).suspicionLevel == "HIGH"
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:EnterToBattleInfo / UpdateFund / LimitReached",
|
||
ReduceBattlesSelect(S1, EnterToBattleInfo(1L))
|
||
.AlreadySeeBattleInfo == setOf<Any?>(1L) &&
|
||
ReduceBattlesSelect(S1, UpdateFund(1L, 20)).Fund[1L] == 20 &&
|
||
ReduceBattlesSelect(S1, LimitReached(true)).IsLimitReached
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:InitProBattleRewardLimit / SetReEnterProtectBattleId",
|
||
ReduceBattlesSelect(
|
||
S1,
|
||
InitProBattleRewardLimit(ProBattleRewardLimitData(1, 2, 3, 4)),
|
||
).ProLimit == ProBattleRewardLimitData(1, 2, 3, 4) &&
|
||
ReduceBattlesSelect(S1, SetReEnterProtectBattleId(5L))
|
||
.ReEnterProtectBattleId == 5L
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:RemoveUser 清四个个人表",
|
||
ReduceBattlesSelect(S5, RemoveUser(1L, 7L)).let {
|
||
(it.Users[1L] as Map<Any?, Any?>).isEmpty() &&
|
||
it.UserScore[7L] == null && it.UserSuspicion[7L] == null
|
||
}
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:RemoveBattle 清四个战斗表",
|
||
ReduceBattlesSelect(
|
||
ReduceBattlesSelect(S5, EnterToBattleInfo(1L)),
|
||
RemoveBattle(1L),
|
||
).let {
|
||
it.Battles.isEmpty() && it.Users.isEmpty() && it.Creators.isEmpty() &&
|
||
it.AlreadySeeBattleInfo.isEmpty()
|
||
}
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:RoundFinish 删计时与资金",
|
||
ReduceBattlesSelect(ReduceBattlesSelect(S2, UpdateFund(1L, 5)), RoundFinish(1L))
|
||
.let { it.RoundEndTimes[1L] == null && it.Fund[1L] == null }
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:MarkBattlesLoaded / ResetBattlesSelect",
|
||
ReduceBattlesSelect(S0, MarkBattlesLoaded).Loaded &&
|
||
ReduceBattlesSelect(S5, ResetBattlesSelect) == BattlesSelectStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattlesSelect:无关动作不改状态",
|
||
ReduceBattlesSelect(S5, AddFrags(1)) == S5
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, MarkBattlesLoaded)
|
||
Check(
|
||
"根:MarkBattlesLoaded 只动 battlesSelect",
|
||
Root1.BattlesSelect.Loaded && Root1.Matchmaking == Root0.Matchmaking
|
||
)
|
||
val W = RootWiring.ByField("battlesSelect")!!
|
||
Check(
|
||
"根:battlesSelect 委派到 fA / Mi.vqk 且 20 个动作",
|
||
W.SliceClass == "fA" && W.Reducer == "Mi.vqk" && W.Actions.size == 20
|
||
)
|
||
}
|
||
|
||
// 92 切片 reducer:BattleUsers(uP / sr.hqs,13 字段;18 个动作)
|
||
run {
|
||
fun User(Id: Long): RemoteUserData = RemoteUserData(
|
||
userId = Id, uid = "u$Id", rank = 5, hasPremium = true,
|
||
clanTag = "T", onlineStatus = "online", battle = null,
|
||
)
|
||
val TankInfo = TankInfoCC(1L, null, 2L, 3L, null, 4L)
|
||
val Resistance = TankResistance(10, "fire")
|
||
|
||
val S0 = BattleUsersStateSlice()
|
||
Check("切片 BattleUsers:默认值完整", S0.OnlineUsers.isEmpty() && S0.Stats.isEmpty())
|
||
val S1 = ReduceBattleUsers(S0, AddBattleUser(User(1L), "RED"))
|
||
Check(
|
||
"切片 BattleUsers:AddBattleUser 写七张表",
|
||
S1.OnlineUsers == setOf<Any?>(1L) && S1.Uids[1L] == "u1" &&
|
||
S1.Ranks[1L] == 5 && S1.ClanTags[1L] == "T" &&
|
||
S1.UsersPremium[1L] == true && S1.Teams[1L] == "RED" &&
|
||
S1.Stats[1L] == User(1L)
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:AddBattleUsers 批量加入",
|
||
ReduceBattleUsers(S1, AddBattleUsers(listOf(User(2L), User(3L)), "BLUE"))
|
||
.OnlineUsers == setOf<Any?>(1L, 2L, 3L)
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:UpdateUserRank / UpdateUsersClanTag",
|
||
ReduceBattleUsers(S1, UpdateUserRank(1L, 9)).Ranks[1L] == 9 &&
|
||
ReduceBattleUsers(S1, UpdateUsersClanTag(mapOf(1L to "X")))
|
||
.ClanTags[1L] == "X"
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:两种 stats 写入",
|
||
ReduceBattleUsers(S1, UpdateUserScoreKillsAndDeaths(UserStat(0, 0, 0, 0, 4L)))
|
||
.Stats[4L] == UserStat(0, 0, 0, 0, 4L) &&
|
||
ReduceBattleUsers(S1, UpdateUsersScoreKillsAndDeaths(listOf(UserStat(0, 0, 0, 0, 5L))))
|
||
.Stats[5L] == UserStat(0, 0, 0, 0, 5L)
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:SetGearScore / SetTankResistance",
|
||
ReduceBattleUsers(S1, SetGearScore(1L, 100)).GearScores[1L] == 100 &&
|
||
ReduceBattleUsers(S1, SetTankResistance(1L, listOf(Resistance)))
|
||
.TankResistance[1L] == listOf(Resistance)
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:RemoveOnlineUser / SetUserPremium",
|
||
ReduceBattleUsers(S1, RemoveOnlineUser(1L)).OnlineUsers.isEmpty() &&
|
||
ReduceBattleUsers(S1, SetUserPremium(1L, false)).UsersPremium[1L] == false
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:可疑用户开关与整体替换",
|
||
ReduceBattleUsers(S1, UpdateUserSuspiciousState(1L, true, false))
|
||
.let { it.SuspiciousUsers == setOf<Any?>(1L) && it.SuspiciousByIpUsers.isEmpty() } &&
|
||
ReduceBattleUsers(S1, InitSuspiciousState(listOf(2L), listOf(3L)))
|
||
.let {
|
||
it.SuspiciousUsers == setOf<Any?>(2L) &&
|
||
it.SuspiciousByIpUsers == setOf<Any?>(3L)
|
||
}
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:等级两臂",
|
||
ReduceBattleUsers(S1, InitUserLevels(mapOf(1L to 3))).TankLevels[1L] == 3 &&
|
||
ReduceBattleUsers(S1, UpdateUserLevel(1L, 4)).TankLevels[1L] == 4
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:坦克信息写入与移除",
|
||
ReduceBattleUsers(S1, SetTankInfo(1L, TankInfo)).TankInfo[1L] == TankInfo &&
|
||
ReduceBattleUsers(S1, SetTankInfo(1L, TankInfo)).let {
|
||
ReduceBattleUsers(it, RemoveTankInfo(1L)).TankInfo.isEmpty()
|
||
}
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:枚举迁移是 no-op、复位清空",
|
||
ReduceBattleUsers(S1, MigrateBattleUserTeams) == S1 &&
|
||
ReduceBattleUsers(S1, ResetBattleUsers) == BattleUsersStateSlice()
|
||
)
|
||
Check(
|
||
"切片 BattleUsers:无关动作不改状态",
|
||
ReduceBattleUsers(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddBattleUser(User(7L), "RED"))
|
||
Check(
|
||
"根:AddBattleUser 只动 battleUsers",
|
||
Root1.BattleUsers.OnlineUsers == setOf<Any?>(7L) &&
|
||
Root1.BattlesSelect == Root0.BattlesSelect
|
||
)
|
||
val W = RootWiring.ByField("battleUsers")!!
|
||
Check(
|
||
"根:battleUsers 委派到 uP / sr.hqs 且 18 个动作",
|
||
W.SliceClass == "uP" && W.Reducer == "sr.hqs" && W.Actions.size == 18
|
||
)
|
||
}
|
||
|
||
// 93 切片 reducer:Challenges(CZ / nh.hsj,4 字段;9 个动作)
|
||
run {
|
||
fun ChallengeOf(Version: Any?): Challenge = Challenge(
|
||
version = Version, endTime = null, tiers = emptyList<Any?>(),
|
||
battlePassFlavor = 1, mainPrizeImage = 0L, background = 0L,
|
||
tierReward = null, takenFreeRewardIndex = 0,
|
||
takenBattlePassRewardIndex = 0,
|
||
)
|
||
|
||
val S0 = ChallengesStateSlice()
|
||
Check("切片 Challenges:默认值完整", S0.Stars.isEmpty() && S0.Challenge.isEmpty())
|
||
val S1 = ReduceChallenges(S0, UpdateStars(mapOf("a" to 3)))
|
||
Check("切片 Challenges:UpdateStars 整体替换", S1.Stars == mapOf("a" to 3))
|
||
val S2 = ReduceChallenges(S1, SetChallenge(ChallengeOf(1)))
|
||
Check(
|
||
"切片 Challenges:SetChallenge 按 version 建索引",
|
||
(S2.Challenge[1] as Challenge).version == 1
|
||
)
|
||
Check(
|
||
"切片 Challenges:RemoveChallenge 按 version 删",
|
||
ReduceChallenges(S2, RemoveChallenge(1)).Challenge.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 Challenges:SetBattlePassPackages 写包表",
|
||
ReduceChallenges(S2, SetBattlePassPackages(mapOf("x" to 5L)))
|
||
.BattlePassPackages == mapOf("x" to 5L)
|
||
)
|
||
val S3 = ReduceChallenges(S2, SetBattlePass(1))
|
||
Check(
|
||
"切片 Challenges:SetBattlePass 追加战令条目",
|
||
S3.BattlePasses.size == 1 && S3.BattlePasses.first().version == 1
|
||
)
|
||
val S4 = ReduceChallenges(
|
||
ReduceChallenges(S3, SetTakenFreeRewardIndex(1, 2)),
|
||
SetTakenBattlePassRewardIndex(1, 4),
|
||
)
|
||
Check(
|
||
"切片 Challenges:两个已领取索引按 flavor 命中",
|
||
S4.BattlePasses.first().takenFreeRewardIndex == 2 &&
|
||
S4.BattlePasses.first().takenBattlePassRewardIndex == 4
|
||
)
|
||
Check(
|
||
"切片 Challenges:SetRewardForDialog / RefreshBattlePasses 是 no-op",
|
||
ReduceChallenges(S4, SetRewardForDialog("1", 1)) == S4 &&
|
||
ReduceChallenges(S4, RefreshBattlePasses) == S4
|
||
)
|
||
Check(
|
||
"切片 Challenges:无关动作不改状态",
|
||
ReduceChallenges(S4, AddFrags(1)) == S4
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, UpdateStars(mapOf("b" to 1)))
|
||
Check(
|
||
"根:UpdateStars 只动 challenges",
|
||
Root1.Challenges.Stars == mapOf("b" to 1) &&
|
||
Root1.BattleUsers == Root0.BattleUsers
|
||
)
|
||
val W = RootWiring.ByField("challenges")!!
|
||
Check(
|
||
"根:challenges 委派到 CZ / nh.hsj 且 9 个动作",
|
||
W.SliceClass == "CZ" && W.Reducer == "nh.hsj" && W.Actions.size == 9
|
||
)
|
||
}
|
||
|
||
// 94 切片 reducer:ClansStorage(lO / me.vr1,3 字段;13 个动作)
|
||
run {
|
||
fun Clan(Id: Any? = 1L): ClanInfo = ClanInfo(
|
||
id = Id, name = "n", tag = "t", description = "d",
|
||
minRankIndexForAddClan = 1, incomingRequestEnabled = true,
|
||
creatorId = 9L, createTime = 0L, users = emptySet<Any?>(),
|
||
gameObject = null, maxCharactersDescription = 100, blocked = false,
|
||
reasonForBlocking = "", self = false, maxMembers = 10,
|
||
newUsers = emptySet<Any?>(),
|
||
)
|
||
|
||
val S0 = ClanStorageStateSlice()
|
||
Check("切片 ClansStorage:默认值完整", S0.Clans.isEmpty() && S0.ForeignClan == null)
|
||
val S1 = ReduceClanStorage(S0, LoadClanIntoStorage(Clan()))
|
||
Check(
|
||
"切片 ClansStorage:ClanLoaded 按 id 入表",
|
||
(S1.Clans[1L] as ClanInfo).name == "n"
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:MinimalRankChanged / RecruitmentChanged 就地 copy",
|
||
(ReduceClanStorage(S1, MinimalRankChanged(1L, 5)).Clans[1L] as ClanInfo)
|
||
.minRankIndexForAddClan == 5 &&
|
||
!(ReduceClanStorage(S1, RecruitmentChanged(1L, false)).Clans[1L] as ClanInfo)
|
||
.incomingRequestEnabled
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:成员与待审用户的增删",
|
||
(ReduceClanStorage(
|
||
ReduceClanStorage(S1, AddClanUser(1L, 7L)),
|
||
AddUsersToNewAccepted(1, listOf(8L)),
|
||
).Clans[1L] as ClanInfo).let {
|
||
it.users == setOf<Any?>(7L) && it.newUsers == setOf<Any?>(8L)
|
||
} &&
|
||
(ReduceClanStorage(
|
||
ReduceClanStorage(S1, AddClanUser(1L, 7L)),
|
||
RemoveClanUser(1L, 7L),
|
||
).Clans[1L] as ClanInfo).users == emptySet<Any?>()
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:UpdateClan 用 null 保留原值",
|
||
(ReduceClanStorage(S1, UpdateClan(1L, "n2", null, null))
|
||
.Clans[1L] as ClanInfo).let { it.name == "n2" && it.tag == "t" }
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:BuyAdditionalPlace 让 maxMembers +1",
|
||
(ReduceClanStorage(S1, BuyAdditionalPlace(1L)).Clans[1L] as ClanInfo)
|
||
.maxMembers == 11
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:外战队三臂",
|
||
ReduceClanStorage(S1, ForeignClanLoaded(Clan(2L))).ForeignClan != null &&
|
||
ReduceClanStorage(S1, ClearForeignClan).ForeignClan == null &&
|
||
ReduceClanStorage(S1, ShowForeignClan(2L)).ForeignClanId == 2L
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:ClanUnloaded 删表",
|
||
ReduceClanStorage(S1, ClanUnloaded(1L)).Clans.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ClansStorage:无关动作不改状态",
|
||
ReduceClanStorage(S1, AddFrags(1)) == S1
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, LoadClanIntoStorage(Clan()))
|
||
Check(
|
||
"根:ClanLoaded 只动 clansStorage",
|
||
Root1.ClansStorage.Clans.keys == setOf<Any?>(1L) &&
|
||
Root1.Challenges == Root0.Challenges
|
||
)
|
||
val W = RootWiring.ByField("clansStorage")!!
|
||
Check(
|
||
"根:clansStorage 委派到 lO / me.vr1 且 13 个动作",
|
||
W.SliceClass == "lO" && W.Reducer == "me.vr1" && W.Actions.size == 13
|
||
)
|
||
}
|
||
|
||
// 95 切片 reducer:Clan(jN / we.hr0,36 字段;43 个动作)
|
||
run {
|
||
val S0 = ClanStateSlice()
|
||
Check(
|
||
"切片 Clan:默认值完整",
|
||
S0.CurrentClanId == null && !S0.HasClan && !S0.HasLicense &&
|
||
S0.ClanAcceptedMembers.isEmpty()
|
||
)
|
||
val S1 = ReduceClan(S0, SetMinRankForCreate(5))
|
||
Check("切片 Clan:SetMinRankForCreate 写最小等级", S1.MinRankForCreateClan == 5)
|
||
val S2 = ReduceClan(S1, AddUsersToAccepted(listOf(1L, 2L)))
|
||
Check(
|
||
"切片 Clan:AddUsersToAccepted 批量入集合",
|
||
S2.ClanAcceptedMembers == setOf<Any?>(1L, 2L)
|
||
)
|
||
Check(
|
||
"切片 Clan:RemoveUserFromAccepted 删单值",
|
||
ReduceClan(S2, RemoveUserFromAccepted(1L))
|
||
.ClanAcceptedMembers == setOf<Any?>(2L)
|
||
)
|
||
val Info = ClanInfo(
|
||
id = 7L, name = "n", tag = "t", description = "d",
|
||
minRankIndexForAddClan = 1, incomingRequestEnabled = true,
|
||
creatorId = 9L, createTime = 0L, users = emptySet<Any?>(),
|
||
gameObject = null, maxCharactersDescription = 100, blocked = false,
|
||
reasonForBlocking = "", self = false, maxMembers = 10,
|
||
newUsers = emptySet<Any?>(),
|
||
)
|
||
val S3 = ReduceClan(S2, ClanInfoLoaded(Info))
|
||
Check(
|
||
"切片 Clan:ClanInfoLoaded 写 6 个身份字段",
|
||
S3.CurrentClanId == 7L && S3.CreatorId == 9L && S3.Name == "n" &&
|
||
S3.Tag == "t" && S3.Description == "d"
|
||
)
|
||
Check(
|
||
"切片 Clan:人员 / 战队列表四臂",
|
||
ReduceClan(S3, IncomingAdded(listOf(3L))).IncomingUsers == setOf<Any?>(3L) &&
|
||
ReduceClan(S3, OutgoingAdded(listOf(4L))).OutgoingUsers == setOf<Any?>(4L) &&
|
||
ReduceClan(S3, AddOutgoingClan(11L)).OutgoingClans == setOf<Any?>(11L) &&
|
||
ReduceClan(S3, AddNewIncomingClan(listOf(12L)))
|
||
.NewIncomingClans == setOf<Any?>(12L)
|
||
)
|
||
Check(
|
||
"切片 Clan:中间态三臂",
|
||
ReduceClan(S3, UpdateIntermediateMinRank(2)).IntermediateMinRankIndex == 2 &&
|
||
ReduceClan(S3, UpdateIntermediateRequests(true))
|
||
.IntermediateRequestsEnabled == true &&
|
||
ReduceClan(S3, UpdateIntermediateDescription("x"))
|
||
.IntermediateDescription == "x"
|
||
)
|
||
Check(
|
||
"切片 Clan:校验状态写 / 复位",
|
||
ReduceClan(S3, UpdateValidation("bad")).ClanUserValidation == "bad" &&
|
||
ReduceClan(ReduceClan(S3, UpdateValidation("bad")), ResetClanUserValidation)
|
||
.ClanUserValidation == null &&
|
||
ReduceClan(ReduceClan(S3, UpdateClanNameValidation("bad")),
|
||
ResetClanNameTagValidation).ClanNameNameValidation == null
|
||
)
|
||
Check(
|
||
"切片 Clan:HasClan=false 整片复位、true 只置位",
|
||
ReduceClan(
|
||
ReduceClan(S3, UpdateClanLicense(true)),
|
||
UpdateHasClan(false),
|
||
) == ClanStateSlice() &&
|
||
ReduceClan(S3, UpdateHasClan(true)).HasClan
|
||
)
|
||
Check(
|
||
"切片 Clan:封禁 / 弹窗 / 改名价格 / 入队限制",
|
||
ReduceClan(S3, ClanBanned("why")).let { it.Banned && it.BanReason == "why" } &&
|
||
ReduceClan(S3, ClanModalVisible(true)).ClanModalVisible &&
|
||
ReduceClan(S3, SetClanEditPrice(ClanEditPriceCC(1, 2, null, 3, 4, 5)))
|
||
.ClanEditPrice == ClanEditPriceCC(1, 2, null, 3, 4, 5) &&
|
||
ReduceClan(S3, UpdateTimeToJoinRestriction(60))
|
||
.RestrictionToJoinInClan == 60
|
||
)
|
||
Check(
|
||
"切片 Clan:通知计数 +1 / -1 / 直接写",
|
||
ReduceClan(S3, IncrementClanNotifications).NotificationsCount == 1 &&
|
||
ReduceClan(ReduceClan(S3, IncrementClanNotifications),
|
||
DecrementClanNotifications).NotificationsCount == 0 &&
|
||
ReduceClan(S3, SetClanNotificationsCount(4)).NotificationsCount == 4
|
||
)
|
||
Check(
|
||
"切片 Clan:三个通知徽标与两个已读动作",
|
||
ReduceClan(S3, NewIncomingUserNotification(2))
|
||
.IncomingUserNotificationCount == 2 &&
|
||
ReduceClan(ReduceClan(S3, NewAcceptedUserNotification(3)),
|
||
AcceptedUserViewed(1L)).AcceptedUserNotificationCount == 2 &&
|
||
ReduceClan(ReduceClan(S3, NewIncomingUserNotification(2)),
|
||
IncomingUserViewed(1L)).IncomingUserNotificationCount == 1 &&
|
||
ReduceClan(S3, NewClanInviteNotification(1)).ClanInviteNotificationCount == 1
|
||
)
|
||
Check("切片 Clan:无关动作不改状态", ReduceClan(S3, AddFrags(1)) == S3)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, SetMinRankForCreate(3))
|
||
Check(
|
||
"根:SetMinRankForCreate 只动 clan",
|
||
Root1.Clan.MinRankForCreateClan == 3 && Root1.ClansStorage == Root0.ClansStorage
|
||
)
|
||
val W = RootWiring.ByField("clan")!!
|
||
Check(
|
||
"根:clan 委派到 jN / we.hr0 且 43 个动作",
|
||
W.SliceClass == "jN" && W.Reducer == "we.hr0" && W.Actions.size == 43
|
||
)
|
||
}
|
||
|
||
// 96 切片 reducer:ChatState(LP / $r.wqv,16 字段;24 个动作)
|
||
run {
|
||
fun Msg(Id: Int, Source: String, Target: String? = null): ChatMessage = ChatMessage(
|
||
addressMode = "", battleLinks = emptyList(), channel = "general", id = Id,
|
||
links = emptyList(), messageType = "text", sourceUser = Source, ip = "",
|
||
uid = null, userId = "1", targetUser = Target, text = "m", timePassedInSec = 0,
|
||
)
|
||
|
||
val S0 = ChatStateSlice()
|
||
Check(
|
||
"切片 ChatState:默认值完整",
|
||
!S0.Open && S0.CurrentChannel == null && S0.ChatMessage == null &&
|
||
S0.SelectedUid == "" && S0.MessagesByChannel.isEmpty() &&
|
||
!S0.IsShowKeyboard && !S0.IsNeedToHideKeyboard && !S0.IsNeedScroll &&
|
||
S0.IsFirstChannelMessagesLoad && S0.IgnoredUserIds.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ChatState:开关 / 频道 / 配置",
|
||
ReduceChat(S0, OpenChat).Open && !ReduceChat(S0, CloseChat).Open &&
|
||
ReduceChat(S0, ChangeChannel("general")).CurrentChannel == "general" &&
|
||
ReduceChat(S0, SetChatConfig(null)).Config == null
|
||
)
|
||
val S1 = ReduceChat(S0, SaveChatMessage(Msg(1, "u1", "u2")))
|
||
Check(
|
||
"切片 ChatState:SaveChatMessage 写消息并选中目标用户",
|
||
(S1.ChatMessage as ChatMessage).id == 1 && S1.SelectedUid == "u2"
|
||
)
|
||
val S2 = ReduceChat(S0, SetMessages(mapOf("general" to listOf(Msg(1, "u1")))))
|
||
Check(
|
||
"切片 ChatState:SetMessages 整体替换频道表",
|
||
(S2.MessagesByChannel["general"] as List<Any?>).size == 1
|
||
)
|
||
Check(
|
||
"切片 ChatState:CleanUsersMessage 按 sourceUser 过滤",
|
||
(ReduceChat(S2, CleanUsersMessage("u1"))
|
||
.MessagesByChannel["general"] as List<Any?>).isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ChatState:右键菜单设置 / 清除",
|
||
ReduceChat(S0, ShowContextMenu("ctx")).ContextMenuData == "ctx" &&
|
||
ReduceChat(S0, ClearChatContextMenu).ContextMenuData == null
|
||
)
|
||
Check(
|
||
"切片 ChatState:键盘 / 滚动开关",
|
||
ReduceChat(S0, ShowChatKeyboard).let { it.IsShowKeyboard && !it.IsNeedToHideKeyboard } &&
|
||
!ReduceChat(S0, HideChatKeyboard).IsShowKeyboard &&
|
||
ReduceChat(S0, RequireHideChatKeyboard).IsNeedToHideKeyboard &&
|
||
!ReduceChat(S0, CancelHideChatKeyboard).IsNeedToHideKeyboard &&
|
||
ReduceChat(S0, EnableChatScroll).IsNeedScroll &&
|
||
!ReduceChat(S0, DisableChatScroll).IsNeedScroll
|
||
)
|
||
Check(
|
||
"切片 ChatState:SendMessageToServer 置两个待处理位",
|
||
ReduceChat(S0, SendMessageToServer("t", "mode", "m")).let {
|
||
it.IsNeedToHideKeyboard && it.IsNeedScroll
|
||
}
|
||
)
|
||
Check(
|
||
"切片 ChatState:速率参数 / 选中 uid 转小写 / 首屏标记",
|
||
ReduceChat(S0, UpdateTypingSpeedAntifloodParams(1, 2)).let {
|
||
it.SymbolCost == 1 && it.EnterCost == 2
|
||
} &&
|
||
ReduceChat(S0, SetSelectedUid("ABC")).SelectedUid == "abc" &&
|
||
!ReduceChat(S0, MarkFirstChannelMessagesLoaded).IsFirstChannelMessagesLoad
|
||
)
|
||
Check(
|
||
"切片 ChatState:屏蔽 / 取消屏蔽 / 重载清空",
|
||
ReduceChat(S0, IgnoreUserMessages("u1")).IgnoredUserIds == setOf<Any?>("u1") &&
|
||
ReduceChat(ReduceChat(S0, IgnoreUserMessages("u1")), ShowUserMessages("u1"))
|
||
.IgnoredUserIds.isEmpty() &&
|
||
ReduceChat(ReduceChat(S0, IgnoreUserMessages("u1")), ResetSettingsFromStorage)
|
||
.IgnoredUserIds.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ChatState:UpdateUsers 是 no-op、ResetChat 复位",
|
||
ReduceChat(S2, UpdateUsers(listOf("u1"))) == S2 &&
|
||
ReduceChat(S2, ResetChat) == ChatStateSlice()
|
||
)
|
||
Check("切片 ChatState:无关动作不改状态", ReduceChat(S2, AddFrags(1)) == S2)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, OpenChat)
|
||
Check(
|
||
"根:OpenChat 只动 chatState",
|
||
Root1.ChatState.Open && Root1.Clan == Root0.Clan
|
||
)
|
||
val W = RootWiring.ByField("chatState")!!
|
||
Check(
|
||
"根:chatState 委派到 LP / \$r.wqv 且 24 个动作",
|
||
W.SliceClass == "LP" && W.Reducer == "\$r.wqv" && W.Actions.size == 24
|
||
)
|
||
}
|
||
|
||
// 97 切片 reducer:ItemNotifications(_1 / A1().dso,1 字段;6 个动作)
|
||
run {
|
||
fun Item(Id: Long, End: Any? = null): ItemNotificationData = ItemNotificationData(
|
||
section = "shop", itemId = Id, type = "new", parentId = null,
|
||
startTime = null, endTime = End,
|
||
)
|
||
|
||
val S0 = ItemNotificationsStateSlice()
|
||
Check("切片 ItemNotifications:默认空集合", S0.Notifications.isEmpty())
|
||
val S1 = ReduceItemNotifications(S0, AddItemNotification("shop", 1L, "new", null, null, null))
|
||
Check(
|
||
"切片 ItemNotifications:AddItemNotification 构造并入集合",
|
||
S1.Notifications.size == 1 &&
|
||
(S1.Notifications.first() as ItemNotificationData).itemId == 1L
|
||
)
|
||
val S2 = ReduceItemNotifications(S1, AddItemNotifications(listOf(Item(2L), Item(3L))))
|
||
Check(
|
||
"切片 ItemNotifications:AddItemNotifications 批量加入",
|
||
(S2.Notifications.map { (it as ItemNotificationData).itemId }).toSet() ==
|
||
setOf(1L, 2L, 3L)
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:RemoveItemNotification 按 section+itemId 删",
|
||
ReduceItemNotifications(
|
||
S2,
|
||
RemoveItemNotification("shop", 2L, null, null, null, true),
|
||
).Notifications.size == 2
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:excludeType 命中时保留",
|
||
ReduceItemNotifications(
|
||
S2,
|
||
RemoveItemNotification("shop", null, null, null, "new", true),
|
||
).Notifications.size == 3
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:UpdateItemNotification 对匹配项应用 handler",
|
||
ReduceItemNotifications(
|
||
S2,
|
||
UpdateItemNotification("shop", 1L, null, null, null, { Item: ItemNotificationData ->
|
||
Item.copy(section = "updated")
|
||
}),
|
||
).let { Updated ->
|
||
(Updated.Notifications.first { (it as ItemNotificationData).itemId == 1L }
|
||
as ItemNotificationData).section == "updated"
|
||
}
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:PersistItemNotifications 是 no-op",
|
||
ReduceItemNotifications(S2, PersistItemNotifications) == S2
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:过期项在重载时被过滤",
|
||
ReduceItemNotifications(
|
||
ReduceItemNotifications(S0, AddItemNotification("shop", 9L, "new", null, null, 1L)),
|
||
ResetSettingsFromStorage,
|
||
).Notifications.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 ItemNotifications:无关动作不改状态",
|
||
ReduceItemNotifications(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, AddItemNotification("shop", 1L, "new", null, null, null))
|
||
Check(
|
||
"根:AddItemNotification 只动 itemNotifications",
|
||
Root1.ItemNotifications.Notifications.size == 1 &&
|
||
Root1.ChatState == Root0.ChatState
|
||
)
|
||
val W = RootWiring.ByField("itemNotifications")!!
|
||
Check(
|
||
"根:itemNotifications 委派到 _1 / A1().dso 且 6 个动作",
|
||
W.SliceClass == "_1" && W.Reducer == "A1().dso" && W.Actions.size == 6
|
||
)
|
||
}
|
||
|
||
// 98 切片 reducer:MatchmakingGroup(rW / NW().is0,4 字段;13 个动作)
|
||
run {
|
||
fun Member(Id: Any?, Ready: Boolean = false): MatchmakingGroupMember =
|
||
MatchmakingGroupMember(UserId = Id, IsReady = Ready)
|
||
|
||
val S0 = MatchmakingGroupStateSlice()
|
||
Check(
|
||
"切片 MatchmakingGroup:默认值完整",
|
||
S0.Members.isEmpty() && S0.AllowedFromFriends.isEmpty() &&
|
||
S0.PendingInvitationsUsers.isEmpty() &&
|
||
S0.SelectedPlayersSendInvite.isEmpty()
|
||
)
|
||
val S1 = ReduceMatchmakingGroup(S0, InitMatchmakingGroup(listOf(Member(1L))))
|
||
Check("切片 MatchmakingGroup:InitMatchmakingGroup 整体替换", S1.Members.size == 1)
|
||
val S2 = ReduceMatchmakingGroup(S1, AddGroupMember(Member(2L)))
|
||
Check(
|
||
"切片 MatchmakingGroup:AddGroupMember 追加组员",
|
||
S2.Members.size == 2
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:AddGroupMember 按 userId 去重",
|
||
ReduceMatchmakingGroup(S2, AddGroupMember(Member(2L, true))).let {
|
||
it.Members.size == 2 &&
|
||
(it.Members.last() as MatchmakingGroupMember).IsReady
|
||
}
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:UserRemoved 删组员",
|
||
ReduceMatchmakingGroup(S2, UserRemoved(1L)).Members.size == 1
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:邀请 / 接受 / 拒绝",
|
||
ReduceMatchmakingGroup(S2, Invite(3L))
|
||
.PendingInvitationsUsers == setOf<Any?>(3L) &&
|
||
ReduceMatchmakingGroup(
|
||
ReduceMatchmakingGroup(S2, Invite(3L)),
|
||
AcceptedInviteToGroup(3L),
|
||
).let {
|
||
it.PendingInvitationsUsers.isEmpty() &&
|
||
it.Members.any { M -> (M as MatchmakingGroupMember).UserId == 3L }
|
||
} &&
|
||
ReduceMatchmakingGroup(
|
||
ReduceMatchmakingGroup(S2, Invite(4L)),
|
||
RejectedInviteToGroup(4L, "no"),
|
||
).PendingInvitationsUsers.isEmpty()
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:UpdateUserReadiness 命中 copy",
|
||
(ReduceMatchmakingGroup(S2, UpdateUserReadiness(1L, true))
|
||
.Members.first() as MatchmakingGroupMember).IsReady
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:UpdateMountItems 按 userId 写挂件",
|
||
(ReduceMatchmakingGroup(S2, UpdateMountItems(mapOf<Any?, Any?>(1L to "mount")))
|
||
.Members.first() as MatchmakingGroupMember).MountItems == "mount"
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:选中列表三臂",
|
||
ReduceMatchmakingGroup(S2, AddUserToSelected(5L))
|
||
.SelectedPlayersSendInvite == setOf<Any?>(5L) &&
|
||
ReduceMatchmakingGroup(
|
||
ReduceMatchmakingGroup(S2, AddUserToSelected(5L)),
|
||
RemoveUserToSelected(5L),
|
||
).SelectedPlayersSendInvite.isEmpty() &&
|
||
ReduceMatchmakingGroup(S2, SetSelectedUsers(listOf(6L, 7L)))
|
||
.SelectedPlayersSendInvite == setOf<Any?>(6L, 7L)
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:SetAllowedFromFriends / Reset",
|
||
ReduceMatchmakingGroup(
|
||
ReduceMatchmakingGroup(S2, SetAllowedFromFriends(listOf(1L))),
|
||
ResetMatchmakingGroup,
|
||
).let { it.AllowedFromFriends == setOf<Any?>(1L) && it.Members.isEmpty() }
|
||
)
|
||
Check(
|
||
"切片 MatchmakingGroup:无关动作不改状态",
|
||
ReduceMatchmakingGroup(S2, AddFrags(1)) == S2
|
||
)
|
||
|
||
val Root0 = RootState()
|
||
val Root1 = ReduceRoot(Root0, Invite(9L))
|
||
Check(
|
||
"根:Invite 只动 matchmakingGroup",
|
||
Root1.MatchmakingGroup.PendingInvitationsUsers == setOf<Any?>(9L) &&
|
||
Root1.ItemNotifications == Root0.ItemNotifications
|
||
)
|
||
val W = RootWiring.ByField("matchmakingGroup")!!
|
||
Check(
|
||
"根:matchmakingGroup 委派到 rW / NW().is0 且 13 个动作",
|
||
W.SliceClass == "rW" && W.Reducer == "NW().is0" && W.Actions.size == 13
|
||
)
|
||
}
|
||
|
||
val Failed = Results.filter { !it.second }
|
||
println("TankiOffline 状态层自检:${Results.size} 项")
|
||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||
println(if (Failed.isEmpty()) "结果:通过" else "结果:失败 ${Failed.size} 项")
|
||
ExitWith(if (Failed.isEmpty()) 0 else 1)
|
||
}
|
||
}
|