From cd680708bd6bb737fecac94e9270cd4c5e269ed6 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 23 Oct 2026 01:24:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(state):=20=E8=BF=81=E7=A7=BB=20home=20?= =?UTF-8?q?=E4=B8=8E=20localBattleUserState=20=E4=B8=A4=E4=B8=AA=E5=86=85?= =?UTF-8?q?=E8=81=94=E5=88=87=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/KtRebuild_Plan.md | 10 +- 3DTank/names/root_wiring.json | 10 +- 3DTank/tools/gen_root_wiring.js | 8 +- .../kotlin/App/State/Reducers/HomeReducer.kt | 30 +++++ .../State/Reducers/LocalBattleUserReducer.kt | 38 ++++++ .../jsMain/kotlin/App/State/RootReducer.kt | 8 +- .../src/jsMain/kotlin/App/State/RootState.kt | 10 +- .../src/jsMain/kotlin/App/State/RootWiring.kt | 2 +- .../app/src/jsMain/kotlin/App/StateCheck.kt | 113 +++++++++++++++--- 9 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/HomeReducer.kt create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/LocalBattleUserReducer.kt diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index a9e9178..88224f2 100644 --- a/3DTank/docs/KtRebuild_Plan.md +++ b/3DTank/docs/KtRebuild_Plan.md @@ -143,13 +143,15 @@ `names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。 - 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。 - 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。 - - 已迁移切片 **3/89**: + - 已迁移切片 **5/89**: - `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`) - `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义) - `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID) - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述三片,其余 **86 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 3/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **61 项全绿**,含 89 槽位完整性、三片一次归约互不串片、未迁移字段透传。 + - `home` → `s1` / 根 reducer `(inline@home)` 分支(`HomeReducer.kt`;`ShopDiscountNotification`) + - `localBattleUserState` → `lE` / `(inline@localBattleUserState)` 分支(`LocalBattleUserReducer.kt`) + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述五片,其余 **84 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 5/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **78 项全绿**,含 89 槽位完整性、五片一次归约互不串片、未迁移字段透传。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); `launchParams` 是纯透传(`(pass-through)`,无 reducer)。 diff --git a/3DTank/names/root_wiring.json b/3DTank/names/root_wiring.json index 68ad891..4552e0e 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-22T17:18:31.765Z", + "generatedAt": "2026-10-22T17:21:56.934Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -9,7 +9,9 @@ "Migrated": [ "inviteState", "loginByPassword", - "twitchState" + "twitchState", + "home", + "localBattleUserState" ], "AnonymousActions": [ { @@ -1122,7 +1124,7 @@ "ShowShopDiscountNotification" ], "Default": "new s1()", - "Migrated": false + "Migrated": true }, { "Field": "localBattleUserState", @@ -1135,7 +1137,7 @@ "SetReArmorTemporaryDisabled" ], "Default": "new lE()", - "Migrated": false + "Migrated": true }, { "Field": "soundSettings", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index 3630ded..7bdf39f 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -393,6 +393,8 @@ const Migrated = { inviteState: { Type: 'InviteStateSlice', Reduce: 'ReduceInvite' }, loginByPassword: { Type: 'LoginByPasswordStateSlice', Reduce: 'ReduceLoginByPassword' }, twitchState: { Type: 'TwitchStateSlice', Reduce: 'ReduceTwitch' }, + home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' }, + localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); @@ -446,9 +448,9 @@ Lines.push('// 与根 reducer ' + RootReducerFn.Name + '(逐切片归约 Lines.push('// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。'); Lines.push('package App.State'); Lines.push(''); -Lines.push('import App.State.Reducers.InviteStateSlice'); -Lines.push('import App.State.Reducers.LoginByPasswordStateSlice'); -Lines.push('import App.State.Reducers.TwitchStateSlice'); +for (const F of Object.keys(Migrated).sort()) { + Lines.push('import App.State.Reducers.' + Migrated[F].Type); +} Lines.push(''); Lines.push('/**'); Lines.push(' * 原 `' + RootClass + '` / `TOState`:' + Rows.length + ' 个切片字段,顺序与 toString 一致。'); diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/HomeReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/HomeReducer.kt new file mode 100644 index 0000000..16d40ed --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/HomeReducer.kt @@ -0,0 +1,30 @@ +// HomeReducer.kt —— 首页切片(对应 module_10032 的 s1 状态 + 根 reducer 的 home 内联分支) +// +// 证据链: +// 状态类 s1:`function s1(t) { t = t !== cd && t; this.esm_1 = t; }` +// toString:`HomeScreen(shopDiscountNotification=… )`,默认 false +// 归约:根 reducer `k6` 的 home 分支(原内联 IIFE,委派表记为 `(inline@home)`) +// `(function (t, n) { return t instanceof o1 ? n.f7j(!0) : n; })(t, n.tpw_1)` +// `ld(s1).f7j = function (t) { return new s1(t); }` 是 copy,`!0` 即新值 true +// 动作 o1:无字段、无 toString(10032 以 `$_$.gl` 导出单例 `Eh`),按 reducer 效果定名 +// `ShowShopDiscountNotification`;命名证据见 `3DTank/names/root_wiring.json` 的 AnonymousActions +// (派发点 module_18334 的 `nt(Qx).qge`,即主菜单商店项上的折扣提示)。 +package App.State.Reducers + +/** + * 原 `o1`:无字段、无 toString 的默认动作;效果是 `HomeScreen.copy(shopDiscountNotification = true)`。 + */ +class ShowShopDiscountNotification + +/** + * 原 `s1` / `HomeScreen`:`HomeScreen(shopDiscountNotification)`,默认 false。 + */ +data class HomeStateSlice( + val ShopDiscountNotification: Boolean = false, +) + +/** 根 reducer 的 home 分支(原内联 IIFE,`(inline@home)`)。 */ +fun ReduceHome(State0: HomeStateSlice, A: Any): HomeStateSlice = when (A) { + is ShowShopDiscountNotification -> State0.copy(ShopDiscountNotification = true) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/LocalBattleUserReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/LocalBattleUserReducer.kt new file mode 100644 index 0000000..b1696b4 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/LocalBattleUserReducer.kt @@ -0,0 +1,38 @@ +// LocalBattleUserReducer.kt —— 本地战斗用户切片(对应 module_10032 的 lE 状态 + 根 reducer 的 upw_1 内联分支) +// +// 证据链: +// 状态类 lE:`function lE(t, n) { t = t === cd ? null : t; n = n !== cd && n; +// this.uq8_1 = t; this.vq8_1 = n; }` +// toString:`LocalBattleUserState(weaponResistanceProperty=…, isReArmorTemporaryDisabled=…)` +// 默认值:`null / false` +// 归约:根 reducer `k6` 的 localBattleUserState 分支(原内联 IIFE,委派表记为 `(inline@localBattleUserState)`) +// `(function (t, n) { +// return t instanceof fE ? n.xq8(t.yq8_1) +// : t instanceof vE ? n.xq8(cd, t.zq8_1) +// : n; +// })(t, n.upw_1)` +// `ld(lE).xq8` 是 copy(缺省实参取自身字段) +// 动作(都在 action_inventory_10032 里): +// fE → SetLocalWeaponResistanceProperty(itemProperty: String) 10032 导出 `$_$.ru` +// vE → SetReArmorTemporaryDisabled(disabled: Boolean) 10032 导出 `$_$.su` +// 派发点例:module_70130 的 `this.z1vd_1.uar(new _a(!t.equals(oa())))` +package App.State.Reducers + +import App.State.SetLocalWeaponResistanceProperty +import App.State.SetReArmorTemporaryDisabled + +/** + * 原 `lE`:`LocalBattleUserState(weaponResistanceProperty, isReArmorTemporaryDisabled)`。 + * 默认值取自原构造函数:`null / false`。 + */ +data class LocalBattleUserStateSlice( + val WeaponResistanceProperty: String? = null, + val IsReArmorTemporaryDisabled: Boolean = false, +) + +/** 根 reducer 的 localBattleUserState 分支(原内联 IIFE,`(inline@localBattleUserState)`)。 */ +fun ReduceLocalBattleUser(State0: LocalBattleUserStateSlice, A: Any): LocalBattleUserStateSlice = when (A) { + is SetLocalWeaponResistanceProperty -> State0.copy(WeaponResistanceProperty = A.itemProperty) + is SetReArmorTemporaryDisabled -> State0.copy(IsReArmorTemporaryDisabled = A.disabled) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt index 9cd66e1..50ab64c 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt @@ -6,13 +6,17 @@ // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) // twitchState → ReduceTwitch(g9 / y9.gta) +// home → ReduceHome(s1 / 根 reducer 的 `(inline@home)` 分支) +// localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支) // -// 其余 86 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 84 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State +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 @@ -32,6 +36,8 @@ object RootReducer { InviteState = ReduceInvite(State0.InviteState, Action), LoginByPassword = ReduceLoginByPassword(State0.LoginByPassword, Action), TwitchState = ReduceTwitch(State0.TwitchState, Action), + Home = ReduceHome(State0.Home, Action), + LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index fbbebe5..031b6f3 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -4,7 +4,9 @@ // 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。 package App.State +import App.State.Reducers.HomeStateSlice import App.State.Reducers.InviteStateSlice +import App.State.Reducers.LocalBattleUserStateSlice import App.State.Reducers.LoginByPasswordStateSlice import App.State.Reducers.TwitchStateSlice @@ -107,10 +109,10 @@ data class RootState( val BattleChat: Any? = null, /** 原 droppableGold(Vet / Wp.kth)。未迁移:原样透传。 */ val DroppableGold: Any? = null, - /** 原 home(s1 / (inline@home))。未迁移:原样透传。 */ - val Home: Any? = null, - /** 原 localBattleUserState(lE / (inline@localBattleUserState))。未迁移:原样透传。 */ - val LocalBattleUserState: Any? = null, + /** 原 home(s1 / (inline@home))。已迁移。 */ + val Home: HomeStateSlice = HomeStateSlice(), + /** 原 localBattleUserState(lE / (inline@localBattleUserState))。已迁移。 */ + val LocalBattleUserState: LocalBattleUserStateSlice = LocalBattleUserStateSlice(), /** 原 soundSettings(Det / Wet().jth)。未迁移:原样透传。 */ val SoundSettings: Any? = null, /** 原 gameSettings(c2 / g2().zss)。未迁移:原样透传。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index 2e0ea17..bf8ab91 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -106,7 +106,7 @@ object RootWiring { ) /** 已迁移(由 RootReducer 真正归约)的字段。 */ - val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState") + val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ val Unmigrated: List get() = All.filter { it.Field !in Migrated } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt index bd2ca17..d90b4bc 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -9,7 +9,7 @@ // ⑥ 动作目录:816 个类与 138 个真实派发名对齐 // ⑦ 回放:真实 toString 样例的类/字段/类型校验 // ⑧ 全目录动作类都能按 KClass 路由 -// ⑨~⑫ 切片 reducer:InviteState / LoginByPasswordState / TwitchState;⑫ 根 reducer 的 89 槽位委派 +// ⑨~⑭ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) package App @@ -22,15 +22,20 @@ 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 @@ -42,8 +47,10 @@ 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 @@ -377,7 +384,50 @@ object StateCheck { Check("切片 Twitch:接 Store 派发后状态已归约", Store.State.DaysForNextLinking == 2) } - // ⑫ 根 reducer:89 个槽位的委派表 + 三个已迁移切片一次归约 + // ⑫ 切片 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 个槽位的委派表 + 五个已迁移切片一次归约 run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -428,20 +478,34 @@ object StateCheck { "根:twitchState 委派到 g9 / uf.gta", TwitchW != null && TwitchW.SliceClass == "g9" && TwitchW.Reducer == "uf.gta" ) + // 后两片是内联 IIFE,委派名是 `(inline@)` + val HomeW = RootWiring.ByField("home") + val LocalBattleW = RootWiring.ByField("localBattleUserState") Check( - "根:迁移表 = 三个已迁移字段,其余 86 个待迁", - RootReducer.Migrated == setOf("inviteState", "loginByPassword", "twitchState") && - RootWiring.Unmigrated.size == 86 + "根: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( + "根:迁移表 = 五个已迁移字段,其余 84 个待迁", + RootReducer.Migrated == + setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState") && + RootWiring.Unmigrated.size == 84 ) - // 一次 ReduceRoot 里三个切片各归约自己的动作,互不串片 + // 一次 ReduceRoot 里五个切片各归约自己的动作,互不串片 val S0 = RootState(User = "user-1", Screen = "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.TwitchState == S0.TwitchState && + S1.Home == S0.Home ) val S2 = ReduceRoot(S1, SetLogin("pilot")) @@ -459,27 +523,44 @@ object StateCheck { S3.InviteState == S2.InviteState && S3.LoginByPassword == S2.LoginByPassword ) + + val S4 = ReduceRoot(S3, ShowShopDiscountNotification()) Check( - "根:三次归约后三个切片各自保留结果", - S3.InviteState.Invite == "uid-1" && - S3.LoginByPassword.Login == "pilot" && - S3.TwitchState.TwitchAccount == "tank_tv" + "根: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( "根:未迁移字段原样透传", - S3.User == "user-1" && S3.Screen == "Preloader" + S5.User == "user-1" && S5.Screen == "Preloader" ) - Check("根:无关动作返回等值状态", ReduceRoot(S3, AddFrags(1)) == S3) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 3/89") + Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 5/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 派发后三片同时归约", + Store.State.LoginByPassword.Status == ByPasswordStatus.LOGGING_IN && + Store.State.TwitchState.IsTwitchEnabled && + Store.State.Home.ShopDiscountNotification ) }