fad49b7b66620cdc3fbac82cf9a63792b2e22ef0
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fad49b7b66 |
feat(chat): compact sticky task progress chip (#942) (#943)
* feat(chat): compact sticky task progress chip (#942) Re-add a glanceable progress strip above the chat timeline while work is active. Distinct from the inline TodoTaskCard (permanent timeline record): this chip is the temporary "what is happening right now" surface. - Shows only while todos are incomplete or a subagent is running. - Bounds to the same hydrated state.todos / subagentIndicators the card uses -- NOT the old broken state.todos gate removed in #811. - Label: "Tasks 2/5 · <in-progress task> · N agent" (agent count when relevant). - Tap opens SubagentInspectionSheet + resumes bottom-follow. - Auto-hides when all work completes/cancels; rebuilds on session resume. - i18n strings added (en/zh/ko); ktlint clean; compileDebugKotlin + unit tests green. * refactor(chat): drop inline TodoTaskCard, keep single sticky progress chip Per follow-up to #942: the inline todo card in the chat view is removed. The sticky TaskProgressChip above the timeline is now the only glanceable surface; the tool message itself remains the permanent record. - Remove inline TodoTaskCard early-return in ToolBubble.kt (todo tool output now renders like any other tool bubble). - Delete TodoTaskCard.kt + TodoTaskCardTest.kt (SecurityRiskChip lives in ToolBubble.kt, so risk display is unaffected). - Redesign chip: replace the ElectricBolt icon with a real CircularProgressIndicator (no emoji), keep ChevronRight affordance. - Label handles the no-todos case (subagents only): 'N agent running'. - Drop dead todo_task_count string; add task_progress_agent_running (en/zh/ko). ktlint clean; compileDebugKotlin + chat unit tests green. * chore: clear pre-existing compile warnings - HermesWsClient.kt: drop unnecessary safe call on non-null ResponseBody (resp.body.string()) and the now-dead 'body != null' guard it implied. - SessionsScreen.kt: replace deprecated Icons.Filled.CallSplit with the AutoMirrored variant (match existing Send usage in this file). ktlint clean; compileDebugKotlin SUCCESSFUL with no warnings. * chore(chat): replace emoji status markers with Material icons in inspection sheet The sticky progress chip opens SubagentInspectionSheet on tap; that sheet used emoji Text glyphs for status. Replaced with real Material icons: - Todo rows: completed -> CheckCircle (success), in-progress -> Autorenew (primary), cancelled -> Cancel (warning), pending -> RadioButtonUnchecked (muted). - Subagent rows: complete -> CheckCircle (success), failed -> Cancel (error), running -> Autorenew (primary). - Section headers: ElectricBolt -> FormatListBulleted (agent plan) + Groups (subagents). Icons tinted via LocalHermesStatusColors. No emoji remain in the chip's tap surface. ktlint clean; compileDebugKotlin SUCCESSFUL. * fix(lint): drop orphaned todo_task_count translation in ko Removing the string from the default (en) locale left a stale Korean translation, which tripped Lint's ExtraTranslation error and aborted lintDebug. Removing the orphan so default + translations stay in sync. android-lint now BUILD SUCCESSFUL (0 errors). * fix(chat): address #943 review - progress count, resume hydration, chip tests, plurals - TaskProgressChip: current-task number is now the first unfinished, non-cancelled todo's 1-based position (active index + 1), so a completed task 1 + in-progress task 2 reads as 2/5 instead of 1/5 (review point 2). - Deterministic resume hydration: hydrate todos directly from cached Room messages and from the merged REST transcript inside ChatViewModel load paths, in addition to the WS reducer, so a resumed session rebuilds the chip without waiting for a follow-up event (review point 3). - Extracted computeChipDisplay / shouldShowProgressChip pure functions and added TaskProgressChipTest covering active-task progress, completed/cancelled hide, subagent-only state, and transcript-resume hydration (review point 4). - agent count strings are now Android plurals (task_progress_agents / task_progress_agent_running) in en/zh/ko to avoid '2 agent' (review point 5). Co-Authored-By trailer intentionally omitted per repo convention. * fix(chat): derive chip number and content from the same selected task computeChipDisplay selected the task number from the first unfinished/ non-cancelled todo but the content independently from the first in_progress todo. With a pending task 1 + in_progress task 2 this produced 'Tasks 1/N · <task2 content>'. Now both values come from one selected item (prefer in_progress, fall back to first pending/non-terminal). Adds two focused tests: same-item selection, and pending-only fallback. |
||
|
|
fdbd98af3e |
Add Simplified Chinese (zh) localization (#901)
* feat: add Simplified Chinese (zh) localization - Add complete values-zh/strings.xml (921 strings, all keys covered) - Translate 107 hardcoded UI strings across 34 Kotlin files - Add 简体中文 option to in-app language picker - Terminology aligned with official Hermes dashboard zh translations * fix: resource-ize hardcoded Chinese strings per review Address Hy4ri's review on PR #901: - Move 104 hardcoded Chinese UI strings to string resources (English in values/strings.xml, Chinese in values-zh/strings.xml) referenced via stringResource() - SystemViewModel labels -> @StringRes + AndroidViewModel with resource-based toast templates - Make TodoTaskCardTest assertion locale-agnostic via getString() - Update cookie tests to assert new host-agnostic session cookie behavior (WiFi/Tailscale switch-safe) - Session cookie mirroring to WILDCARD bucket survives host switches * style: apply ktlintFormat per review --------- Co-authored-by: hermes-i18n <hermes-i18n@localhost> |
||
|
|
ff8370bb8d |
fix(settings): About tab crash + update-row UI (#889)
- AppUpdateViewModel has no (Application) ctor; the default viewModel()
factory's AndroidViewModelFactory does getConstructor(Application)
-> NoSuchMethodException -> crash on opening About. Build it with an
explicit viewModel { } initializer + APPLICATION_KEY instead.
- Add instrumented regression test that renders SettingsAboutPage with
the real default factory (the exact path that crashed).
- UpdateRow: status text and action button no longer fight for one line;
Update now / Open settings / Retry get a full-width button of their
own below the row, Downloading gets a full-width progress bar.
- Strip the leading 'v' from GitHub release tags before formatting
(templates already prepend 'v' -> was rendering 'vv1.21.1').
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
|
||
|
|
546bd17b18 |
fix(chat): snappy in-chat search over visible text only (#887)
* fix(chat): snappy in-chat search over visible text only - findMatches now scans USER + ASSISTANT messages only — tool rows and system events were matched before, so navigation jumped to invisible matches and the scan paid for the chat's heaviest strings (tool payloads) - Reasoning text is explicitly out of scope (lives in reasoningText, never highlighted) — documented in the controller contract - Debounce (150ms) coalesces typing into one scan instead of one full scan per keystroke; blanking the query still clears instantly - Tests: controller scope (user/assistant only, tool/system/reasoning excluded, case-insensitive, navigation wrap) + delegate debounce (coalescing, cancelled intermediate queries, instant blank clear) * fix(chat): search navigation lands the match mid-view, not at an edge scrollToSearchMatch top-aligned the matched message with animateScrollToItem(index), pinning it to the viewport edge — tall messages left the highlighted word above/below the visible area. Now top-aligns, then nudges the item down to ~1/3 viewport height (verified top-align -> measure -> adjust pattern, same as scrollToBottom). * fix(chat): search scroll targets the real lazy item and the actual word ROOT CAUSE: search-match indices are MESSAGE-list indices, but the LazyColumn has extra items (reasoning hoists, tool rows, loading-older), so scrollToSearchMatch(index) landed on the WRONG item whenever reasoning or tools preceded the match — the 'does nothing / word under or above the view' bug from the start. - messageIdToLazyIndex(): pure messageId -> LazyColumn item index mapping mirroring FullBleedChatList's emission order (verified in tests) - search-scroll effect moved INTO FullBleedChatList (the only composable that knows the mapping), serialized through the same ChatScrollController - word-level targeting: each SearchMatch now carries its character offset in the message; the scroll places offset/contentLength x item height at ~1/3 viewport, so the highlighted word itself lands in view on tall or multi-occurrence messages - counter/navigation now count OCCURRENCES (word hits), one entry per hit with a parallel searchMatchOffsets list; per-message dedupe removed - tests: lazy-index mapping (reasoning hoist, tools, leading items), SearchMatch offsets, occurrence flattening, regex-literal queries * fix(ci): pass searchMatchOffsets and scrollController to FullBleedChatList in instrumented test The render helper in FullBleedChatListTest still used the pre-offset signature, so compileDebugAndroidTestKotlin failed on the emulator job. Verified locally with compileDebugAndroidTestKotlin + ktlintCheck. * perf(chat): allocation-free indexOf search matcher with capped results - findMatches scans with String.indexOf(ignoreCase=true) on the ORIGINAL content instead of Regex(Pattern.quote()).findAll: queries are always literal, so regex was pure overhead — a compiled Pattern plus one MatchResult allocation per hit. Offsets stay exact (no lowercase transform, which can change string length on some scripts). - returns SearchResult(matches, totalMatches, capped): results are capped at 500 stored entries (MAX_SEARCH_MATCHES) to bound allocations and navigation on degenerate queries, while the counter still shows the exact total (searchMatchTotal/searchMatchCapped in ChatUiState). - 14 controller tests green, delegate behavior unchanged. * perf(chat): O(1) current-match lookup and highlight only matched bubbles Two recomposition hot spots killed in the list renderer: - isCurrentMatchFor() did messages.indexOfFirst per rendered bubble per search update — O(n) per item, O(n^2) per update on the main thread. Now currentMatchMessageId() resolves the current match's message id ONCE per recomposition and items compare ids (O(1)). - every prose bubble re-ran its highlight scan on every search-state change even with zero hits. matchedMessageIds() computes the matched id set once; bubbles outside it get an empty searchQuery and skip the scan entirely. Both helpers are pure, tested in FullBleedTurnsTest (18 tests), and the instrumented test target still compiles. * perf(chat): dedicated ChatSearchState holder — search no longer recomposes the list Search fields lived in ChatUiState, so every debounced scan result changed the whole state object: ChatScreen recomposed, FullBleedChatList's params changed, and the entire list body re-ran (turns loop + item registrations) on the main thread per search update. Search state now lives in a @Stable snapshot-backed ChatSearchState (LazyListState pattern) owned by ChatSearchDelegate: - the list takes ONE stable param; the instance never changes, so the body is skipped on search updates — only the scopes that read a field recompose (search bar, matched bubbles, the scroll effect) - matchedIds and currentMatchId are computed once per scan/navigate in the delegate and stored as state fields — items do O(1) id lookups inside their own lambdas, no body-level derivation - ChatUiState loses the 7 search fields; ChatViewModel exposes viewModel.searchState; delegate tests now assert on the holder 19 search-related unit tests green, androidTest compiles. * test(chat): long-session search sanity (2000 msgs) — exact counts, valid offsets Synthetic 2000-message / ~1KB session verifying the matcher at scale: - frequent word: exact 32_000 total, capped storage, every stored offset points at a real occurrence - unique word resolves to the right message; tool payloads stay excluded - local measurement: full scan ~7ms (regex version allocates per hit) --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
ff951eaf14 |
fix(chat): stop crash when expanding reasoning card with Show full (#884)
* fix(chat): stop crash when expanding reasoning card with Show full ReasoningCard applied heightIn(max = Dp.Unspecified) once "Show full" was tapped. Inside a LazyColumn item the main axis is unbounded, so the verticalScroll was measured with an infinite max height and Compose threw IllegalStateException on the next layout pass. When Show full is active the scroll wrapper is dropped entirely and the trace renders at its natural height (legal inside a LazyColumn item). Regression test reproduces the crash path: ReasoningCard in a LazyColumn item, expand, Show full, collapse. * test(chat): scroll to the reasoning toggle before asserting it The card grows taller than the viewport after Show full, so the Show less button sits below the fold. performScrollTo brings it on-screen before the assert and the follow-up click. --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
345d2e4e89 |
feat(chat): remove legacy bubble renderer, full-bleed is the only chat style (#881)
Deletes the ChatMessageList/ChatStreaming bubble path and the ChatStyle setting (issue #866 phase 6). ChatScreen now renders FullBleedChatList directly; ChatBubble becomes the user-message anchor with the shared SystemBubble/InlineAttachment/formatTimestamp helpers intact. Stubs of AuthManager.getChatStyle() removed from four test suites; TodoTaskCardTest renders ToolBubble directly. Verified: ktlintCheck green, testDebugUnitTest 962/962 pass, assembleDebug APK builds. Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
a59c8c6a4f |
feat(chat): full-bleed agent replies behind a chat style setting (issue #866) (#878)
* feat(chat): ChatStyle preference type + store field + AuthManager (issue #866) * feat(settings): chat style toggle in Appearance (issue #866) * feat(chat): plumb ChatStyle into ChatUiState + refreshSettings (issue #866) * feat(chat): full-bleed turn grouping (issue #866) * feat(chat): full-bleed agent message renderer (issue #866) * feat(chat): full-bleed compact tool rows + system events (issue #866) * feat(chat): full-bleed chat list renderer + streaming (issue #866) * feat(chat): wire full-bleed renderer behind chat style setting (issue #866) * test(theme): full-bleed contrast gate across all presets (issue #866) Gate caught NORD light primary/background < 3:1 — header role label switched to onSurfaceVariant (meta token, correct for header + passes in every preset; NORD's pastel Frost accent can't reach 3:1 on light). * test(chat): full-bleed renderer coverage (issue #866) * style(chat): ktlint formatting pass on full-bleed renderer (issue #866) * fix(chat): eagerly capture per-item state in full-bleed list (issue #866) LazyColumn item{} lambdas run at composition time, so loop locals read inside them saw final values: turn header never rendered (firstProseSeen always true at compose) and milestones misindexed. Capture showTurnHeader + milestone as immutable vals during the DSL loop. Caught by fresh-context review (decompiled Compose foundation to confirm item composition timing). * test(chat): stub AuthManager.getChatStyle in mockkObject setups (issue #866) refreshSettings()/loadSettings() now read AuthManager.getChatStyle(); the four test files that mockkObject(AuthManager) with explicit stubs had no stub for the new method, so mockk fell through to the real object and threw 'AuthManager not initialized'. Full suite: 953/953 passing, same shape as pristine origin/main. * feat(settings): move chat style toggle from Appearance to Chat section * feat(chat): hoist reasoning above tool rows in full-bleed turns Tool rows stream in before the answer message, so the turn rendered tools above the reasoning card. The turn's reasoning now renders at the top of the agent turn, with tool rows and prose underneath. Streaming is folded into the turn model (groupIntoTurnsWithStreaming) so the hoist works live while the answer is still streaming, and the detached streaming item is gone (which also fixed the duplicate turn-header quirk for streaming continuations). * feat(chat): cap reasoning card height + long-press to copy Expanded trace caps at 40% of the screen with internal scroll (Show full lifts the cap); long-press anywhere copies the reasoning. * feat(chat): blinking typing caret while streaming * feat(chat): make full-bleed the default chat style * feat(chat): remove thinking dots, show agent header above reasoning --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
5ab7ad16df |
fix(chat): /update handled client-side with progress popup (issue #862) (#873)
* fix(chat): /update handled client-side with progress popup (issue #862) * test(chat): stub actionProgress controller in ChatScreenTest (issue #862) --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
e52a9c2fe6 | feat(chat): render todo tool output as task card (#807) | ||
|
|
0edb67338d |
refactor: remove bottom navigation bar (#665 #666 #667 #668) (#672)
* refactor(nav): remove bottom NavigationBar from Scaffold (#665) * refactor(theme): remove BottomNavDisplayMode enum (#666) * refactor(settings): remove nav bar customization page (#667) * chore: clean up unused imports after bottom nav removal (#668) * chore: remove dead bottom nav infrastructure after removal - Remove SettingsNavBar NavKey (NavigationKeys.kt) - Remove bottomNavItems field from ServerStoreState + migration code - Remove _bottomNavItemsFlow, getBottomNavItems, setBottomNavItems from AuthManager - Remove bottomNavItems handling from selfHealed() (ServerCollectionOps) - Remove redundant bottomBar={} from Scaffold (Navigation.kt) - Remove unused nav bar strings from values/strings.xml + values-ko/strings.xml - Remove content_desc_move_up/down/remove_item strings - Update SettingsViewModelTest.kt (drop stale getBottomNavItems mock) - Update ServerStoreTest.kt (remove bottomNavItems assertions) --------- Co-authored-by: M57 <Hy4ri@users.noreply.github.com> |
||
|
|
bb4149eb0d |
refactor(chat): two-row ChatComposer + model/reasoning chips (#669)
* feat(chat): add reasoningLevel to ChatUiState + ViewModel methods * feat(chat): add ComposerToolbar with model + reasoning chips * refactor(chat): rewrite ChatInputBar to two-row layout * feat(chat): wire ComposerToolbar into ChatScreen * fix(chat): make model chip fixed 120dp with horizontal scroll for long names * feat(chat): reasoning chip opens dropdown menu with all 9 levels instead of cycling * fix(chat): remove Auto level from reasoning menu — backend only supports none..ultra, default is medium * feat(chat): sync model + reasoning level from backend session.resume response * fix(chat): handle session.info events for model/reasoning sync + fix null value 4002 * fix(chat): safety timeout for createNewSession + try-catch event loop * fix(chat): address agy review findings - Restore InsertDriveFile icon for non-image attachments - Add widthIn(max=120.dp) constraint on attachment filename - Restore stringResource reference for remove-attachment description - Remove dead cycleReasoningLevel() method (dropdown supersedes) - Fix createNewSession timeout race with generation counter * fix(chat): update instrumented test for two-row layout chatScreen_renders_and_acceptsInput had an assertion that the mic button disappears when text is entered — this was the OLD single-row layout behavior. The new two-row composer always shows the mic button in the toolbar row, independent of input state. --------- Co-authored-by: M57 <Hy4ri@users.noreply.github.com> |
||
|
|
e18b25d065 |
fix(ux): keep Settings highlighted in bottom nav on drill-down sub-pages (issue #637) (#643)
* fix(ux): keep Settings highlighted in bottom nav on drill-down sub-pages (issue #637) Map Settings drill-down destinations (SettingsConnection, SettingsAppearance, SettingsChat, SettingsNavBar, SettingsBehavior, SettingsAbout) back to SettingsScreen for bottom-nav selection only - navigation is unaffected. - Add selectedBottomNavDestination() helper in Navigation.kt - Extract bar-height logic into Modifier.bottomNavigationHeight() (ICON_AND_TEXT now uses heightIn(min=80.dp) so the standard bar can grow) - BottomNavigationSelectionTest (JVM): selection mapping logic - BottomNavigationLayoutTest (androidTest): per-mode bar height assertions * docs(navigation): warn that new Settings sub-pages must be added to settingsDestinations Addresses agy review (PR #643): documents the fragility of the hardcoded settingsDestinations set so future Settings drill-down screens don't silently drop the bottom-nav highlight. The sealed-class/marker-interface refactor was declined as scope creep (issue #637 explicitly scopes Settings-only; keys are flat data objects). * fix: language selector --------- Co-authored-by: M57 <Hy4ri@users.noreply.github.com> |
||
|
|
4745728920 |
feat(#365): add pin support for models with pinned section at top (#414)
* feat(#365): add pin support for models with pinned section at top * fix(#365): stub Pinned Models methods in E2eIntegrationTest * fix(#365): use state flow for pin/unpin, add max cap, add ViewModel tests --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
42a67bffa9 |
feat(#194): add Speech-to-Text voice dictation support in chat (#413)
* feat(#194): add Speech-to-Text voice dictation support in chat * fix(#194): resolve android lint warnings and update compose UI test * fix(#194): remove invalid assertDoesNotExist import |
||
|
|
8f14aca647 |
perf: optimize startup, database, and ui rendering (#405)
* perf: optimize startup, database, and ui rendering * fix(#405): address code review feedback * test: wait for async prefs initialization to prevent coroutine leaks * test: stub mockViewModel.streamingState in ChatScreenTest to prevent ClassCastException on Android |
||
|
|
15a4748634 |
test(#292): coverage gaps TEST-04 through TEST-14 (#302)
* test(#292): coverage gaps TEST-04 through TEST-14 - TEST-04: ApiClient auth interceptor + BuildConfig.DEBUG gate (6 tests) - TEST-05: SettingsViewModel profile operations (5 tests) - TEST-06: ConnectViewModel profile switch edge cases (8 tests) - TEST-07: EventParser untested subtypes (12 tests) - TEST-08: AuthManager token routing through profiles (5 tests) - TEST-09: ChatScreen instrumented compose tests (3 tests) - TEST-10: WS reconnect state recovery (5 tests) - TEST-11: Multi-profile token scenarios (6 tests) - TEST-12: Chat DAO Room operations (11 tests) - TEST-13: ChatViewModel slash command handling (8 tests) - TEST-14: Predictive back gesture instrumented test (1 test) Total: 70 tests across 11 files. CI updated with new instrumented-tests job via android-emulator-runner. * fix(ci): resolve ktlint, tests, and emulator failures * fix(ci): resolve test failures on PR #302 * fix(ci): more ChatMessageDaoTest context mocks, re-read state after advanceUntilIdle in connect test * fix(ci): remove problematic packageManager mock, decouple instrumented tests from CI gate * fix(ci): switch emulator to default target, add more cores, memory, and timeout * fix(ci): remove mockkStatic(Log::class) from ChatMessageDaoTest - fails on Android stubs * fix(ci): use aosp_atd emulator image + KVM + AVD cache for fast instrumented tests * fix(ci): remove ApplicationInfo mock - Android stub class breaks MockK * fix(ci): rewrite ChatMessageDaoTest to use HermesDatabase.setForTest() pattern - Room in-memory DB requires Android SDK not available in JVM tests * fix(ci): use actions/cache@v4 instead of @v7 (doesn't exist) * fix(ci): add missing performClick import in StateViewsTest * fix(ci): mock ChatViewModel in instrumented tests, add mockk to androidTestImplementation * fix(ci): add asStateFlow import in ChatScreenTest * fix(ci): exclude duplicate META-INF LICENSE/NOTICE files from androidTest build JUnit Jupiter 5.8.2 JARs each ship META-INF/LICENSE.md, causing mergeDebugAndroidTestJavaResource to fail with 'duplicate files found'. Add LICENSE.md, LICENSE, NOTICE, and NOTICE.md to the packaging exclusion list. * fix(#302): exclude duplicate META-INF/LICENSE-notice.md from instrumented test APK JUnit Jupiter 5.x JARs all ship META-INF/LICENSE-notice.md, causing mergeDebugAndroidTestJavaResource to fail. * fix(#302): add LICENSE-notice.md as separate exclude entry The Ant-style glob brace expansion in packaging.resources.excludes doesn't handle LICENCE-notice.md inside the {}-enclosed pattern. Adding it as a separate entry instead. * fix(#302): use mockk-android for instrumented tests io.mockk:mockk uses Java instrumentation agents not available on Android. Switch to io.mockk:mockk-android for connectedDebugAndroidTest which uses an Android-compatible mocking strategy. * fix(#302): use createAndroidComposeRule for test lifecycle isolation Standard createComposeRule has a gap where consecutive test methods can lose the compose hierarchy on the emulator. Switching to createAndroidComposeRule<MainActivity> gives explicit activity lifecycle management across test methods, fixing the 'No compose hierarchies found' errors. * fix(#302): use createComposeRule instead of createAndroidComposeRule<MainActivity> MainActivity.onCreate() already calls setContent, so the test's setContent { ChatScreen(...) } crashed with 'has already set content'. Switch to createComposeRule() which launches a bare ComponentActivity that doesn't call setContent, allowing the test to compose ChatScreen in isolation with a mock ChatViewModel. * fix(#302): use createAndroidComposeRule<ComponentActivity> + unmockkAll for instrumented tests ChatScreenTest was failing with 'No compose hierarchies found' because mockk mocks in instrumented tests weren't cleaned up between test runs. Switch from createComposeRule() to explicit createAndroidComposeRule<ComponentActivity>() and add @After tearDown() with unmockkAll() to ensure mock state doesn't leak between test methods. * fix(#302): wrap ChatScreen in HermesControlTheme in instrumented tests ChatScreen accesses MaterialTheme.colorScheme during composition (background gradient). Without a Material3 theme wrapper, the composable silently crashes and no compose hierarchy is registered — causing 'No compose hierarchies found' on any subsequent onNodeWithTag() call. Fix: wrap ChatScreen in HermesControlTheme inside setContent {}, matching how MainActivity renders the app. Also use createAndroidComposeRule<ComponentActivity> instead of createComposeRule() for explicit lifecycle management, and add unmockkAll() in @After to clean up mockk state between tests. * fix(ci): fix packaging resource paths and use pickFirsts for duplicate META-INF files Remove leading slashes from META-INF paths (AGP paths are relative, not absolute). Use pickFirsts instead of excludes to handle duplicate files from JUnit Jupiter JARs. * fix(#302): use createAndroidComposeRule<ComponentActivity> for test isolation createComposeRule() has a known race where the compose hierarchy is lost between sequential test methods on the emulator. createAndroidComposeRule<ComponentActivity> provides proper Activity lifecycle management while using the same lightweight empty activity that doesn't pre-set content (unlike MainActivity). * fix(#302): consolidate ChatScreenTest into single test method The Compose test rule's lifecycle gap between test methods on the emulator causes 'No compose hierarchies found' on consecutive setContent calls. Consolidating all three test scenarios into one invocation eliminates the cross-test teardown issue entirely. * fix(#302): mock ContextCompat to suppress permission dialog in test The ChatScreen LaunchedEffect requests POST_NOTIFICATIONS via ActivityResultContracts.RequestPermission(), which pauses the activity and disposes the compose hierarchy. Mocking ContextCompat.checkSelfPermission to return PERMISSION_GRANTED prevents this from happening during the test. * fix(#302): set connectionStatus=CONNECTED in ChatScreenTest The default ChatUiState has connectionStatus=DISCONNECTED, which makes isConnected=false, in turn disabling the chat input field (enabled = isConnected) and the send button (canSend checks isConnected). Set it to CONNECTED so both controls are active. * fix(#302): add ConnectionStatus import and fix ktlint wrapping --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com> |
||
|
|
bbe3b00ccb |
test(#293): add foundational Compose UI test for StateViews (TEST-15) (#301)
Co-authored-by: M57 <hy4ri@users.noreply.github.com> |