Skip to content

Commit 5d12122

Browse files
Merge pull request #177 from alvin000009238/feature/refactor-mock-data
Refactor Android mock data system and cleanup old sample data
2 parents 54220cb + f0cb658 commit 5d12122

11 files changed

Lines changed: 651 additions & 188 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@
3131
- 推送 `v*` tag 會觸發 `.github/workflows/android-release.yml`,建立 signed `arm64-v8a` release APK 並發布 GitHub Release。
3232
- Release notes 來自 `CHANGELOG.md` 內與 tag 對應的 `## [x.y.z]` 區塊;新增版本時要先補 changelog。
3333
- GitHub Secrets 需設定 `ANDROID_RELEASE_KEYSTORE_BASE64``ANDROID_RELEASE_KEYSTORE_PASSWORD``ANDROID_RELEASE_KEY_ALIAS``ANDROID_RELEASE_KEY_PASSWORD`。不要提交 keystore 或密碼。
34+
35+
## Android UI fake data
36+
37+
- UI 展示資料集中在 `android/app/src/main/java/com/clhs/score/data/FakeData.kt`;新增畫面預覽或假資料情境時優先擴充這裡,不要在 Composable 內臨時硬編資料。
38+
- Android app 可用 `-PuseFakeData=true` 切到 `FakeGradeRepository`,讓登入後成績列表、平均、班排、各科分析、圖表與模擬器都不依賴 API。
39+
- Compose Preview 入口在 `android/app/src/main/java/com/clhs/score/ui/ScorePreviews.kt`,應直接使用 `FakeData``GradesUiState`

android/app/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ val releaseKeystore = providers.environmentVariable("ANDROID_RELEASE_KEYSTORE")
1010
val releaseKeystorePassword = providers.environmentVariable("ANDROID_RELEASE_KEYSTORE_PASSWORD")
1111
val releaseKeyAlias = providers.environmentVariable("ANDROID_RELEASE_KEY_ALIAS")
1212
val releaseKeyPassword = providers.environmentVariable("ANDROID_RELEASE_KEY_PASSWORD")
13+
val useFakeData = providers.gradleProperty("useFakeData")
14+
.map { it.toBoolean() }
15+
.orElse(false)
1316
val hasReleaseSigning = listOf(
1417
releaseKeystore,
1518
releaseKeystorePassword,
@@ -38,10 +41,12 @@ android {
3841
}
3942

4043
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
44+
buildConfigField("boolean", "USE_FAKE_DATA", useFakeData.get().toString())
4145
}
4246

4347
buildFeatures {
4448
compose = true
49+
buildConfig = true
4550
}
4651

4752
compileOptions {

android/app/src/androidTest/java/com/clhs/score/ui/ScoreUiTest.kt

Lines changed: 23 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
1111
import androidx.compose.ui.Modifier
1212
import androidx.compose.ui.test.assertCountEquals
1313
import androidx.compose.ui.test.assertIsDisplayed
14+
import androidx.compose.ui.test.hasClickAction
1415
import androidx.compose.ui.test.junit4.createComposeRule
1516
import androidx.compose.ui.test.onAllNodesWithText
1617
import androidx.compose.ui.test.onNodeWithContentDescription
@@ -22,15 +23,16 @@ import com.clhs.score.data.ExamSummary
2223
import com.clhs.score.data.GradeReport
2324
import com.clhs.score.data.GradeStandard
2425
import com.clhs.score.data.LocalScoreInsightProvider
26+
import com.clhs.score.data.MockGradeSystem
2527
import com.clhs.score.data.StudentInfo
28+
import com.clhs.score.data.StudentScenario
2629
import com.clhs.score.data.SubjectScore
2730
import com.clhs.score.data.YearTermOption
2831
import com.clhs.score.data.buildGradeAnalysis
2932
import com.clhs.score.data.buildGradeTrend
3033
import com.clhs.score.data.cleanSubjectName
3134
import com.clhs.score.ui.theme.ScoreTheme
3235
import com.clhs.score.viewmodel.GradesUiState
33-
import com.clhs.score.viewmodel.LoginUiState
3436
import kotlinx.serialization.json.JsonObject
3537
import org.junit.Rule
3638
import org.junit.Test
@@ -40,28 +42,19 @@ class ScoreUiTest {
4042
val composeRule = createComposeRule()
4143

4244
@Test
43-
fun loginScreenShowsRequiredFields() {
45+
fun introScreenExposesLoginEntryPoint() {
46+
var clicked = false
4447
composeRule.setContent {
4548
ScoreTheme {
46-
LoginScreen(
47-
state = LoginUiState(errorMessage = "驗證失敗,請重新輸入"),
48-
snackbarHost = {},
49-
onUsernameChange = {},
50-
onPasswordChange = {},
51-
onCaptchaChange = {},
52-
onRefreshCaptcha = {},
53-
onLogin = {},
54-
)
49+
IntroScreen(onLoginClick = { clicked = true })
5550
}
5651
}
5752

58-
composeRule.onNodeWithText("帳號").assertIsDisplayed()
59-
composeRule.onNodeWithText("密碼").assertIsDisplayed()
60-
composeRule.onNodeWithText("驗證碼").assertIsDisplayed()
61-
composeRule.onNodeWithText("驗證碼圖片").assertIsDisplayed()
62-
composeRule.onNodeWithText("刷新").assertIsDisplayed()
63-
composeRule.onNodeWithText("登入").assertIsDisplayed()
64-
composeRule.onNodeWithText("驗證失敗,請重新輸入").assertIsDisplayed()
53+
composeRule.onNodeWithText("成績", substring = true).assertIsDisplayed()
54+
composeRule.onNode(hasClickAction()).performClick()
55+
composeRule.runOnIdle {
56+
assert(clicked)
57+
}
6558
}
6659

6760
@Test
@@ -80,11 +73,11 @@ class ScoreUiTest {
8073
composeRule.onNodeWithContentDescription("進階").assertIsDisplayed()
8174
composeRule.onAllNodesWithText("全部科目").assertCountEquals(0)
8275
composeRule.onAllNodesWithText("圖表").assertCountEquals(0)
83-
composeRule.onNodeWithText("測試學生", substring = true).assertIsDisplayed()
76+
composeRule.onNodeWithText("展示學生", substring = true).assertIsDisplayed()
8477
composeRule.onNodeWithText("加權平均").assertIsDisplayed()
85-
composeRule.onNodeWithText("班排 13/37(前 35%)").assertIsDisplayed()
86-
composeRule.onNodeWithText("類排 60/219(前 27%)").assertIsDisplayed()
87-
composeRule.onNodeWithText("科目數 3 | 最高分 84").assertIsDisplayed()
78+
composeRule.onNodeWithText("班排 15/38(前 39%)").assertIsDisplayed()
79+
composeRule.onNodeWithText("類排 88/226(前 38%)").assertIsDisplayed()
80+
composeRule.onNodeWithText("科目數 7 | 最高分 80").assertIsDisplayed()
8881
composeRule.onNodeWithText("重點解讀").assertIsDisplayed()
8982
composeRule.onNodeWithText("最值得補強").assertIsDisplayed()
9083
composeRule.onNodeWithText("最具優勢").assertIsDisplayed()
@@ -158,7 +151,8 @@ class ScoreUiTest {
158151
composeRule.setContent {
159152
ScoreTheme {
160153
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
161-
AnalysisSection(report = sampleReport(), analysis = buildGradeAnalysis(sampleReport()))
154+
val report = MockGradeSystem.generateReport()
155+
AnalysisSection(report = report, analysis = buildGradeAnalysis(report))
162156
}
163157
}
164158
}
@@ -177,18 +171,21 @@ class ScoreUiTest {
177171
showTrend: Boolean = true,
178172
trendError: String? = null,
179173
) {
180-
val report = sampleReport()
174+
val report = MockGradeSystem.generateReport(StudentScenario.NORMAL)
181175
val analysis = buildGradeAnalysis(report)
182176
val trend = if (showTrend) buildGradeTrend(
183177
currentExamName = "期末考",
184178
currentReport = report,
185-
previousReports = listOf("期中考" to sampleReport(mathScore = 78.0, englishScore = 80.0, chineseScore = 61.0)),
179+
previousReports = listOf("期中考" to MockGradeSystem.generateReport(
180+
StudentScenario.NORMAL,
181+
customScores = listOf(78.0, 80.0, 61.0, 70.0, 60.0, 60.0, 60.0)
182+
)),
186183
) else null
187184
var expanded by remember { mutableStateOf(emptySet<String>()) }
188185
GradesScreen(
189186
state = GradesUiState(
190187
isLoggedIn = true,
191-
studentNo = "310471",
188+
studentNo = "110000",
192189
structure = listOf(
193190
YearTermOption(
194191
text = "114學年度 上學期",
@@ -218,46 +215,4 @@ class ScoreUiTest {
218215
onOpenScoreSimulator = {},
219216
)
220217
}
221-
222-
private fun sampleReport(
223-
mathScore: Double = 84.0,
224-
englishScore: Double = 82.0,
225-
chineseScore: Double = 58.0,
226-
): GradeReport = GradeReport(
227-
message = "",
228-
studentInfo = StudentInfo(
229-
studentNo = "310471",
230-
studentName = "測試學生",
231-
className = "二年 11 班",
232-
seatNo = "20",
233-
updatedAt = "",
234-
showClassRank = true,
235-
showClassRankCount = true,
236-
showCategoryRank = true,
237-
showCategoryRankCount = true,
238-
),
239-
examSummary = ExamSummary(
240-
year = 114,
241-
termText = "",
242-
examName = "期末考",
243-
totalScoreDisplay = "300.00",
244-
averageScoreDisplay = "75.00",
245-
classRank = 13.0,
246-
classCount = 37,
247-
categoryRank = 60.0,
248-
categoryRankCount = 219,
249-
flunkCount = 0,
250-
),
251-
subjects = listOf(
252-
SubjectScore("國語文", "%.2f".format(chineseScore), chineseScore, "70.00", 70.0, 25, 37, null, null, "114學年度 上學期", false, false, false),
253-
SubjectScore("英語文", "%.2f".format(englishScore), englishScore, "76.00", 76.0, 8, 37, null, null, "114學年度 上學期", false, false, false),
254-
SubjectScore("數學", "%.2f".format(mathScore), mathScore, "68.00", 68.0, 5, 37, null, null, "114學年度 上學期", false, false, false),
255-
),
256-
standards = listOf(
257-
GradeStandard("國語文", 88.0, 80.0, 70.0, 60.0, 50.0, 12.0, 2, 6, 12, 10, 5, 2, 1, 1, 1, 0),
258-
GradeStandard("英語文", 90.0, 82.0, 70.0, 60.0, 50.0, 13.0, 3, 8, 10, 9, 5, 2, 1, 1, 1, 0),
259-
GradeStandard("數學", 85.0, 78.0, 68.0, 58.0, 48.0, 15.0, 1, 5, 11, 12, 6, 2, 1, 1, 1, 0),
260-
),
261-
rawResult = JsonObject(emptyMap()),
262-
)
263218
}

android/app/src/main/java/com/clhs/score/MainActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ class MainActivity : ComponentActivity() {
1717
enableEdgeToEdge()
1818
setContent {
1919
ScoreTheme {
20-
val viewModel: ScoreViewModel = viewModel(factory = ScoreViewModel.factory(applicationContext))
20+
val viewModel: ScoreViewModel = viewModel(
21+
factory = ScoreViewModel.factory(
22+
context = applicationContext,
23+
useFakeData = BuildConfig.USE_FAKE_DATA,
24+
),
25+
)
2126
val loginState by viewModel.loginState.collectAsState()
2227
val gradesState by viewModel.gradesState.collectAsState()
2328
ScoreApp(

0 commit comments

Comments
 (0)