docs(ui): make docs snapshots authentic to the production SDK#2676
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
3e10e67 to
498f38c
Compare
Doc snapshots previously rendered differently depending on the host running the tests (macOS dev machines vs Linux CI) because three platform reads were unpinned: `CurrentPlatform.type`, `defaultTargetPlatform`, and `Theme.of(context).platform`. Code paths conditional on any of these would flip — most visibly the mic button visibility in `StreamMessageComposer`. Introduce `docsGoldenTest`, a wrapper around alchemist's `goldenTest` that pins all three platform reads to iOS: - `CurrentPlatform.debugCurrentPlatformOverride` and `debugDefaultTargetPlatformOverride` are set inside the alchemist `pumpWidget` hook (i.e. inside the test body) and reset via `whilePerforming`'s cleanup callback — the latest hook before `_verifyInvariants` enforces that foundation debug vars are null. - `Theme.of(context).platform` is pinned via `ThemeData.platform` on `docsScreenshotsTheme()`. Also adopt the `GoldenComponentFactory` from #2649, wired automatically into every snapshot through the same `pumpWidget` hook so no per-test plumbing is needed. It overrides the `emoji` component builder to add an 8 px top padding, nudging emoji glyphs to align with surrounding text. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
498f38c to
db011e0
Compare
Roboto carries no emoji glyphs, so any inline emoji in plain Text widgets (message bodies, etc.) would render as tofu. Add the emoji font families loaded by `_loadEmojiFont` to the textTheme's fallback chain so Flutter finds the glyph automatically. Defensive change — no current snapshot has inline emoji in plain text; goldens unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pinning the platform to iOS makes Material's iOS typography request the `CupertinoSystemDisplay` and `CupertinoSystemText` family aliases. On a real device the platform resolves those to SF Pro automatically; the Flutter test renderer has no such mapping, so until now we worked around it by overriding everything to Roboto. Register `/System/Library/Fonts/SFNS.ttf` under both alias family names via `FontLoader` in `_loadAppleSystemFont`. With that in place we can drop the `fontFamily: 'Roboto'` override on `ThemeData` and switch Stream's `StreamTextTheme` to `CupertinoSystemText` — Material widgets, message text, and headings all now render in authentic SF on macOS. The loader is a no-op outside macOS. Linux CI doesn't have SFNS.ttf, but its goldens use `obscureText: true` so every glyph is replaced with an Ahem block regardless — no behavioral change there. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_loadEmojiFont` and `_loadAppleSystemFont` both register absolute-path host system fonts that aren't in any asset manifest. Wrap them in a single `_loadHostSystemFonts()` so `testExecutable` reads as two awaits (asset-manifest fonts, then host fonts) instead of three. Pure refactor — no behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-aware runners The single Linux job previously regenerated `goldens/ci/*.png` for every alchemist-dependent package — which is what the SDK packages commit, but not what `docs_screenshots` commits. Docs snapshots commit `goldens/macos/*.png` (real-text variant rendered with SF on macOS); the Linux runner can never produce them. Split the workflow by adding a `workflow_dispatch.inputs.target` choice (`sdk` | `docs`) that routes to one of two jobs: - `update_sdk_goldens` on ubuntu-latest — runs the new `update:goldens:sdk` melos script, which skips `docs_*` packages. Auto-commits `packages/**/goldens/**/*.png`. - `update_docs_goldens` on macos-latest — runs `update:goldens:docs`, scoped to `docs_*` and exporting `UPDATE_PLATFORM_GOLDENS=1`. Auto-commits `docs/**/goldens/macos/*.png`. `UPDATE_PLATFORM_GOLDENS` is a carve-out in `docs/docs_screenshots/test/flutter_test_config.dart`: by default the CI variant gate (`isRunningInCi → CiGoldens only`) skips the platform snapshots, which is correct for any other runner; setting the env var flips PlatformGoldens back on so the macOS job actually regenerates the files it's there for. The macOS job stays manual-dispatch only and is opt-in via the choice input — macOS runner minutes cost ~10x Linux on GitHub Actions, so defaulting to `sdk` keeps routine regenerations cheap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.gitignore` for docs_screenshots commits only `goldens/macos/*.png` — the CI/obscured variant exists transiently on disk but is never inspected and never compared against anything in the repo. Generating it wastes test time and obscures intent. Simplify `docs/docs_screenshots/test/flutter_test_config.dart` to enable only `PlatformGoldensConfig` regardless of the host. The `UPDATE_PLATFORM_GOLDENS` env-var carve-out is no longer needed (the gate it flipped is gone), so drop it from `update:goldens:docs` in melos.yaml. Side effect: with PlatformGoldens always on, running docs_screenshots tests on a Linux runner would compare against a nonexistent `goldens/linux/` set and fail. Add `--ignore="docs_*"` to the `test:flutter` melos script so the regular Linux CI pipeline skips them entirely — docs goldens are regenerated and verified through the dedicated macOS `update_docs_goldens` job instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`DefaultStreamEmoji` carries its own `fontFamilyFallback` chain pointing at the platform emoji fonts, and that's the path every emoji in the current snapshot set takes. No test renders an inline emoji glyph through a plain Text widget, so the theme-level fallback was defending a case that doesn't exist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the candidate-list pattern in `_loadEmojiFont`: walk a list of absolute font paths and bail at the first one that exists. Catches pre-Catalina macOS where the system font ships as `SFNSDisplay.ttf` instead of the modern `SFNS.ttf`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream's `StreamAvatar` / `StreamNetworkImage` / `CachedNetworkImage` chain doesn't fetch real network bytes in tests — and even if it tried, `flutter_test`'s HttpClient short-circuits every request. Up until now every `User(image: 'https://…')` rendered as the gradient-letter placeholder, which makes the docs look like nothing's loading. Intercept the chain at the `StreamComponentFactory.networkImage` hook: `GoldenComponentFactory` now installs a builder that returns an `Image.memory` backed by one of 18 portrait fixtures sourced directly from Stream's Chat SDK Design System Figma (node 2867:55922, "User Photo"). Files live in `test/fixtures/avatars/` and are named to match the Figma component names (e.g. `amelia-moore.png`); add or replace one by dropping a PNG there and listing its slug in `_avatarSlugs`. URLs hash deterministically into the list, so the same `User(image: 'https://docs.fixture/avatar/bob.png')` always resolves to the same person across snapshots. To make this actually fire across the suite: - `setupMockChannel` stubs `channel.image` and the default member User's image to synthetic `https://docs.fixture/avatar/{id}.png` URLs. Every channel previously had `channel.image: null`, so the channel-avatar branch never even reached `StreamNetworkImage`. - Sweep across `User(id: '…')` constructions in test files — same `image:` URL pattern derived from the id — so user avatars in poll, thread, message, and search snapshots route through the same fixture. - `docsGoldenTest` default `pumpBeforeTest` is now alchemist's `precacheImages` (was `onlyPumpAndSettle`). Without that pump, `MemoryImage` decode races the frame and the avatar circle shows the default background color instead of the actual bytes. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vatars Two related cleanups, both about making the snapshots reflect a real chat in a real product. **Sample users in one file.** Inline `User(id: 'user-2', name: 'Bob', …)` constructions were scattered across nine test files with arbitrary IDs that didn't match anything in the design system. Move all of them into `test/src/sample_users.dart` as named constants (`ameliaMoore`, `noahSmith`, …) corresponding to the 18 named users in Figma node 2867:55922. Tests now reference users by name; adding or replacing a sample user is one constant + one fixture PNG. **Channel avatars derive from members.** `setupMockChannel` previously stubbed `channel.image` to a synthetic URL, so every channel snapshot took the `channel.image` branch in `StreamChannelAvatar` and rendered a single circle. Real chats almost never set `channel.image` on group channels — the avatar is derived from the member set. Stub `channel.image` back to `null`, default the member list to three sample users so the channel-list snapshot shows the stacked group avatar from `StreamUserAvatarGroup`. Distinct DM channels with two members render the other member's avatar, matching production behavior. Also along the way: - `golden_network_image.dart` now resolves URLs by slug (extracted from the last path segment), with a hash-based fallback for dynamic URLs like `user-$messageId` that don't match a known slug. `User(image: 'https://docs.fixture/avatar/amelia-moore.png')` now deterministically renders Amelia regardless of how the URL hashes. - `asOwnUser(user)` helper on `sample_users.dart` for `currentUser` stubs. - `user_list_view` snapshot iterates the full 18-user roster with a seeded `Random(42)` toggling online indicators — varied without being flaky. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default channels all picked the same 3 members (Amelia, Charlotte, Elena), so every channel-list row rendered the same stacked group avatar. Switch the `setupMockChannel` fallback to a per-channel deterministic pick: seed a `Random` off the channel id, choose between 2 and 6 sample users, shuffle them in. Each channel now gets a unique group avatar (Engineering happens to land on 6 → triggers the "+4" overflow chip from `StreamUserAvatarGroup`, exactly like a real busy channel would). Determinism comes from a stable djb2-ish content hash, not Dart's process-randomized `String.hashCode` — so the same channel id always picks the same member set across runs. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The full 18-user roster overflows naturally — keeping the snapshot at the prior 500 px height keeps it consistent with the surrounding fixtures (channel previews, message lists) and avoids a one-off tall golden in the docs grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`_makeSearchResult` previously built inline `User(id: 'user-$messageId',
…)` so the URL slug couldn't match any fixture and the avatar resolved
through the hash fallback — names ("Alice", "Bob", "Carol", "Dave")
ended up paired with arbitrary portraits. Pass a `User` directly so the
slug aligns with the photo, and use design-system constants (Amelia,
Noah, Charlotte, Liam) so the names match the rest of the docs.
Side benefit: the first row now reads "You in General" because
`ameliaMoore` is the current user — accurate to how
`StreamMessageSearchListView` renders the current user's own messages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A single [OwnUser] constant in `sample_users.dart` replaces the per-file `final _currentUser = ameliaMoore;` / `final _user1 = …;` aliases and the ad-hoc `asOwnUser(ameliaMoore)` calls scattered across tests. Because `OwnUser` is-a `User`, the same constant drops into both `currentUser` stubs and any place a `User` is expected (message authors, member lists, etc.) — so "who is signed in" is decided in exactly one place across every doc snapshot. `asOwnUser` is kept for the rare case where a test needs to promote a different sample user (e.g. variant tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the original snapshot's row count so the diff against the prior golden stays compact — the design-system roster is still demonstrated elsewhere via avatars rendering with the new fixture set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mple app
The chat-list, thread-list, and swipe-action snapshots were dressed in
generic Material chrome — `AppBar('Stream Chat')`, a fictional
`Chats / Mentions / Threads` bottom nav, a red "Delete" swipe-action.
Real Stream apps look nothing like that, so the docs misrepresented the
product.
Rework the chrome to match `sample_app`:
- **Channel list & slidable channel list** (`channel_preview_test.dart`)
swap the generic `AppBar` for `StreamChannelListHeader(title:
Text('Chats'))`. The leading slot resolves to the signed-in user's
avatar (Amelia) via the existing `currentUser` stub.
- **Swipe channel reveal** (same file) shows
`streamIcons.more` (surface bg) + `streamIcons.mute` (accent bg) — the
exact actions `sample_app/lib/widgets/channel_list.dart` ships.
- **Thread list** (`thread_list_view_test.dart`) uses
`StreamChannelListHeader(title: Text('Threads'))` and a four-tab
bottom nav: `Chats` (`streamIcons.messageBubble` / `messageBubbleFill`),
`Threads` (`streamIcons.thread` / `threadFill`, active),
`Drafts` (`Icons.drafts_outlined` / `drafts_rounded`), and
`Reminders` (`Icons.bookmark_outline_rounded` / `bookmark_rounded`).
The Material icons mirror the sample's actual choices — Stream
doesn't ship a Drafts or Reminders icon.
- **Poll creator AppBar** (`poll_test.dart`) replaces `Icons.close` and
`Icons.send` with `streamIcons.xmark` and `streamIcons.send` —
thinner, design-system shapes.
Goldens regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ist headers Two follow-ups now that we're using the SDK's header components: - **Poll creator** swaps Material `AppBar` + `IconButton` for `StreamAppBar` + `StreamButton.icon`, the toolbar + button primitives the SDK ships for modal/dialog chrome. Circular icon buttons, hairline bottom divider, design-system padding — same shape any production app gets for free when using these widgets. - **Channel list, slidable channel list, and thread list** add a trailing `StreamButton.icon(icon: Icon(icons.plus))` to `StreamChannelListHeader`. The docstring for `StreamChannelListHeader` calls this out as the canonical "new chat" affordance, and rendering it makes the header's three-slot shape (avatar leading / title / action trailing) immediately visible to readers. Also revert `user_list_view_test.dart` to the explicit five-User array style — keeps the original visual layout and drops the dart:math import. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r demos
Two follow-on tweaks to make the header demos cover the full SDK API:
- Poll creator now uses `StreamSheetHeader` (the SDK's modal/dialog
toolbar) instead of `StreamAppBar`. Visually similar but semantically
matches what a sheet-launched poll creator actually renders in
production. Skipped `automaticallyImplyLeading: true` because our
test scaffold has no popable route — kept the explicit leading
button.
- `channel_list_header_test.dart` now wires `trailing: StreamButton.icon(
icon: Icon(context.streamIcons.plus))` on both snapshots. Combined
with the existing leading (avatar) and `subtitle: Text('12 channels')`,
the "custom subtitle" snapshot now demos every slot of
`StreamChannelListHeader` in a single golden.
Goldens regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t/threads Two corrections that bring the docs closer to the production SDK: - **Message widget actions** stopped reinventing the action list. The snapshot used to render Material `Icons.*` against hand-rolled placeholder action types — visually plausible but missing the SDK's canonical "Mark as Unread" and "Flag Message" entries, and unaware of channel capabilities. Switch to `StreamMessageActionsBuilder.buildActions(context: ..., message: ..., channel: ..., currentUser: ...)` so the modal renders exactly what a user sees on long-press: Stream icons, translated labels, capability- aware visibility. Channel mock gets the full capability set (`sendReply`, `pinMessage`, `quoteMessage`, `readEvents`, `update*Message`, `delete*Message`) so every default action surfaces. - **Thread list bottom nav** trims `Chats / Threads / Drafts / Reminders` to just `Chats / Threads` (matches what `sample_app` actually shows by default; Drafts and Reminders are config-gated additions there). Theming wraps the `BottomNavigationBar` in a `DecoratedBox` with `backgroundElevation1` + `borderSubtle` top divider, sets `elevation: 0`, `iconSize: 20`, `textPrimary`/`textTertiary` item colors, and `metadataEmphasis` label styles — copied verbatim from `sample_app/lib/pages/channel_list_page.dart`. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry thread authorship Four corrections to the SDK alignment: - Message actions modal wraps the builder output in `StreamContextMenuAction.partitioned(...)` so the destructive **Delete Message** lands at the end with a separator above it — the canonical grouping the SDK exposes for that purpose. - Poll creator's `StreamSheetHeader` buttons now use `StreamButton.icon(type: .outline, style: .secondary)` for the leading dismiss button (matching the SDK's auto-implied leading on sheet routes) and `type: .solid` for the trailing confirm — the pattern `sample_app/lib/widgets/add_members_sheet.dart` uses. - `_makeThread` now takes a `parentAuthor` so the thread list renders mixed authorship — one thread started by the current user (rendered as "You: …"), the others by Charlotte and Noah. Previously every thread was authored by `ownUser`, which made the list look like a single-user digest. - Thread list empty state drops the hand-rolled `Center(Column(...))` and lets `StreamThreadListView` render its default `StreamScrollViewEmptyWidget` (messageBubblesLarge icon + translated "Reply to a message to start a thread") — same widget production users see. Goldens regenerated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on list `partitioned` adds Flag and Mute on top of Reply/Thread/Mark/Copy/Edit/ Pin/Delete, so the previous 500 px constraint clipped the modal. 700 px fits the full list with breathing room. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…odal height Two cleanups: - `slidable_channel_list` was stubbing `currentUser` *before* the `fakeChannel` calls, so each `setupMockChannel` invocation overwrote `client.state` with its own `MockClientState()` (whose default `currentUser` is `OwnUser(id: 'user-id')`, the "U" placeholder). Move the stub after the channels are created so it wins, matching the order `channel_list_view` already uses. The header now renders Amelia's avatar properly. - `message_widget_actions` was at 700 px after adding the full action list — visibly too tall. Trim to 620 px so the modal fits with comfortable margin and no dead space below. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous `.gitignore` pattern `!**/goldens/macos/*` only un-ignored macos files relative to a (missing) preceding ignore rule, so the `ci/` subdirectory was being tracked alongside `macos/`. With `flutter_test_config` already disabling `CiGoldensConfig.enabled`, the `ci/` files were stale leftovers from before that change — committing them was pure noise. Switch the ignore to `**/test/**/goldens/**` (recursively ignore everything under any goldens dir) with `!**/test/**/goldens/macos/**` re-allowing the macos subtree. Delete the orphan `ci/` PNGs so the docs only ship the platform variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StreamChannelInfo` (the default subtitle for `StreamChannelHeader`) branches on `channel.memberCount`: > 2 → "N members, M online", else the DM-style "Last online …". Our mock left `memberCount` un-stubbed, so it resolved to null and every snapshot fell into the DM branch — yielding a misleading "Last online a few seconds ago" for channels that visibly have 3+ avatars stacked. Stub `memberCount` (and `memberCountStream`) from the configured member list in `setupMockChannel`. Group-channel snapshots now read "3 Members" (or whatever the per-channel deterministic count is), matching what users actually see. `channel_list_header` snapshots were already correct — they use the connection-state default title with no subtitle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gesture
The slidable snapshots were hand-rolled — Row+Stack+Transform.translate
faking a swiped state with hardcoded action icons. Replace with the
actual `flutter_slidable` setup the sample app uses
(`sample_app/lib/widgets/channel_list.dart`):
`Slidable` with `endActionPane: ActionPane(extentRatio: 0.4, motion:
BehindMotion(), children: [more, mute])`. For the multi-channel test
the wrap goes through `StreamChannelListView.itemBuilder` so every row
gets the same slidable.
`whilePerforming: (tester) async { await tester.drag(...); await
tester.pumpAndSettle(); }` actually performs the gesture before the
snapshot. The result is a real animated reveal instead of a fake
translation, which catches any drift in Slidable's API or our usage.
Adds `flutter_slidable: ^4.0.0` to docs_screenshots dev_dependencies.
Drops the now-unused mocktail import in this file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StreamPollCreatorSheet` (the production widget) wires its trailing `StreamButton.icon` to `context.streamIcons.checkmark`, not `send`. Our snapshot was showing a paper-plane icon which doesn't appear anywhere in the real poll creation flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Yes, we now use users from the list we created
Summary
Reworks the
docs_screenshotsgolden suite so every snapshot looks like the real app instead of a Material approximation. The pivots:CurrentPlatform,defaultTargetPlatform, andThemeData.platformto iOS in adocsGoldenTestwrapper. Set inside alchemist'spumpWidgethook, reset viawhilePerforming's cleanup callback — the only timing that satisfies_verifyInvariants. Snapshots render identically on macOS dev and Linux CI._loadAppleSystemFontregisters/System/Library/Fonts/SFNS.ttfunder theCupertinoSystemDisplay/CupertinoSystemTextfamily aliases Material's iOS typography expects. Apple Color Emoji loaded the same way. No more Roboto stand-in.2867:55922("User Photo") live undertest/fixtures/avatars/. AnetworkImageStreamComponentBuilder(installed viaGoldenComponentFactory) resolves any URL by its slug (https://docs.fixture/avatar/amelia-moore.png→ that fixture). Adding a new avatar is two lines: drop a PNG + add the slug to_avatarSlugs.test/src/sample_users.dartexports 18 namedUserconstants matching the Figma roster (ameliaMoore,noahSmith, …) plus a singleownUser(OwnUser) for the signed-in identity used across every snapshot.User(id: 'user-2', name: 'Bob', …)literals are gone.StreamChannelListHeadereverywhere a genericAppBarused to live (withtrailing: StreamButton.icon(icons.plus)), bottom nav copies sample'sChats / Threadstwo-tab theming verbatim,StreamSheetHeaderfor the poll creator, message-actions modal usesStreamMessageActionsBuilder.buildActions+StreamContextMenuAction.partitionedso Delete lands at the end under a separator. Empty states use the SDK's defaultStreamScrollViewEmptyWidget.Slidable(sample app's exactActionPanewithBehindMotion,more+mute) and trigger the reveal viawhilePerforming: (tester) async { await tester.drag(...); await tester.pumpAndSettle(); }. ~70 lines ofStack + Transform.translatefakery deleted.setupMockChanneldefaults to a deterministic 2–6 member group seeded offchannel.id(so the Engineering channel happens to surface the +N overflow chip), stubschannel.memberCountso group headers read "3 Members" instead of misleading "Last online a few seconds ago".update_goldens.ymlgained atarget: sdk|docsdispatch input — SDK target stays onubuntu-latest, docs target runs onmacos-latestfor authoritative platform-variant regen. Auto-commit patterns are scoped so the two jobs never fight.!**/goldens/macos/*was a no-op without a preceding ignore. Now**/test/**/goldens/**ignores by default with!.../macos/(re-include the directory) +!.../macos/**(re-include files) — both lines required because git can't re-include a file beneath an ignored directory.What this fixes / adds
AppBar('Stream Chat')everywhereStreamChannelListHeaderwith avatar leading + plus trailingTransform.translateslidableSlidable+ real swipe gesturechannel.image: null→ "Last online a few seconds ago" on group channelsmemberCount→ "3 Members"Test plan
fvm flutter analyze— no issues across the suitefvm flutter test(no--update-goldens) — 41/41 passfvm flutter test --update-goldens— clean regeneration, all goldens stable across local runsupdate_goldensworkflow withtarget=docson macOS — verified end-to-end, bot auto-commits PNGs scoped todocs/**/goldens/macos/*.png🤖 Generated with Claude Code
📦 Migration guide — bringing #2649's snapshots onto this branch
#2649 ("docs(ui): more docs screenshots") adds four new snapshot scenarios (
flutter_showcase,localization_support,theming,stream_message_composerextensions) plus aGoldenComponentFactoryand adocsScreenshotsDarkTheme(). This branch reshapes most of that surface area. Here's how to land #2649's content on top of these patterns.TL;DR
User(id: 'user-1', name: 'Mace Windu', …)test/src/sample_users.dart—ameliaMoore,noahSmith, etc.final _currentUser = OwnUser(id: 'user-1', name: 'Alice')ownUser(already anOwnUser, single source of truth)goldenTest('…', …)docsGoldenTest('…', …)— pins platform, wraps inGoldenComponentFactory, precaches imagesGoldenComponentFactorywrapped inside every scaffolddocsGoldenTest— drop the manual wrapimport 'package:alchemist/alchemist.dart';final _redTheme = ThemeData(…)hand-builtdocsScreenshotsTheme()(preservesplatform: TargetPlatform.iOS,fontFamily: CupertinoSystemText, etc.)stream_chat_localizationsadded topubspec.yamldev_dependencies, notdependenciesFile-by-file
test/src/golden_component_factory.dartAlready on this branch — delete #2649's copy entirely and accept ours. It also overrides
networkImageto render avatar fixtures, which #2649's version doesn't.test/src/golden_theme.dart#2649 adds
docsScreenshotsDarkTheme(). Keep that addition, but rebuild it from our light theme so it inherits the platform pin and SF font setup. Sketch:test/theming/theming_test.dartfinal _currentUser = OwnUser(id: 'user-1', name: 'Mace Windu')→ownUser.final _otherUser = User(id: 'user-2', name: 'Lando Calrissian')→ pick a sample user (e.g.noahSmith). If the original "Mace / Lando" identities matter for the brand demo, add them tosample_users.dartand ship matching PNGs totest/fixtures/avatars/._redTheme()should compose fromdocsScreenshotsTheme()— copy its body and overrideextensionswith the redStreamColorScheme. Keep theplatform:andfontFamily:lines so iOS typography + SF font still resolve.docsGoldenTest(...)instead ofgoldenTest(...).GoldenComponentFactory(child: ...)wrap —docsGoldenTestalready handles it.test/localization/localization_support_test.dartUser(id: 'user-1', name: 'Alice')→ownUser.StreamChannelinMaterialApp. Usetheme: docsScreenshotsTheme()so SF + Roboto fallback work in every locale.stream_chat_localizations: ^10.0.0-beta.13todev_dependencies(notdependencies— the docs package ispublish_to: none).docsGoldenTest. Remove manualGoldenComponentFactory.test/flutter_showcase/flutter_showcase_test.dartdocsScreenshotsTheme()and the newdocsScreenshotsDarkTheme()(see above).User(id: 'user-2', name: 'Bassett')etc. → pick fromsampleUsers. If you need the actualbassettidentity for the marketing image, add it tosample_users.dart+ drop a PNG intotest/fixtures/avatars/.docsGoldenTest. Remove any explicitGoldenComponentFactorywraps.setupMockChannelnow defaults to a deterministic 2–6 member group and stubschannel.memberCount. If the showcase phones need a specific member count, passmembers: [...]explicitly.test/message_input/stream_message_composer_test.dart&test/message_list/message_widget_test.dartUser(id: 'other-user', …)→ameliaMoore/noahSmith/ similar.goldenTest(...)withdocsGoldenTest(...).import 'package:alchemist/alchemist.dart';if it's only used forgoldenTest.Channel + member defaults
#2649's tests construct channels via
setupMockChannelorfakeChannelwithout specifying members or capabilities. This branch now:channel.id) so each channel gets a unique stacked-avatar group.channel.image: nullsoStreamChannelInfoderives the avatar from members.channel.memberCountso group headers render "N Members" instead of "Last online a few seconds ago".If a #2649 test relies on a single specific member or a
channel.image, pass them explicitly. Otherwise the defaults give you authentic group avatars for free.Workflow
#2649 doesn't touch CI. Once these tests land, run them via the new workflow:
The macOS job will render fresh
goldens/macos/*.pngand auto-commit them. Don't commit local goldens — let the workflow produce the authoritative bytes (local macOS rendering drifts by a few bytes from CI macOS).Quick checklist
golden_component_factory.dart— use ours.User(id: …, name: …)literals intosample_users.dart(or reference existing constants).goldenTest→docsGoldenTest. Drop the alchemist import if unused.GoldenComponentFactory(child: …)wraps from each scaffold.ThemeData()to compose fromdocsScreenshotsTheme()so the platform pin and SF setup carry through.stream_chat_localizationsunderdev_dependencies.emptyBuilder/ hand-rolled "no items" widgets — let the SDK render its default.update_goldens.ymlwithtarget=docs.