docs: 修正 Task 10 计划(补 DeviceViewModel 测试与 Preview,修正步骤编号与文案清单)
This commit is contained in:
@@ -3117,7 +3117,92 @@ git add -A && git commit -m "feat: 实现运动页与运动历史子页"
|
|||||||
- `class DeviceViewModel(ringDeviceRepository)` 暴露 `uiState: StateFlow<DeviceUiState>`、`fun setFunctionEnabled(id: String, enabled: Boolean)`
|
- `class DeviceViewModel(ringDeviceRepository)` 暴露 `uiState: StateFlow<DeviceUiState>`、`fun setFunctionEnabled(id: String, enabled: Boolean)`
|
||||||
- `@Composable fun DeviceScreen(onOpenSettings: () -> Unit, modifier, viewModel)`、`@Composable fun DeviceSettingsScreen(modifier, viewModel)`
|
- `@Composable fun DeviceScreen(onOpenSettings: () -> Unit, modifier, viewModel)`、`@Composable fun DeviceSettingsScreen(modifier, viewModel)`
|
||||||
|
|
||||||
- [ ] **Step 1: 写 `DeviceViewModel.kt`**
|
- [ ] **Step 1: 写失败的测试**
|
||||||
|
|
||||||
|
`app/src/test/java/com/placeholder/ringhealth/feature/device/DeviceViewModelTest.kt`
|
||||||
|
(与 Task 8 / Task 9 的两个 ViewModel 测试同形;ledger Ruling R27 —— 三个 ViewModel 的覆盖度应当一致):
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
package com.placeholder.ringhealth.feature.device
|
||||||
|
|
||||||
|
import com.placeholder.ringhealth.data.mock.MockRingDeviceRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
/** 设备页 ViewModel 测试。 */
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class DeviceViewModelTest {
|
||||||
|
|
||||||
|
private val dispatcher = StandardTestDispatcher()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
Dispatchers.setMain(dispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `输出设备状态与五个功能开关`() = runTest(dispatcher) {
|
||||||
|
val viewModel = DeviceViewModel(MockRingDeviceRepository())
|
||||||
|
backgroundScope.launch { viewModel.uiState.collect {} }
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
val state = viewModel.uiState.value
|
||||||
|
assertEquals(78, state.device.batteryPercent)
|
||||||
|
assertEquals("1.0.5", state.device.firmwareVersion)
|
||||||
|
assertEquals(5, state.settings.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `setFunctionEnabled 只改目标项且可来回切换`() = runTest(dispatcher) {
|
||||||
|
val viewModel = DeviceViewModel(MockRingDeviceRepository())
|
||||||
|
backgroundScope.launch { viewModel.uiState.collect {} }
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
// Mock 的 5 项里只有 stress 与 blood_pressure 初始为 false,其余 3 项为 true
|
||||||
|
// (见 MockDataProvider.defaultFunctionSettings)。切换 stress 后应为 4 项开启。
|
||||||
|
val target = "stress"
|
||||||
|
assertFalse(viewModel.uiState.value.settings.first { it.id == target }.enabled)
|
||||||
|
|
||||||
|
viewModel.setFunctionEnabled(target, true)
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertTrue(viewModel.uiState.value.settings.first { it.id == target }.enabled)
|
||||||
|
// 关键:其它开关不受影响。只断言目标项变了,无法发现「整表被覆盖」这类回归。
|
||||||
|
assertEquals(4, viewModel.uiState.value.settings.count { it.enabled })
|
||||||
|
|
||||||
|
viewModel.setFunctionEnabled(target, false)
|
||||||
|
advanceUntilIdle()
|
||||||
|
assertFalse(viewModel.uiState.value.settings.first { it.id == target }.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 1b: 运行测试,确认失败**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /d/cherry/repos/ring-health-app && ./gradlew :app:testDebugUnitTest --tests "com.placeholder.ringhealth.feature.device.DeviceViewModelTest"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `Unresolved reference: DeviceViewModel`,编译失败。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 写 `DeviceViewModel.kt`**
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
package com.placeholder.ringhealth.feature.device
|
package com.placeholder.ringhealth.feature.device
|
||||||
@@ -3168,7 +3253,7 @@ class DeviceViewModel(
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 1b: 把 `DeviceViewModel` 注册到 `AppViewModelProvider`**
|
- [ ] **Step 3: 把 `DeviceViewModel` 注册到 `AppViewModelProvider`**
|
||||||
|
|
||||||
在 `AppViewModelProvider.kt` 的 `viewModelFactory { }` 内**追加**一个 `initializer`(ledger Ruling R2),
|
在 `AppViewModelProvider.kt` 的 `viewModelFactory { }` 内**追加**一个 `initializer`(ledger Ruling R2),
|
||||||
并在该文件顶部加 `import com.placeholder.ringhealth.feature.device.DeviceViewModel`:
|
并在该文件顶部加 `import com.placeholder.ringhealth.feature.device.DeviceViewModel`:
|
||||||
@@ -3184,25 +3269,28 @@ class DeviceViewModel(
|
|||||||
- [ ] **Step 4: 追加文案到 `strings.xml`**
|
- [ ] **Step 4: 追加文案到 `strings.xml`**
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<string name="device_title">我的设备</string>
|
|
||||||
<string name="device_connected">已连接</string>
|
<string name="device_connected">已连接</string>
|
||||||
<string name="device_disconnected">未连接</string>
|
<string name="device_disconnected">未连接</string>
|
||||||
<string name="device_battery">电量 %1$d%%</string>
|
<string name="device_battery">电量 %1$d%%</string>
|
||||||
<string name="device_firmware">固件版本 %1$s</string>
|
<string name="device_firmware">固件版本 %1$s</string>
|
||||||
<string name="device_charging">充电中</string>
|
|
||||||
<string name="device_settings_title">功能设置</string>
|
<string name="device_settings_title">功能设置</string>
|
||||||
<string name="device_find_ring">查找戒指</string>
|
|
||||||
<string name="device_find_ring_desc">让戒指震动以便寻找</string>
|
|
||||||
<string name="device_firmware_update">固件升级</string>
|
|
||||||
<string name="device_firmware_update_desc">检查并升级到最新固件</string>
|
|
||||||
<string name="device_wear_detection">佩戴检测</string>
|
|
||||||
<string name="device_wear_detection_desc">未佩戴时暂停测量</string>
|
|
||||||
<string name="device_more">更多设置</string>
|
<string name="device_more">更多设置</string>
|
||||||
<string name="device_switch_on">已开启</string>
|
<string name="device_switch_on">已开启</string>
|
||||||
<string name="device_switch_off">已关闭</string>
|
<string name="device_switch_off">已关闭</string>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: 实现 `DeviceScreen.kt`**
|
> **本任务不得新增无消费者的字符串资源(ledger Ruling R27)。** 计划早期版本的这份清单有 16 条,
|
||||||
|
> 逐条 grep 调用点后砍到 8 条。被删的 8 条是
|
||||||
|
> `device_title`(`我的设备`)、`device_charging`(`充电中`)、
|
||||||
|
> `device_find_ring` / `device_find_ring_desc`、`device_firmware_update` / `device_firmware_update_desc`、
|
||||||
|
> `device_wear_detection` / `device_wear_detection_desc` —— 全计划**零调用点**。
|
||||||
|
> 它们看起来是照着小美运动健康的设备页草拟的(查找戒指 / 固件升级 / 佩戴检测三行入口),
|
||||||
|
> 但本任务实际实现的设备页只有「戒指信息卡 + 功能开关列表 + 更多设置入口」,
|
||||||
|
> 那三行从来没有被写进任何代码。**不要把它们加回来。**
|
||||||
|
> 若 M2 真要做「查找戒指」这类入口,那时再补文案 —— 提前写好只会留下 8 条永不渲染的字符串。
|
||||||
|
> (`device_settings_hint` 不在此列,它在 Step 5 单独追加,有调用点。)
|
||||||
|
|
||||||
|
- [ ] **Step 5: 实现 `DeviceScreen.kt`**
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
package com.placeholder.ringhealth.feature.device
|
package com.placeholder.ringhealth.feature.device
|
||||||
@@ -3219,7 +3307,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bluetooth
|
import androidx.compose.material.icons.filled.Bluetooth
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material.icons.filled.Watch
|
import androidx.compose.material.icons.filled.Watch
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -3230,16 +3317,19 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.placeholder.ringhealth.AppViewModelProvider
|
import com.placeholder.ringhealth.AppViewModelProvider
|
||||||
import com.placeholder.ringhealth.R
|
import com.placeholder.ringhealth.R
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
||||||
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
||||||
import com.placeholder.ringhealth.domain.model.DeviceFunctionSetting
|
import com.placeholder.ringhealth.domain.model.DeviceFunctionSetting
|
||||||
|
import com.placeholder.ringhealth.domain.model.RingDeviceState
|
||||||
|
|
||||||
/** 设备(戒指管理)页。 */
|
/** 设备(戒指管理)页。 */
|
||||||
@Composable
|
@Composable
|
||||||
@@ -3352,9 +3442,47 @@ internal fun SettingRow(setting: DeviceFunctionSetting, modifier: Modifier = Mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun DeviceContentPreview() {
|
||||||
|
RingHealthTheme {
|
||||||
|
DeviceContent(
|
||||||
|
state = DeviceUiState(
|
||||||
|
device = RingDeviceState(true, "RingHealth 智能戒指", 78, "1.0.5", false),
|
||||||
|
settings = previewSettings(),
|
||||||
|
),
|
||||||
|
onOpenSettings = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview 用的假开关列表。
|
||||||
|
*
|
||||||
|
* `internal` 而非 `private`:`DeviceSettingsScreen.kt` 的 Preview 也要用。
|
||||||
|
* 取值与 `MockDataProvider.defaultFunctionSettings()` 保持一致,
|
||||||
|
* 免得 Preview 里看到的和跑起来看到的不是同一份数据。
|
||||||
|
*/
|
||||||
|
internal fun previewSettings(): List<DeviceFunctionSetting> = listOf(
|
||||||
|
DeviceFunctionSetting("heart_rate", "自动心率监测", "每 10 分钟测量一次", true),
|
||||||
|
DeviceFunctionSetting("blood_oxygen", "自动血氧监测", "夜间持续监测", true),
|
||||||
|
DeviceFunctionSetting("sleep", "睡眠监测", "自动识别睡眠时段", true),
|
||||||
|
DeviceFunctionSetting("stress", "压力监测", "全天候压力评估", false),
|
||||||
|
DeviceFunctionSetting("blood_pressure", "血压监测", "需在 App 内手动校准", false),
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: 实现 `DeviceSettingsScreen.kt`(替换占位)**
|
> `previewSettings()` 是**唯一**的例外:它是 `internal` 而不是 `private`,因为两个页面的 Preview 共用。
|
||||||
|
> 它只在 Preview 与 debug 构建里被调用,不进任何生产路径。
|
||||||
|
|
||||||
|
> **Preview 是本项目唯一可用的目视检查手段(ledger Ruling R21 / R26)**:本机没有模拟器镜像也没有真机,
|
||||||
|
> `assembleDebug` 只能证明「能编译」。因此每个页面都必须拆成「公开的有状态外壳 + `private` 的无状态
|
||||||
|
> `XContent` + 挂在 `XContent` 上的 `@Preview`」——只有无状态的那层才能喂假数据渲染。
|
||||||
|
> 两个页面的 Preview 共用同一个 `internal fun previewSettings()`(定义在 `DeviceScreen.kt` 末尾,
|
||||||
|
> 见本步代码块之后):`DeviceSettingsScreen.kt` 也要用它,`private` 在文件之间传不过去。
|
||||||
|
|
||||||
|
- [ ] **Step 6: 实现 `DeviceSettingsScreen.kt`(替换占位)**
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
package com.placeholder.ringhealth.feature.device
|
package com.placeholder.ringhealth.feature.device
|
||||||
@@ -3370,18 +3498,21 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.placeholder.ringhealth.AppViewModelProvider
|
import com.placeholder.ringhealth.AppViewModelProvider
|
||||||
import com.placeholder.ringhealth.R
|
import com.placeholder.ringhealth.R
|
||||||
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
import com.placeholder.ringhealth.core.designsystem.MetricGreen
|
||||||
|
import com.placeholder.ringhealth.core.designsystem.RingHealthTheme
|
||||||
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
import com.placeholder.ringhealth.core.designsystem.TextSecondary
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
import com.placeholder.ringhealth.core.designsystem.component.HealthCard
|
||||||
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
import com.placeholder.ringhealth.core.designsystem.component.SectionHeader
|
||||||
@@ -3396,13 +3527,32 @@ fun DeviceSettingsScreen(
|
|||||||
viewModel: DeviceViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
viewModel: DeviceViewModel = viewModel(factory = AppViewModelProvider.Factory),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
DeviceSettingsContent(
|
||||||
|
settings = state.settings,
|
||||||
|
onToggle = viewModel::setFunctionEnabled,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无状态设备设置内容。
|
||||||
|
* 与 `DeviceContent` 同样拆出独立函数,Preview 才能喂假数据渲染(ledger Ruling R26)。
|
||||||
|
* 注意回调签名是 `(id, enabled)` 而不是 `(DeviceFunctionSetting, Boolean)`:
|
||||||
|
* 无状态层不该知道「怎么改」,只负责把「改哪一项、改成什么」报上去。
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun DeviceSettingsContent(
|
||||||
|
settings: List<DeviceFunctionSetting>,
|
||||||
|
onToggle: (String, Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
item { SectionHeader(title = stringResource(R.string.device_settings_title)) }
|
item { SectionHeader(title = stringResource(R.string.device_settings_title)) }
|
||||||
items(state.settings) { setting ->
|
items(settings) { setting ->
|
||||||
HealthCard {
|
HealthCard {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -3415,8 +3565,9 @@ fun DeviceSettingsScreen(
|
|||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
checked = setting.enabled,
|
checked = setting.enabled,
|
||||||
onCheckedChange = { checked -> viewModel.setFunctionEnabled(setting.id, checked) },
|
onCheckedChange = { checked -> onToggle(setting.id, checked) },
|
||||||
colors = androidx.compose.material3.SwitchDefaults.colors(checkedTrackColor = MetricGreen),
|
// SwitchDefaults 走正常 import,不写字面量全限定名(与其余文件的 import 风格一致)
|
||||||
|
colors = SwitchDefaults.colors(checkedTrackColor = MetricGreen),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3431,15 +3582,23 @@ fun DeviceSettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun DeviceSettingsContentPreview() {
|
||||||
|
RingHealthTheme {
|
||||||
|
DeviceSettingsContent(settings = previewSettings(), onToggle = { _, _ -> })
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: 追加提示文案到 `strings.xml`**
|
- [ ] **Step 7: 追加提示文案到 `strings.xml`**
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<string name="device_settings_hint">阶段一为演示开关,接入戒指后将同步到设备。</string>
|
<string name="device_settings_hint">阶段一为演示开关,接入戒指后将同步到设备。</string>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: 在 `RingHealthApp.kt` 接入真实页面**
|
- [ ] **Step 8: 在 `RingHealthApp.kt` 接入真实页面**
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
composable(Routes.DEVICE) {
|
composable(Routes.DEVICE) {
|
||||||
@@ -3452,15 +3611,22 @@ fun DeviceSettingsScreen(
|
|||||||
|
|
||||||
删除 `DeviceSettingsPlaceholder` 的定义与 import。
|
删除 `DeviceSettingsPlaceholder` 的定义与 import。
|
||||||
|
|
||||||
- [ ] **Step 7: 构建 + 验证**
|
- [ ] **Step 9: 构建 + 单测**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /d/cherry/repos/ring-health-app && ./gradlew assembleDebug
|
cd /d/cherry/repos/ring-health-app && ./gradlew :app:testDebugUnitTest && ./gradlew assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: 设备 Tab 显示戒指卡片(电量 78%、固件 1.0.5);“更多设置”进入后开关可切换且返回后状态保持。
|
Expected: 两条命令均 `BUILD SUCCESSFUL`;`DeviceViewModelTest` 由 Step 1b 的 RED 转为 GREEN,
|
||||||
|
全量测试 15 项通过(Task 9 之后是 13 项,本任务 +2)。
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
界面上「设备 Tab 显示戒指卡片(电量 78%、固件 1.0.5)」「更多设置进入后开关可切换且返回后状态保持」
|
||||||
|
这两条**不在本步验证**(ledger Ruling R21:本机无模拟器与真机),顺延到 Task 14 Step 3 的人工验收清单。
|
||||||
|
|
||||||
|
> **不要用 `FROM-CACHE` 的绿当作证据。** Task 9 的修复轮实测到:Gradle 构建缓存命中时
|
||||||
|
> 任务会直接标记为 `FROM-CACHE` 而不真正执行。若看到 `FROM-CACHE`,加 `--rerun` 重跑一次再下结论。
|
||||||
|
|
||||||
|
- [ ] **Step 10: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add -A && git commit -m "feat: 实现设备页与设备设置子页(可交互开关)"
|
git add -A && git commit -m "feat: 实现设备页与设备设置子页(可交互开关)"
|
||||||
|
|||||||
Reference in New Issue
Block a user