Mirror Android L1 + L2 coverage to chat-component + wire testIds (+37 Vitest) - #72
Open
phwizard wants to merge 3 commits into
Open
Mirror Android L1 + L2 coverage to chat-component + wire testIds (+37 Vitest)#72phwizard wants to merge 3 commits into
phwizard wants to merge 3 commits into
Conversation
Adds roomsSlice.test.ts — 17 hermetic reducer tests targeting the
same field-bug clusters covered by the Android SDK's L1 suite and the
iOS SDK's RoomStoreTests, ported to the Redux Toolkit slice shape.
The web's `roomMessages` slice unifies what Android splits between
RoomStore and MessageStore (rooms hold their messages inline). Tests
call the reducer directly with (previousState, action) — pure, no
store/middleware/thunks, ~7ms total runtime.
Cluster mapping (see QA_SCENARIOS.md in the ethora monorepo):
Cluster A — multi-room state machine (+7):
- addRoom inserts new room keyed by jid
- addRoom preserves existing unread + lastViewed on upsert
(REST refreshes must not wipe the badge)
- addRoom rejects invalid jids without mutating state
- setCurrentRoom transitions A → B → A
- deleteRoom drops only the targeted room
- updateRoom merges partial updates without dropping fields
- setLastViewedTimestamp writes timestamp back onto the room
(and documents the normalize-to-ms contract via
getTimestampFromUnknown)
Cluster D — send + duplication (+4):
- addRoomMessage appends a new message
- addRoomMessage idempotent on duplicate id (MAM-replay safety)
- addRoomMessage merges pending + echo across optimistic/server
ids (bidirectional id/xmppId match)
- addRoomMessage cross-room — sending to A doesn't touch B
Cluster F — history + render parity (+4):
- setRoomMessages round-trips content
- replaceRoomMessages sorts by timestamp ascending
- deleteRoomMessage tombstones the bubble (web contract —
preserves row with isDeleted=true; documents divergence from
iOS which drops the row outright)
- editRoomMessage updates the body in place
Per-room state isolation (+2):
- setComposing on room A doesn't bleed into room B
- setComposing transitions cleanly true → false on same room
Verified: \`npx vitest run\` → 21 tests, 0 failures (was 4 before
this PR — Login.test.tsx; +17 net).
Login.test.tsx +5 tests (4 → 9) pinning real UX contracts the form
must hold:
- shows BOTH email and password errors when both fields are blank
(regression guard: a single submit must surface both unrelated
validation paths — not let the second wipe the first)
- accepts a password of exactly 6 characters
(boundary — guards against the classic off-by-one if the check
flips from `< 6` to `<= 6`)
- surfaces a "wrong data" error when the server returns 401
(the documented 401 recovery path that re-uses the
passwordError slot)
- does not render error texts on a successful login
(regression guard against stale errors persisting through a
successful flow)
- re-submitting with corrected input replaces stale error text
(validates the `errors.email && ...` render-guard — a merge-
instead-of-replace regression would leave the corrected field
showing the old error)
Web's L2 component test infrastructure (Vitest + RTL +
renderWithProviders) is the canonical equivalent of Android's
Compose UI tests and iOS's ViewModel tests. Existing AuthTestIds
data-testid wiring on Login.tsx makes these tests robust to copy
changes — they query by ID, not by visible text.
Verified: \`npx vitest run\` → 26 tests across roomsSlice.test.ts
+ Login.test.tsx, 0 failures.
…ist + tests
Wires the cross-platform selectors from src/testIds.ts onto the
actual React components, then exercises each via component tests.
Before this commit, testIds.ts shipped the contract but no component
rendered it — the matching Android testTags and iOS
accessibilityIdentifiers had no web counterpart to test against.
Wiring (production-code changes):
- SendInput.tsx → ChatInputTestIds.{attachButton,inputField,sendButton}
on the paperclip, both input variants (textarea + single-line),
and the primary send button. Secondary-send variant intentionally
not wired (the primary button is the canonical send affordance).
- MessageImage.tsx → MessageBubbleTestIds.mediaContent on both
image branches (with fileURL and the placeholder fallback) so a
cross-platform flow doesn't need to know which branch rendered.
- RoomList.tsx → RoomListTestIds.{roomsList,searchInput} on the
list container and the search input.
- ChatRoomItem.tsx → RoomListTestIds.roomRow on the per-row
container (`<ChatItem>`).
- NewChatModal.tsx → RoomListTestIds.createRoomButton on the "+"
trigger Button.
Tests (+15 across 3 new files):
SendInput.test.tsx (+6):
- renders inputField + attachButton
- typed text populates the controlled input
- sendButton is hidden until the user types (no accidental empty sends)
- typing reveals sendButton; clicking it dispatches sendMessage
with the typed text
- config.disableMedia=true hides the attachButton
- placeholderText prop overrides the default "Type message" copy
RoomList.test.tsx (+6):
- renders roomsList + searchInput + createRoomButton
- renders one roomRow per chat
- typing in searchInput filters rows by title
- clicking a roomRow fires onRoomClick with that room object
- hideSearch config hides searchInput
- disableCreate config hides createRoomButton
MessageImage.test.tsx (+3):
- renders the <img> with the mediaContent testid + correct src
- placeholder branch (no fileURL) still carries the same testid
so cross-platform flows don't need to know which branch rendered
- clicking the image dispatches the file-preview modal (the only
opener for the full-screen preview)
Verified: \`npx vitest run\` → 41 tests across 5 files, 0 failures.
Unhandled Firebase Messaging warnings during test runs are unrelated
noise from a transitive import — they're "Unhandled Errors" not test
failures, every test passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds `roomsSlice.test.ts` — 17 hermetic reducer tests targeting the same field-bug clusters covered by the Android SDK's L1 suite and the iOS SDK's `RoomStoreTests`, ported to the Redux Toolkit slice shape.
The web's `roomMessages` slice unifies what Android splits between `RoomStore` and `MessageStore` (rooms hold their messages inline). Tests call the reducer directly with `(previousState, action)` — pure, no store/middleware/thunks, ~7 ms runtime total.
Cluster mapping (per QA_SCENARIOS.md in the ethora monorepo)
Cluster A — Multi-room state machine (+7)
Cluster D — Send + duplication (+4)
Cluster F — History + render parity (+4)
Per-room state isolation (+2)
Test results
`npx vitest run` → 21 tests (this PR +17, Login.test.tsx 4), 0 failures.
Test plan