ios, android, desktop: previews render the same in every snapshot run (#7938)
* ios, android, desktop: previews draw fixed sample data instead of calling the core * desktop: database error preview passes the keychain setting * ios: snapshot fixtures use fixed parsed markdown instead of the core * ios: snapshot fixtures: raw string delimiters past the # in markdown JSON * desktop: snapshot test sets the app bar height as the app does * docs: plan to replace preview-only parameters with test measures * ios, desktop: previews run master's code; badges and version views split Preview-only parameters are removed. BadgesCheckOrderView, BadgesHowLongView and VersionView are split into a loader and a layout, which their previews draw. Snapshot tests set a fixed QR picture: the environment value fixedQRCode on iOS, LocalFixedQRCode on desktop. * ios: snapshot fixtures build parsed markdown with initializers The table held the JSON of the Linux core, which the iOS app does not decode. The values are those the core returns for each text. * ci: references of fixed QR codes and version; ios: call overlay preview as on master iOS references from https://github.com/evgeny-simplex/simplex-chat/actions/runs/40944118370, desktop references from https://github.com/evgeny-simplex/simplex-chat/actions/runs/40932566864. ActiveCallOverlay_Previews, its scene and its references are restored as on master. * ios, desktop: snapshot tests render every preview, no exclusion lists BadgesChooseBadgeView, ServersSummaryView and TerminalView are split into a loader and a layout. AddChannelView, AddGroupView, GroupProfileView and UserProfileView layouts take the name and description validators, so desktop previews render without the core library. NewChatView's preview opens the Connect tab, UserProfilesView's preview has its own users. AppearanceSettings, MigrateFromDevice and the duplicate UserProfileLayoutEditOn previews are deleted. * ios, desktop: previews show short links; tests draw the fixed QR code with each app's generator The snapshot tests draw the fixed QR picture for a short address link with generateImage on iOS and qrCodeBitmap on desktop, in place of committed images. Previews of group links, addresses, a pending connection and QR codes pass short links, as the apps show them. The fixture markdown table equals the output of master's parser. * ios, desktop: excluded previews and their views as on master * ci: references of short-link QR codes
No files matched your search
@@ -11,6 +11,18 @@ import StoreKit
|
||||
import SimpleXChat
|
||||
|
||||
struct BadgesCheckOrderView: View {
|
||||
let level: BadgeLevel
|
||||
let period: BadgePeriod
|
||||
var startDate = Date()
|
||||
var priceList: BadgePriceList? = nil
|
||||
|
||||
var body: some View {
|
||||
BadgesCheckOrderLayout(level: level, period: period, startDate: startDate, priceList: priceList)
|
||||
.task { await BadgeStore.shared.load() }
|
||||
}
|
||||
}
|
||||
|
||||
struct BadgesCheckOrderLayout: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject private var store = BadgeStore.shared
|
||||
@@ -104,7 +116,6 @@ struct BadgesCheckOrderView: View {
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task { await store.load() }
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
@@ -217,7 +228,7 @@ struct RestorePurchasesButton: View {
|
||||
struct BadgesCheckOrderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
BadgesCheckOrderView(
|
||||
BadgesCheckOrderLayout(
|
||||
level: .supporter,
|
||||
period: .oneMonth,
|
||||
startDate: previewDate,
|
||||
|
||||
@@ -69,6 +69,17 @@ enum BadgePeriod: String, CaseIterable, Identifiable {
|
||||
}
|
||||
|
||||
struct BadgesHowLongView: View {
|
||||
let level: BadgeLevel
|
||||
var startDate = Date()
|
||||
var priceList: BadgePriceList? = nil
|
||||
|
||||
var body: some View {
|
||||
BadgesHowLongLayout(level: level, startDate: startDate, priceList: priceList)
|
||||
.task { await BadgeStore.shared.load() }
|
||||
}
|
||||
}
|
||||
|
||||
struct BadgesHowLongLayout: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@ObservedObject private var store = BadgeStore.shared
|
||||
// store.forSale reads the active profile's badge
|
||||
@@ -142,7 +153,6 @@ struct BadgesHowLongView: View {
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task { await store.load() }
|
||||
}
|
||||
|
||||
private func periodCard(_ period: BadgePeriod) -> some View {
|
||||
@@ -239,7 +249,7 @@ struct BadgeBillingFooter: View {
|
||||
struct BadgesHowLongView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
BadgesHowLongView(
|
||||
BadgesHowLongLayout(
|
||||
level: .supporter,
|
||||
startDate: previewDate,
|
||||
priceList: BadgePriceList(oneMonth: .price("$7"), monthly: .price("$7"), annual: .price("$42"), monthlyAmount: 7, annualAmount: 42, subscriptionsForSale: true, subscribedBefore: false)
|
||||
|
||||
@@ -241,9 +241,9 @@ struct GroupLinkView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var groupLink: GroupLink? = GroupLink(
|
||||
userContactLinkId: 1,
|
||||
connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil),
|
||||
shortLinkDataSet: false,
|
||||
shortLinkLargeDataSet: false,
|
||||
connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: "https://smp6.simplex.im/g#cL3iklYlt_Tws1B_1aGylbT_3HowPMQhuCFoBiCjZ1M"),
|
||||
shortLinkDataSet: true,
|
||||
shortLinkLargeDataSet: true,
|
||||
groupLinkId: "abc",
|
||||
acceptMemberRole: .member
|
||||
)
|
||||
|
||||
@@ -46,19 +46,31 @@ struct SimpleXLinkQRCode: View {
|
||||
|
||||
private let smallQRRatio: CGFloat = 0.63
|
||||
|
||||
extension EnvironmentValues {
|
||||
struct FixedQRCode: EnvironmentKey {
|
||||
static let defaultValue: UIImage? = nil
|
||||
}
|
||||
|
||||
var fixedQRCode: UIImage? {
|
||||
get { self[FixedQRCode.self] }
|
||||
set { self[FixedQRCode.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
struct QRCode: View {
|
||||
let uri: String
|
||||
var small: Bool = false
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
var onShare: (() -> Void)? = nil
|
||||
@Environment(\.fixedQRCode) private var fixedQRCode
|
||||
@State private var image: UIImage? = nil
|
||||
@State private var makeScreenshotFunc: () -> Void = {}
|
||||
@State private var width: CGFloat = .infinity
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let image = image {
|
||||
if let image = fixedQRCode ?? image {
|
||||
qrCodeImage(image).frame(width: width, height: width)
|
||||
GeometryReader { g in
|
||||
let w = g.size.width * (small ? smallQRRatio : 1)
|
||||
@@ -90,7 +102,7 @@ struct QRCode: View {
|
||||
}
|
||||
}
|
||||
.onTapGesture(perform: makeScreenshotFunc)
|
||||
.task { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") }
|
||||
.task { if fixedQRCode == nil { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } }
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +115,7 @@ private func qrCodeImage(_ image: UIImage) -> some View {
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
|
||||
private func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? {
|
||||
func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? {
|
||||
let context = CIContext()
|
||||
let filter = CIFilter.qrCodeGenerator()
|
||||
filter.message = Data(uri.utf8)
|
||||
@@ -131,6 +143,6 @@ extension View {
|
||||
|
||||
struct QRCode_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
QRCode(uri: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D")
|
||||
QRCode(uri: "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw", small: true)
|
||||
}
|
||||
}
|
||||
@@ -1027,7 +1027,7 @@ struct SetSimplexDomainView: View {
|
||||
struct UserAddressView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.userAddress = UserContactLink(CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil))
|
||||
chatModel.userAddress = UserContactLink(CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw"))
|
||||
|
||||
return Group {
|
||||
UserAddressView()
|
||||
|
||||
@@ -10,9 +10,26 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct VersionView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State var versionInfo: CoreVersionInfo?
|
||||
|
||||
var body: some View {
|
||||
VersionLayout(appVersion: appVersion, appBuild: appBuild, versionInfo: versionInfo)
|
||||
.onAppear {
|
||||
do {
|
||||
versionInfo = try apiGetVersion()
|
||||
} catch let error {
|
||||
logger.error("apiGetVersion error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionLayout: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
let appVersion: String?
|
||||
let appBuild: String?
|
||||
let versionInfo: CoreVersionInfo?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
@@ -37,18 +54,11 @@ struct VersionView: View {
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.onAppear {
|
||||
do {
|
||||
versionInfo = try apiGetVersion()
|
||||
} catch let error {
|
||||
logger.error("apiGetVersion error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VersionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VersionView()
|
||||
VersionLayout(appVersion: "7.1", appBuild: "363", versionInfo: CoreVersionInfo(version: "7.1.0.14", simplexmqVersion: "7.1.0.14", simplexmqCommit: "c0c416daf052eb0215ae432ae665522a7c96c0a6"))
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
@@ -5,6 +5,12 @@ import UIKit
|
||||
@testable import SimpleXChat
|
||||
|
||||
final class SnapshotTests: XCTestCase {
|
||||
private var fixedQRCode: UIImage?
|
||||
|
||||
override func setUp() async throws {
|
||||
fixedQRCode = await generateImage(previewQRLink, tintColor: UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1), errorLevel: "M")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testPreviews() throws {
|
||||
NSTimeZone.default = try XCTUnwrap(TimeZone(identifier: "UTC"))
|
||||
@@ -21,7 +27,7 @@ final class SnapshotTests: XCTestCase {
|
||||
for scene in previewScenes() where scene.allSizes || size == "large" {
|
||||
let themeName = dark ? "dark" : "light"
|
||||
let file = "\(scene.name)-\(themeName)-\(size).png"
|
||||
let image = try renderScene(scene, theme.toAppTheme(), dark: dark, in: windowScene)
|
||||
let image = try renderScene(scene, theme.toAppTheme(), dark: dark, fixedQRCode: fixedQRCode, in: windowScene)
|
||||
try XCTUnwrap(image.pngData()).write(to: dir.appendingPathComponent(file))
|
||||
index.append([
|
||||
"scene": scene.name,
|
||||
@@ -305,13 +311,16 @@ private func snapshotTheme(_ dark: Bool) -> ThemeManager.ActiveTheme {
|
||||
)
|
||||
}
|
||||
|
||||
private let previewQRLink = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw"
|
||||
|
||||
@MainActor
|
||||
private func renderScene(_ scene: SnapshotScene, _ theme: AppTheme, dark: Bool, in windowScene: UIWindowScene) throws -> UIImage {
|
||||
private func renderScene(_ scene: SnapshotScene, _ theme: AppTheme, dark: Bool, fixedQRCode: UIImage?, in windowScene: UIWindowScene) throws -> UIImage {
|
||||
let screen = windowScene.screen
|
||||
let width = screen.bounds.width
|
||||
let root = VStack(spacing: 0) { scene.view }
|
||||
.environmentObject(ChatModel.shared)
|
||||
.environmentObject(theme)
|
||||
.environment(\.fixedQRCode, fixedQRCode)
|
||||
.frame(width: width, height: scene.height)
|
||||
let host = UIHostingController(rootView: AnyView(root))
|
||||
if #available(iOS 16.4, *) {
|
||||
@@ -548,6 +557,94 @@ private final class SnapshotFixtures {
|
||||
let team: GroupInfo
|
||||
let news: GroupInfo
|
||||
let contact: Contact
|
||||
private let markdown: [String: ParsedMarkdown] = [
|
||||
"https://simplex.chat": ParsedMarkdown(formattedText: [
|
||||
FormattedText(text: "https://simplex.chat", format: .uri)
|
||||
]),
|
||||
"---": ParsedMarkdown(
|
||||
formattedText: [
|
||||
FormattedText(text: "---", format: .markup(token: .ruleMark))
|
||||
],
|
||||
markdownBlocks: [
|
||||
.rule(mark: SegmentRange(from: 0, to: 1))
|
||||
]
|
||||
),
|
||||
"Hi! Did you see the **new release**?": ParsedMarkdown(formattedText: [
|
||||
FormattedText(text: "Hi! Did you see the "),
|
||||
FormattedText(text: "new release", format: .bold(singleStar: false)),
|
||||
FormattedText(text: "?")
|
||||
]),
|
||||
"Yes, *great* changes 🎉": ParsedMarkdown(formattedText: [
|
||||
FormattedText(text: "Yes, "),
|
||||
FormattedText(text: "great", format: .bold(singleStar: true)),
|
||||
FormattedText(text: " changes 🎉")
|
||||
]),
|
||||
"# Release notes\n- terminal layout\n- `code` blocks\n\n> quoted line": ParsedMarkdown(
|
||||
formattedText: [
|
||||
FormattedText(text: "# ", format: .markup(token: .headingMark(level: 1))),
|
||||
FormattedText(text: "Release notes"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))),
|
||||
FormattedText(text: "terminal layout"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))),
|
||||
FormattedText(text: "code", format: .snippet),
|
||||
FormattedText(text: " blocks"),
|
||||
FormattedText(text: "\n\n", format: .markup(token: .paragraphBreak(lineBreaks: 2))),
|
||||
FormattedText(text: "> ", format: .markup(token: .quoteMark)),
|
||||
FormattedText(text: "quoted line")
|
||||
],
|
||||
markdownBlocks: [
|
||||
.heading(level: 1, content: SegmentRange(from: 1, to: 2)),
|
||||
.list(items: [
|
||||
ListEntry(number: nil, content: SegmentRange(from: 4, to: 5), items: []),
|
||||
ListEntry(number: nil, content: SegmentRange(from: 7, to: 9), items: [])
|
||||
]),
|
||||
.quote(content: SegmentRange(from: 11, to: 12))
|
||||
]
|
||||
),
|
||||
"Plan for today:\n1. review\n2. **release**": ParsedMarkdown(
|
||||
formattedText: [
|
||||
FormattedText(text: "Plan for today:"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "1. ", format: .markup(token: .listMark(number: "1."))),
|
||||
FormattedText(text: "review"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "2. ", format: .markup(token: .listMark(number: "2."))),
|
||||
FormattedText(text: "release", format: .bold(singleStar: false))
|
||||
],
|
||||
markdownBlocks: [
|
||||
.paragraph(content: SegmentRange(from: 0, to: 1)),
|
||||
.list(items: [
|
||||
ListEntry(number: "1.", content: SegmentRange(from: 3, to: 4), items: []),
|
||||
ListEntry(number: "2.", content: SegmentRange(from: 6, to: 7), items: [])
|
||||
])
|
||||
]
|
||||
),
|
||||
"**Version 7.1** is out": ParsedMarkdown(formattedText: [
|
||||
FormattedText(text: "Version 7.1", format: .bold(singleStar: false)),
|
||||
FormattedText(text: " is out")
|
||||
]),
|
||||
"## What's new\n- terminal layout\n- faster sync": ParsedMarkdown(
|
||||
formattedText: [
|
||||
FormattedText(text: "## ", format: .markup(token: .headingMark(level: 2))),
|
||||
FormattedText(text: "What's new"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))),
|
||||
FormattedText(text: "terminal layout"),
|
||||
FormattedText(text: "\n", format: .markup(token: .lineBreak)),
|
||||
FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))),
|
||||
FormattedText(text: "faster sync")
|
||||
],
|
||||
markdownBlocks: [
|
||||
.heading(level: 2, content: SegmentRange(from: 1, to: 2)),
|
||||
.list(items: [
|
||||
ListEntry(number: nil, content: SegmentRange(from: 4, to: 5), items: []),
|
||||
ListEntry(number: nil, content: SegmentRange(from: 7, to: 8), items: [])
|
||||
])
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
init() {
|
||||
alice = Self.member(2, "alice", .admin, image: imageBase64(UIColor(red: 0.85, green: 0.45, blue: 0.3, alpha: 1), CGSize(width: 64, height: 64)))
|
||||
@@ -594,7 +691,7 @@ private final class SnapshotFixtures {
|
||||
}
|
||||
|
||||
private func msg(_ dir: CIDirection, _ text: String, _ minutes: Double, quote: CIQuote? = nil, reactions: [CIReactionCount] = [], content: MsgContent? = nil, file: CIFile? = nil) -> ChatItem {
|
||||
let parsed = text.isEmpty ? nil : parseSimpleXMarkdownText(text)
|
||||
let parsed = markdown[text]
|
||||
let mc = content ?? .text(text)
|
||||
return ChatItem(
|
||||
chatDir: dir,
|
||||
@@ -613,7 +710,7 @@ private final class SnapshotFixtures {
|
||||
}
|
||||
|
||||
private func quote(_ dir: CIDirection?, _ text: String, image: String? = nil) -> CIQuote {
|
||||
let parsed = text.isEmpty ? nil : parseSimpleXMarkdownText(text)
|
||||
let parsed = markdown[text]
|
||||
return CIQuote(
|
||||
chatDir: dir,
|
||||
itemId: 1,
|
||||
|
||||
@@ -212,7 +212,7 @@ internal fun PreviewContactConnectionInfoView() {
|
||||
SimpleXTheme {
|
||||
ContactConnectionInfoLayout(
|
||||
chatModel = ChatModel,
|
||||
connLink = CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null),
|
||||
connLink = CreatedConnLink("https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D", "https://smp4.simplex.im/i#Wu_NH3_F5YphQrIMSCSfWhvqz7tjpZsu/A2dCDGGVkh0-doRPIZVTM-TflHes2uYMVRgA_tK_uCs"),
|
||||
showShortLink = remember { mutableStateOf(true) },
|
||||
contactConnection = PendingContactConnection.getSampleData(),
|
||||
focusAlias = false,
|
||||
|
||||
@@ -60,6 +60,8 @@ fun SimpleXLinkQRCode(
|
||||
)
|
||||
}
|
||||
|
||||
val LocalFixedQRCode = staticCompositionLocalOf<ImageBitmap?> { null }
|
||||
|
||||
@Composable
|
||||
fun QRCode(
|
||||
connReq: String,
|
||||
@@ -73,8 +75,9 @@ fun QRCode(
|
||||
val scope = rememberCoroutineScope()
|
||||
val logoSize = if (small) 0.21f else 0.16f
|
||||
val errorLevel = if (small) QrCode.ErrorLevel.M else QrCode.ErrorLevel.L
|
||||
val fixedQRCode = LocalFixedQRCode.current
|
||||
val qr = remember(connReq, tintColor, withLogo) {
|
||||
qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
|
||||
(fixedQRCode ?: qrCodeBitmap(connReq, 1024, errorLevel)).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
|
||||
.let { if (withLogo) it.addLogo(logoSize) else it }
|
||||
}
|
||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
@@ -127,6 +130,6 @@ expect fun ImageBitmap.replaceColor(from: Int, to: Int): ImageBitmap
|
||||
@Composable
|
||||
fun PreviewQRCode() {
|
||||
SimpleXTheme {
|
||||
QRCode(connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")
|
||||
QRCode(connReq = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw", small = true)
|
||||
}
|
||||
}
|
||||
@@ -839,9 +839,9 @@ fun PreviewUserAddressLayoutAddressCreated() {
|
||||
UserAddressLayout(
|
||||
user = User.sampleData,
|
||||
userAddress = UserContactLinkRec(
|
||||
CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null),
|
||||
shortLinkDataSet = false,
|
||||
shortLinkLargeDataSet = false,
|
||||
CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw"),
|
||||
shortLinkDataSet = true,
|
||||
shortLinkLargeDataSet = true,
|
||||
addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null)
|
||||
),
|
||||
createAddress = {},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package chat.simplex.common
|
||||
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -306,9 +305,3 @@ class DialogState<T> {
|
||||
|
||||
fun onResult(result: T) = onResult!!.complete(result)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppPreview() {
|
||||
AppScreen()
|
||||
}
|
||||
@@ -8,10 +8,11 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.WindowState
|
||||
import chat.simplex.common.AppPreview
|
||||
import boofcv.alg.fiducial.qrcode.QrCode
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.simplexWindowState
|
||||
@@ -43,6 +44,7 @@ import kotlin.test.assertEquals
|
||||
|
||||
private const val SCENE_WIDTH = 402
|
||||
private const val SCENE_MAX_HEIGHT = 874
|
||||
private const val PREVIEW_QR_LINK = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw"
|
||||
|
||||
private val chatItemFontScales = listOf(1f, 1.4f, 1.5f)
|
||||
|
||||
@@ -112,7 +114,6 @@ private val previewScenes = listOf(
|
||||
SnapshotScene("UserAddressView.PreviewUserAddressLayoutAddressCreated") { PreviewUserAddressLayoutAddressCreated() },
|
||||
SnapshotScene("AdvancedNetworkSettings.PreviewAdvancedNetworkSettingsLayout") { PreviewAdvancedNetworkSettingsLayout() },
|
||||
SnapshotScene("NetworkAndServers.PreviewNetworkAndServersLayout") { PreviewNetworkAndServersLayout() },
|
||||
SnapshotScene("DesktopApp.AppPreview") { AppPreview() },
|
||||
)
|
||||
|
||||
private val previewsCallingCore = listOf(
|
||||
@@ -139,12 +140,29 @@ class SnapshotTest {
|
||||
val theme = appPrefs.currentTheme.get()!!
|
||||
val themeOverrides = appPrefs.themeOverrides.get()
|
||||
val fontScale = appPrefs.fontScale.get()
|
||||
val densityScale = appPrefs.densityScale.get()
|
||||
val oneHandUI = appPrefs.oneHandUI.get()
|
||||
val chatBottomBar = appPrefs.chatBottomBar.get()
|
||||
val chatItemRoundness = appPrefs.chatItemRoundness.get()
|
||||
val chatItemTail = appPrefs.chatItemTail.get()
|
||||
val privacyMediaBlurRadius = appPrefs.privacyMediaBlurRadius.get()
|
||||
val appearanceBarsBlurRadius = appPrefs.appearanceBarsBlurRadius.get()
|
||||
val inAppBarsAlpha = appPrefs.inAppBarsAlpha.get()
|
||||
val appBarMaxHeightPx = AppBarHandler.appBarMaxHeightPx
|
||||
val index = mutableListOf<Snapshot>()
|
||||
val failures = mutableListOf<String>()
|
||||
try {
|
||||
Locale.setDefault(Locale.US)
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
simplexWindowState.windowState = WindowState(size = DpSize(SCENE_WIDTH.dp, SCENE_MAX_HEIGHT.dp))
|
||||
appPrefs.densityScale.set(1f)
|
||||
appPrefs.oneHandUI.set(true)
|
||||
appPrefs.chatBottomBar.set(true)
|
||||
appPrefs.chatItemRoundness.set(0.75f)
|
||||
appPrefs.chatItemTail.set(true)
|
||||
appPrefs.privacyMediaBlurRadius.set(0)
|
||||
appPrefs.appearanceBarsBlurRadius.set(if (appPrefs.deviceSupportsBlur) 50 else 0)
|
||||
appPrefs.inAppBarsAlpha.set(appPrefs.inAppBarsDefaultAlpha)
|
||||
appPrefs.themeOverrides.set(listOf(ThemeOverrides(base = DefaultTheme.LIGHT), ThemeOverrides(base = DefaultTheme.DARK)))
|
||||
for (base in listOf(DefaultTheme.LIGHT, DefaultTheme.DARK)) {
|
||||
ThemeManager.applyTheme(base.themeName)
|
||||
@@ -170,6 +188,15 @@ class SnapshotTest {
|
||||
TimeZone.setDefault(timeZone)
|
||||
appPrefs.themeOverrides.set(themeOverrides)
|
||||
appPrefs.fontScale.set(fontScale)
|
||||
appPrefs.densityScale.set(densityScale)
|
||||
appPrefs.oneHandUI.set(oneHandUI)
|
||||
appPrefs.chatBottomBar.set(chatBottomBar)
|
||||
appPrefs.chatItemRoundness.set(chatItemRoundness)
|
||||
appPrefs.chatItemTail.set(chatItemTail)
|
||||
appPrefs.privacyMediaBlurRadius.set(privacyMediaBlurRadius)
|
||||
appPrefs.appearanceBarsBlurRadius.set(appearanceBarsBlurRadius)
|
||||
appPrefs.inAppBarsAlpha.set(inAppBarsAlpha)
|
||||
AppBarHandler.appBarMaxHeightPx = appBarMaxHeightPx
|
||||
ThemeManager.applyTheme(theme)
|
||||
}
|
||||
assertEquals(emptyList(), failures)
|
||||
@@ -178,9 +205,11 @@ class SnapshotTest {
|
||||
|
||||
private fun renderSnapshot(content: @Composable () -> Unit): Image {
|
||||
var height = 0
|
||||
val fixedQRCode = qrCodeBitmap(PREVIEW_QR_LINK, 1024, QrCode.ErrorLevel.M)
|
||||
val frame = renderScene(width = SCENE_WIDTH, height = SCENE_MAX_HEIGHT, frames = 4) {
|
||||
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
|
||||
Box(Modifier.width(SCENE_WIDTH.dp).background(CurrentColors.collectAsState().value.colors.background).onSizeChanged { height = it.height }) {
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(), content = content)
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(), LocalFixedQRCode provides fixedQRCode, content = content)
|
||||
}
|
||||
}
|
||||
return Surface.makeRasterN32Premul(SCENE_WIDTH, height).run {
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 147 KiB |
@@ -0,0 +1,320 @@
|
||||
# Pure previews: rework
|
||||
|
||||
Date: 2026-10-29.
|
||||
|
||||
Pull request: https://github.com/simplex-chat/simplex-chat/pull/7938. Branch: ep/pure-previews.
|
||||
|
||||
The branch had 109 preview-only parameters, each marked `// do not pass`, in 65 files. All are removed. Previews run master's code, except three views split into a loader and a layout, and one environment value for the QR picture.
|
||||
|
||||
Rules, from the owner:
|
||||
|
||||
```
|
||||
1 previews keep master's core calls that send no network request: database reads, parsing, validation
|
||||
2 a preview whose rendering in CI can send a network request draws a layout split cleanly from its view, or its scene is excluded
|
||||
3 VersionView is split, so that its scene does not depend on the release
|
||||
4 QR codes: one environment value on iOS, one CompositionLocal on Kotlin, set by the snapshot tests
|
||||
5 camera: master's code
|
||||
```
|
||||
|
||||
Paths:
|
||||
|
||||
```
|
||||
iOS apps/ios/Shared/
|
||||
Kotlin apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/
|
||||
```
|
||||
|
||||
Line numbers of app code are those of master at 3bdecd63f. Core lines are those of `src/Simplex/Chat/Library/Commands.hs` and `src/Simplex/Chat/Remote.hs` at the same commit; agent lines are those of simplexmq c0c416daf, the commit `cabal.project` pins.
|
||||
|
||||
## 1. Outcomes
|
||||
|
||||
```
|
||||
(a) CI the parameter goes; the preview runs master's code; the scene is rendered on master and compared on every ios-sim or desktop-linux run
|
||||
(a) CL the parameter goes; the preview draws previewDate (2025-01-15 10:15 UTC); from 2026-01-01 the clock-dependent text is "1/15/25" and "Wed, 15 Jan 2025", as in master's references
|
||||
(a) X the parameter goes; the scene stays excluded, as on master
|
||||
(a) S1 the parameter goes; the snapshot test sets the QR picture of seam S1
|
||||
(a) SIM the parameter goes; the simulator build of CodeScanner draws a fixed text and requests no camera access
|
||||
(b) the parameter goes; the preview draws the layout of a loader/layout split (section 3)
|
||||
```
|
||||
|
||||
Calls kept in previews, as on master:
|
||||
|
||||
```
|
||||
db core API reading or writing the CI database, without network (section 4)
|
||||
fn pure core function: markdown parse, name validation, JSON length, server address parse
|
||||
agent agent API reading agent memory
|
||||
store StoreKit read without network: canMakePayments, storefront country code
|
||||
keychain keychain read
|
||||
files file read or delete
|
||||
defaults UserDefaults write
|
||||
paste pasteboard read
|
||||
camera camera authorization status, and the permission request of ScannerInView
|
||||
global write to a shared singleton
|
||||
```
|
||||
|
||||
## 2. Parameters
|
||||
|
||||
| # | File | Parameter | Outcome | Measure | Calls kept |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Model/ChatModel.swift | `ChatModel.init(migrationState:)` | (a) CI | previews call `ChatModel()`, as master's 12 preview sites | files |
|
||||
| 2 | Views/Badges/BadgesCheckOrderView.swift | `priceList` | (b) | `BadgesCheckOrderLayout(priceList:)` | — |
|
||||
| 3 | Views/Badges/BadgesCheckOrderView.swift | `previewCanBuy` | (b) | the layout reads `store.canBuy` | — |
|
||||
| 4 | Views/Badges/BadgesCheckOrderView.swift | `previewCanMakePayments` | (b) | the layout reads `AppStore.canMakePayments` | store |
|
||||
| 5 | Views/Badges/BadgesChooseBadgeView.swift | `previewMonthlyPrices` | (a) X | master | — |
|
||||
| 6 | Views/Badges/BadgesChooseBadgeView.swift | `previewBrowserAllowed` | (a) X | master | — |
|
||||
| 7 | Views/Badges/BadgesHowLongView.swift | `priceList` | (b) | `BadgesHowLongLayout(priceList:)` | — |
|
||||
| 8 | Views/Badges/BadgesRedeemCodeView.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera |
|
||||
| 9 | Views/Call/IncomingCallView.swift | `previewInvitation` | (a) CI | master's preview sets `CallController.shared.activeCallInvitation` | global |
|
||||
| 10 | Views/Chat/ChatInfoView.swift | `ChatInfoView.previewContactInfo` | (a) CI | `apiContactInfo`, `apiGetContactCode` for the sample contact | db |
|
||||
| 11 | Views/Chat/ChatInfoView.swift | `ChatInfoView.previewChatItemTTL` | (a) CI | `ChatModel.shared.chatItemTTL` of the CI database | — |
|
||||
| 12 | Views/Chat/ChatInfoView.swift | `ChatTTLOption.previewChatItemTTL` | (a) CI | as row 11 | — |
|
||||
| 13 | Views/Chat/ChatItemForwardingView.swift | `previewChats` | (a) CI | `ChatModel.shared.chats` of the CI profile | — |
|
||||
| 14 | Views/Chat/ChatItemForwardingView.swift | `previewHasSimplexLink` | (a) CI | `hasSimplexLink("hello")` | fn |
|
||||
| 15 | Views/Chat/ChatView.swift | `previewAvailableContent` | (a) CI | `initChatView` for the sample contact | db |
|
||||
| 16 | Views/Chat/Group/AddGroupMembersView.swift | `AddGroupMembersView.previewContacts` | (a) CI | `filterMembersToAdd`: contacts of `ChatModel.shared.chats` | — |
|
||||
| 17 | Views/Chat/Group/AddGroupMembersView.swift | `AddGroupMembersViewCommon.previewContacts` | (a) CI | as row 16 | — |
|
||||
| 18 | Views/Chat/Group/ChannelRelaysView.swift | `previewMembers` | (a) CI | `loadGroupMembers`, `apiGetGroupRelays` for the sample group | db |
|
||||
| 19 | Views/Chat/Group/GroupChatInfoView.swift | `previewGroupLink` | (a) CI | `apiGetGroupLink` for the sample group | db |
|
||||
| 20 | Views/Chat/Group/GroupChatInfoView.swift | `previewChatItemTTL` | (a) CI | as row 11 | — |
|
||||
| 21 | Views/Chat/Group/GroupChatInfoView.swift | `sendReceiptsUserDefault` | (a) CI | `onAppear` reads `User.sampleData`, which the test sets as `ChatModel.shared.currentUser` | — |
|
||||
| 22 | Views/Chat/Group/GroupLinkView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 23 | Views/Chat/Group/GroupLinkView.swift | `shouldCreate` | (a) CI | `apiCreateGroupLink` for group 1 fails on the CI database | db |
|
||||
| 24 | Views/Chat/Group/GroupMemberInfoView.swift | `previewMemberInfo` | (a) CI | `apiGroupMemberInfo`, `apiGetGroupMemberCode`, `apiContactInfo` for the sample member | db |
|
||||
| 25 | Views/Chat/Group/GroupProfileView.swift | `previewShortDescrLength` | (a) CI | `chatJsonLength` of the sample group's short description; GroupWelcomeView_Previews as well | fn |
|
||||
| 26 | Views/Chat/Group/MemberSupportView.swift | `previewMembers` | (a) CI | `loadGroupMembers` for the sample group | db |
|
||||
| 27 | Views/Chat/Group/SecondaryChatView.swift | `previewAvailableContent` | (a) CI | `initChatView` of ChatView for the sample group scope | db |
|
||||
| 28 | Views/Chat/ScanCodeView.swift | `previewCameraStatus` | (a) SIM | `CodeScannerView` | — |
|
||||
| 29 | Views/Chat/VerifyCodeView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 30 | Views/ChatList/ChatListView.swift | `badgeModel` | (a) CI | `BadgeModel.shared` of the CI profile | — |
|
||||
| 31 | Views/ChatList/ChatListView.swift | `previewSubsTotal` | (a) CI | `getAgentSubsTotal` of the CI profile | agent |
|
||||
| 32 | Views/ChatList/ChatListView.swift | `previewInUS` | (a) CI | `isInUS` of the simulator's storefront | store |
|
||||
| 33 | Views/ChatList/ChatListView.swift | `previewNoShownBadge` | (a) CI | as row 30 | — |
|
||||
| 34 | Views/ChatList/ChatListView.swift | `chatTagsModel` | (a) CI | `ChatTagsModel.shared` of the CI profile | — |
|
||||
| 35 | Views/ChatList/ChatListView.swift | `SubsStatusIndicator.previewSubsTotal` | (a) CI | as row 31 | agent |
|
||||
| 36 | Views/ChatList/ServersSummaryView.swift | `previewServersSummary` | (a) X | master | — |
|
||||
| 37 | Views/ChatList/ServersSummaryView.swift | `previewServerAddresses` | (a) X | master | — |
|
||||
| 38 | Views/ChatList/UserPicker.swift | `previewUsers` | (a) CI | `listUsersAsync` of the CI database | db |
|
||||
| 39 | Views/Database/DatabaseEncryptionView.swift | `initialRandomDBPassphrase` | (a) CI | `initialRandomDBPassphraseGroupDefault` of the CI app | — |
|
||||
| 40 | Views/Database/DatabaseEncryptionView.swift | `storedKey` | (a) CI | `kcDatabasePassword` of the CI app | keychain |
|
||||
| 41 | Views/Database/DatabaseEncryptionView.swift | `previewDatabasePassword` | (a) CI | as row 40 | keychain |
|
||||
| 42 | Views/Database/DatabaseErrorView.swift | `storedDBKey` | (a) CI | as row 40 | keychain |
|
||||
| 43 | Views/Database/DatabaseErrorView.swift | `previewShowRestoreDbButton` | (a) CI | `shouldShowRestoreDbButton`: `encryptionStartedDefault` of the CI app is false | — |
|
||||
| 44 | Views/Database/DatabaseView.swift | `previewUserName` | (a) CI | `User.sampleData`, which the test sets | — |
|
||||
| 45 | Views/Database/DatabaseView.swift | `previewAppFilesCountAndSize` | (a) CI | the CI app's files folder | files |
|
||||
| 46 | Views/LocalAuth/SetAppPasscodeView.swift | `previewProhibitedPasscode` | (a) CI | `kcSelfDestructPassword` of the CI app | keychain |
|
||||
| 47 | Views/Migration/MigrateFromDevice.swift | `previewMigrationState` | (a) X | master | — |
|
||||
| 48 | Views/Migration/MigrateToDevice.swift | `pasteboardHasStrings` | (a) CI | the CI simulator's pasteboard, empty after the smoke test | paste |
|
||||
| 49 | Views/Migration/MigrateToDevice.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera |
|
||||
| 50 | Views/NewChat/AddChannelView.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn |
|
||||
| 51 | Views/NewChat/AddChannelView.swift | `previewHasRelays` | (a) CI | `checkHasRelays` of the CI profile | db |
|
||||
| 52 | Views/NewChat/AddGroupView.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn |
|
||||
| 53 | Views/NewChat/NewChatView.swift | `NewChatView.previewQRImage` | (a) X | master | — |
|
||||
| 54 | Views/NewChat/NewChatView.swift | `connLinkInvitation` | (a) X | master | — |
|
||||
| 55 | Views/NewChat/NewChatView.swift | `InviteView.previewQRImage` | (a) X | master | — |
|
||||
| 56 | Views/NewChat/NewChatView.swift | `ScannerInView.previewCameraStatus` | (a) CI | rows 8, 49, 67 | camera |
|
||||
| 57 | Views/NewChat/QRCode.swift | `MutableQRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 58 | Views/NewChat/QRCode.swift | `SimpleXCreatedLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 59 | Views/NewChat/QRCode.swift | `SimpleXLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 60 | Views/NewChat/QRCode.swift | `QRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 61 | Views/Onboarding/CreateProfile.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn |
|
||||
| 62 | Views/Onboarding/CreateSimpleXAddress.swift | `previewQRImage` | (a) S1 | master's preview draws the address QR code only when `ChatModel.shared.userAddress` is set; it is nil for the CI profile | — |
|
||||
| 63 | Views/Onboarding/OnboardingView.swift | `previewVersionSaved` | (a) CI | `setLastVersionDefault` | defaults |
|
||||
| 64 | Views/Onboarding/SimpleXInfo.swift | `previewVersionSaved` | (a) CI | as row 63 | defaults |
|
||||
| 65 | Views/Onboarding/WhatsNewView.swift | `previewInUS` | (a) CI | as row 32 | store |
|
||||
| 66 | Views/RemoteAccess/ConnectDesktopView.swift | `previewRemoteCtrls` | (a) CI | `setLocalDeviceName`, `listRemoteCtrls` of the CI database | db |
|
||||
| 67 | Views/RemoteAccess/ConnectDesktopView.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera |
|
||||
| 68 | Views/TerminalView.swift | `authorized` | (a) X | master's parameter; master | — |
|
||||
| 69 | Views/TerminalView.swift | `showing` | (a) X | master | — |
|
||||
| 70 | Views/UserSettings/CallSettings.swift | `previewInChina` | (a) CI | `CallController.isInChina` of the simulator's storefront | store |
|
||||
| 71 | Views/UserSettings/NetworkAndServers/NetworkAndServers.swift | `justOpened` | (a) CI | `getUserServers`, `validateServers_` of the CI database | db |
|
||||
| 72 | Views/UserSettings/NetworkAndServers/NewServerView.swift | `previewServerAddress` | (a) CI | `parseServerAddress("")` | fn |
|
||||
| 73 | Views/UserSettings/NetworkAndServers/ProtocolServerView.swift | `previewServerAddress` | (a) CI | `parseServerAddress` of the sample server | fn |
|
||||
| 74 | Views/UserSettings/NetworkAndServers/ProtocolServerView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 75 | Views/UserSettings/NetworkAndServers/ProtocolServersView.swift | `YourServersView.previewServerAddresses` | (a) CI | `parseServerAddress` of the sample servers | fn |
|
||||
| 76 | Views/UserSettings/NetworkAndServers/ProtocolServersView.swift | `ProtocolServerViewLink.previewServerAddress` | (a) CI | as row 75 | fn |
|
||||
| 77 | Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift | `previewCameraStatus` | (a) SIM | `CodeScannerView` | — |
|
||||
| 78 | Views/UserSettings/NotificationsView.swift | `previewNtfToken` | (a) CI | `apiGetNtfToken` of the CI database | db |
|
||||
| 79 | Views/UserSettings/PrivacySettings.swift | `previewUserName` | (a) CI | as row 44 | — |
|
||||
| 80 | Views/UserSettings/SettingsView.swift | `previewInUS` | (a) CI | as row 32 | store |
|
||||
| 81 | Views/UserSettings/UserAddressView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 82 | Views/UserSettings/VersionView.swift | `previewVersionInfo` | (b) | `VersionLayout(appVersion:appBuild:versionInfo:)` | — |
|
||||
| 83 | model/ChatModel.kt | `getTimestampDateText.previewNow` | (a) CL | DateSeparator text | — |
|
||||
| 84 | model/ChatModel.kt | `getTimestampText.previewNow` | (a) CL | chat list time | — |
|
||||
| 85 | views/chat/ChatView.kt | `ChatLayout.previewNow` | (a) CL | as row 83 | — |
|
||||
| 86 | views/chat/ChatView.kt | `ChatItemsList.previewNow` | (a) CL | as row 83 | — |
|
||||
| 87 | views/chat/ChatView.kt | `DateSeparator.previewNow` | (a) CL | as row 83 | — |
|
||||
| 88 | views/chat/group/GroupMemberInfoView.kt | `previewContactConnectionStats` | (a) CI | `apiContactInfo` in `withBGApi` throws "Controller is not initialized" in the test JVM | db |
|
||||
| 89 | views/chat/group/GroupProfileView.kt | `previewShortDescrFits` | (a) X | `previewsCallingCore`, as master | — |
|
||||
| 90 | views/chatlist/ChatPreviewView.kt | `previewNow` | (a) CL | as row 84 | — |
|
||||
| 91 | views/chatlist/ContactRequestView.kt | `previewNow` | (a) CL | as row 84 | — |
|
||||
| 92 | views/database/DatabaseErrorView.kt | `previewStoredDBKey` | (a) CI | `ksDatabasePassword.get()` returns null with the CI test's settings | — |
|
||||
| 93 | views/database/DatabaseErrorView.kt | `previewShowRestoreDbButton` | (a) CI | `shouldShowRestoreDbButton` returns false: `encryptionStartedAt` is null in the CI test's settings | — |
|
||||
| 94 | views/database/DatabaseErrorView.kt | `previewUseKeychain` | (a) CI | `storeDBPassphrase` default, true | — |
|
||||
| 95 | views/newchat/AddChannelView.kt | `AddChannelView.previewNameValid` | (a) X | `previewsCallingCore`, as master | — |
|
||||
| 96 | views/newchat/AddChannelView.kt | `AddChannelView.previewHasRelays` | (a) X | as row 95 | — |
|
||||
| 97 | views/newchat/AddChannelView.kt | `ProfileStepView.previewNameValid` | (a) X | as row 95 | — |
|
||||
| 98 | views/newchat/AddChannelView.kt | `ProfileStepView.previewHasRelays` | (a) X | as row 95 | — |
|
||||
| 99 | views/newchat/AddGroupView.kt | `previewNameValid` | (a) X | as row 95 | — |
|
||||
| 100 | views/newchat/ContactConnectionInfoView.kt | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 101 | views/newchat/NewChatSheet.kt | `NewChatSheet.previewConnectTarget` | (a) CI | `chatParseMarkdown` in a `LaunchedEffect` throws in the test JVM, which does not load the core library; the scene renders as in master's reference | fn |
|
||||
| 102 | views/newchat/NewChatSheet.kt | `NewChatSheetLayout.previewConnectTarget` | (a) CI | as row 101 | fn |
|
||||
| 103 | views/newchat/NewChatSheet.kt | `ContactsSearchBar.previewConnectTarget` | (a) CI | as row 101 | fn |
|
||||
| 104 | views/newchat/QRCode.kt | `SimpleXCreatedLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 105 | views/newchat/QRCode.kt | `SimpleXLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 106 | views/newchat/QRCode.kt | `QRCode.previewImage` | (a) S1 | fixed QR picture | — |
|
||||
| 107 | views/onboarding/SimpleXInfo.kt | `previewMigrationState` | (a) CI | `chatModel.migrationState` is null with the CI test's settings | files |
|
||||
| 108 | views/usersettings/UserAddressView.kt | `previewQRImage` | (a) S1 | fixed QR picture | — |
|
||||
| 109 | views/usersettings/UserProfileView.kt | `previewShortDescrFits` | (a) X | as row 89; EditOff and EditOn | — |
|
||||
|
||||
Counts:
|
||||
|
||||
```
|
||||
(a) 104
|
||||
CI 64
|
||||
CL 7
|
||||
X 17
|
||||
S1 14
|
||||
SIM 2
|
||||
(b) 5
|
||||
(c) 0
|
||||
```
|
||||
|
||||
## 3. Splits
|
||||
|
||||
Each loader keeps its view's name and parameters, so its callers are unchanged. The loader runs the call it ran before, at the same moment: the view's `.task` or `.onAppear` moves from the end of the layout's modifiers to the layout in the loader's body. State stays where it was: the loader owns the loaded value, the layout owns its UI state. The preview draws the layout with fixed values.
|
||||
|
||||
```
|
||||
BadgesCheckOrderView Views/Badges/BadgesCheckOrderView.swift
|
||||
loader BadgesCheckOrderView(level, period, startDate, priceList): .task { await BadgeStore.shared.load() }
|
||||
layout BadgesCheckOrderLayout(level, period, startDate, priceList)
|
||||
moved .task { await store.load() } (:107)
|
||||
preview BadgesCheckOrderLayout
|
||||
full lines 10 added, 1 removed, 1 changed
|
||||
|
||||
BadgesHowLongView Views/Badges/BadgesHowLongView.swift
|
||||
loader BadgesHowLongView(level, startDate, priceList): .task { await BadgeStore.shared.load() }
|
||||
layout BadgesHowLongLayout(level, startDate, priceList)
|
||||
moved .task { await store.load() } (:145)
|
||||
preview BadgesHowLongLayout
|
||||
full lines 9 added, 1 removed, 1 changed
|
||||
|
||||
VersionView Views/UserSettings/VersionView.swift
|
||||
loader VersionView: @State versionInfo, .onAppear { versionInfo = try apiGetVersion() }
|
||||
layout VersionLayout(appVersion, appBuild, versionInfo)
|
||||
moved .onAppear (:40-46), @EnvironmentObject theme (:13) into the layout
|
||||
preview VersionLayout(appVersion: "7.1", appBuild: "363", versionInfo: CoreVersionInfo(version: "7.1.0.14", ...))
|
||||
full lines 16 added, 8 removed, 1 changed
|
||||
```
|
||||
|
||||
`store` of both badge layouts is `BadgeStore.shared` (`@ObservedObject private var store = BadgeStore.shared`), the object the loaders call.
|
||||
|
||||
## 4. Network evidence
|
||||
|
||||
Previews that kept a core, agent or StoreKit call:
|
||||
|
||||
| Rows | Scene | Code path | Network in CI |
|
||||
|---|---|---|---|
|
||||
| 2, 3, 4 | BadgesCheckOrderView | BadgesCheckOrderView.swift:107 `store.load()` → BadgeStore.swift:219 `Product.products(for:)`, :228 `subscriptionOriginalIds()` | StoreKit: yes; split |
|
||||
| 7 | BadgesHowLongView | BadgesHowLongView.swift:145 `store.load()` → BadgeStore.swift:219 | StoreKit: yes; split |
|
||||
| 10 | ChatInfoView | ChatInfoView.swift:305-326: :313 `apiContactInfo` → Commands.hs:1956-1964 (`getContact` :1958, agent `getConnectionServers` :1963 → Agent.hs:3128-3131 store read, :3141-3180 agent memory); :314 `apiGetContactCode` → Commands.hs:2040-2053, :3838-3839 → Agent.hs:3133-3138 store read | no |
|
||||
| 15 | ChatView | ChatView.swift:333-338 → `initChatView` :743-803: :750 `apiContactInfo` (row 10) for the sample contact; :791 → :805-818 `apiGetChatContentTypes` → Commands.hs:738-739 store read; :772-790, with :783 `apiGetUpdatedGroupLinkData`, needs a group with `useRelays`; :797-801 needs an unread chat | no |
|
||||
| 18 | ChannelRelaysView | ChannelRelaysView.swift:45-55: `loadGroupMembers` → ChatModel.swift:592-602 `apiListMembers` → Commands.hs:3293-3294 store read; :49 `apiGetGroupRelays` → Commands.hs:2790-2795 store read | no |
|
||||
| 19 | GroupChatInfoView | GroupChatInfoView.swift:298-312: :305 `apiGetGroupLink` → Commands.hs:3392-3395 store read | no |
|
||||
| 23 | GroupLinkView | GroupLinkView.swift:164-169 → :179-196 `apiCreateGroupLink(1)` → Commands.hs:3364-3377: `getGroupInfo` :3365 fails before `createConnection` :3373; the CI database has no group: smoke-test.sh:93-126 creates only the profile | no |
|
||||
| 24 | GroupMemberInfoView | GroupMemberInfoView.swift:293-328: :301 `apiGroupMemberInfo` → Commands.hs:1986-1989 store read and agent memory; :303 `apiGetGroupMemberCode` → Commands.hs:2054-2078 store read; :320 `apiContactInfo` (row 10) | no |
|
||||
| 26 | MemberSupportView | MemberSupportView.swift:22-26 `loadGroupMembers` (row 18) | no |
|
||||
| 27 | SecondaryChatView | ChatView `initChatView` for a group: `GroupInfo.sampleData.useRelays` is false (SimpleXChat/ChatTypes.swift:3103), so :772-790 is skipped; :791 (row 15) | no |
|
||||
| 31, 35 | ChatListView | ChatListView.swift:736-760 `startTask` :753 `getAgentSubsTotal` → Commands.hs:3760-3764 → Agent/Client.hs:2627-2645 agent memory | no |
|
||||
| 38 | UserPicker | UserPicker.swift:100-116: :106 `listUsersAsync` → Commands.hs:517 store read | no |
|
||||
| 51 | AddChannelView | AddChannelView.swift:112-113 → `checkHasRelays` :272-278: :273 `getUserServers` → Commands.hs:1816-1817 store read | no |
|
||||
| 66 | ConnectDesktopView | ConnectDesktopView.swift:87-104: :88 `setLocalDeviceName` → Commands.hs:3704 variable write; :89 `listRemoteCtrls` → Commands.hs:3723 → Remote.hs:606-611 store read; :92 `disconnectDesktop` needs a desktop session; :94 `findKnownDesktop` needs `useMulticast` (:29-31), which needs a linked desktop | no |
|
||||
| 71 | NetworkServersView | NetworkAndServers.swift:136-155: :140 `getUserServers` (row 51); :144 `validateServers_` → Commands.hs:1833-1834, :4322-4326 store read | no |
|
||||
| 78 | NotificationsView | NotificationsView.swift:83-85 `apiGetNtfToken` → Commands.hs:1681 → Agent.hs:3332-3336 store read | no |
|
||||
| 88 | GroupMemberInfoView.kt | GroupMemberInfoView.kt:502-509 `withBGApi { apiContactInfo }` → model/SimpleXAPI.kt:989 throws "Controller is not initialized" | no |
|
||||
| — | ActiveCallOverlay | `WebRTCClient` of the preview: WebRTCClient.swift:25 `getChatCtrl()` → SimpleXChat/API.swift:19-22 reads the controller pointer; :82-89 sets two flags of `RTCAudioSession.sharedInstance()` | no |
|
||||
|
||||
## 5. QR picture (S1)
|
||||
|
||||
iOS, `Views/NewChat/QRCode.swift`:
|
||||
|
||||
```swift
|
||||
extension EnvironmentValues {
|
||||
struct FixedQRCode: EnvironmentKey {
|
||||
static let defaultValue: UIImage? = nil
|
||||
}
|
||||
|
||||
var fixedQRCode: UIImage? {
|
||||
get { self[FixedQRCode.self] }
|
||||
set { self[FixedQRCode.self] = newValue }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
QRCode added @Environment(\.fixedQRCode) private var fixedQRCode
|
||||
QRCode changed if let image = fixedQRCode ?? image {
|
||||
QRCode changed .task { if fixedQRCode == nil { image = await generateImage(...) } }
|
||||
```
|
||||
|
||||
Kotlin, `views/newchat/QRCode.kt`:
|
||||
|
||||
```
|
||||
added val LocalFixedQRCode = staticCompositionLocalOf<ImageBitmap?> { null }
|
||||
added val fixedQRCode = LocalFixedQRCode.current (QRCode, before remember)
|
||||
changed (fixedQRCode ?: qrCodeBitmap(connReq, 1024, errorLevel)).replaceColor(...) (QRCode, in remember)
|
||||
```
|
||||
|
||||
Snapshot tests:
|
||||
|
||||
```
|
||||
link https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw, the address of tests/MarkdownTests.hs:324
|
||||
iOS setUp: fixedQRCode = await generateImage(link, tintColor: QRCode's default tint, errorLevel: "M")
|
||||
iOS renderScene root: .environment(\.fixedQRCode, fixedQRCode)
|
||||
iOS QRCode.swift: generateImage is internal, so the test calls it
|
||||
Kotlin renderSnapshot root: LocalFixedQRCode provides qrCodeBitmap(link, 1024, QrCode.ErrorLevel.M), in the theme of the render
|
||||
```
|
||||
|
||||
The views add the logo, and on desktop the tint, to the picture, as to a generated one. Short links are drawn `small`, with error level M, in both apps.
|
||||
|
||||
Previews with links pass the short form of a preset server, as the core returns it (shortenShortLink): host only, without the key hash.
|
||||
|
||||
```
|
||||
iOS GroupLinkView_Previews connShortLink https://smp6.simplex.im/g#cL3iklYlt_Tws1B_1aGylbT_3HowPMQhuCFoBiCjZ1M; shortLinkDataSet, shortLinkLargeDataSet true
|
||||
iOS UserAddressView_Previews connShortLink the address link
|
||||
iOS QRCode_Previews QRCode(uri: the address link, small: true)
|
||||
Kotlin PreviewUserAddressLayoutAddressCreated connShortLink the address link; shortLinkDataSet, shortLinkLargeDataSet true
|
||||
Kotlin PreviewContactConnectionInfoView connFullLink the invitation of master's QRCode_Previews; connShortLink https://smp4.simplex.im/i#Wu_NH3_F5YphQrIMSCSfWhvqz7tjpZsu/A2dCDGGVkh0-doRPIZVTM-TflHes2uYMVRgA_tK_uCs
|
||||
Kotlin PreviewQRCode QRCode(connReq = the address link, small = true)
|
||||
```
|
||||
|
||||
Each short link decodes as AConnShortLink and encodes back to the same text.
|
||||
|
||||
## 6. Other branch changes
|
||||
|
||||
```
|
||||
ActiveCallView.swift, ActiveCallOverlay_Previews master
|
||||
DesktopApp.kt, AppPreview deleted keep
|
||||
SnapshotTests.swift, ActiveCallOverlay scene master
|
||||
SnapshotTests.swift, SnapshotFixtures markdown table keep, values built with initializers
|
||||
SnapshotTest.kt, setting pins and appBarMaxHeightPx keep
|
||||
SnapshotTest.kt, AppPreview scene removed keep
|
||||
check-previews.py, EXCLUDED master
|
||||
SnapshotTest.kt, previewsCallingCore master
|
||||
every other file master
|
||||
```
|
||||
|
||||
## 7. App code
|
||||
|
||||
Full lines by `git diff -w --word-diff`, blank lines aside:
|
||||
|
||||
```
|
||||
iOS Views/Badges/BadgesCheckOrderView.swift 10 added, 1 removed, 1 changed
|
||||
iOS Views/Badges/BadgesHowLongView.swift 9 added, 1 removed, 1 changed
|
||||
iOS Views/UserSettings/VersionView.swift 16 added, 8 removed, 1 changed
|
||||
iOS Views/NewChat/QRCode.swift 10 added, 4 changed: 1 of them the preview
|
||||
iOS Views/Chat/Group/GroupLinkView.swift 3 changed: preview
|
||||
iOS Views/UserSettings/UserAddressView.swift 1 changed: preview
|
||||
Kotlin views/newchat/QRCode.kt 2 added, 2 changed: 1 of them the preview
|
||||
Kotlin views/newchat/ContactConnectionInfoView.kt 1 changed: preview
|
||||
Kotlin views/usersettings/UserAddressView.kt 3 changed: preview
|
||||
Kotlin desktopMain DesktopApp.kt 6 removed: preview and its import
|
||||
```
|
||||
@@ -31,11 +31,11 @@ Queueing for a runner adds from under a minute to 20 minutes. Results are artifa
|
||||
|
||||
## Snapshots
|
||||
|
||||
`:common:desktopTest` runs `SnapshotTest` (`apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt`). It renders each preview listed in `previewScenes` with Compose's `ImageComposeScene`, 402 dp wide at density 1, one pixel per dp, as tall as the preview's content up to 874 dp, so a preview that fills any height is 874 pixels tall. It renders the light and dark themes without a wallpaper, over the theme's background, and keeps the fourth frame, 3 s of frame time after the first. The scene's coroutine context sets the animation duration scale (`MotionDurationScale`) to 0, so each animation ends at its first frame and a spinner is drawn the same in every run. As the app does for its screens, the test provides an `AppBarHandler` (`LocalAppBarHandler`) and the window state (`simplexWindowState`, sized as the scene). Each preview is rendered at the default font size; the chat item previews (package `views.chat.item`) also at 1.4 and 1.5, the two largest values of the desktop app's font size setting.
|
||||
`:common:desktopTest` runs `SnapshotTest` (`apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt`). It renders each preview listed in `previewScenes` with Compose's `ImageComposeScene`, 402 dp wide at density 1, one pixel per dp, as tall as the preview's content up to 874 dp, so a preview that fills any height is 874 pixels tall. It renders the light and dark themes without a wallpaper, over the theme's background, and keeps the fourth frame, 3 s of frame time after the first. The scene's coroutine context sets the animation duration scale (`MotionDurationScale`) to 0, so each animation ends at its first frame and a spinner is drawn the same in every run. As the app does for its screens, the test provides an `AppBarHandler` (`LocalAppBarHandler`), the window state (`simplexWindowState`, sized as the scene) and the app bar height in pixels (`AppBarHandler.appBarMaxHeightPx`, which `AppScreen` sets), and restores the height when it finishes. Each preview is rendered at the default font size; the chat item previews (package `views.chat.item`) also at 1.4 and 1.5, the two largest values of the desktop app's font size setting.
|
||||
|
||||
`everyPreviewIsRendered` reads the Kotlin sources of `commonMain` and `desktopMain` without comments, and fails when a `@Preview` function is missing from both `previewScenes` and `previewsCallingCore`, or when either names a function absent from the sources; a scene is named `<file>.<function>`. `previewsCallingCore` lists the previews whose layouts validate names or descriptions with the core library (`chatValidName`, `chatJsonLength`), which the test JVM does not load, so they are not rendered. The previews of `androidMain` and of the Android app module are left out too, since they use Android APIs.
|
||||
|
||||
The test runs in UTC and en_US (the JVM's default `TimeZone` and `Locale`), and replaces the theme overrides with ones without a wallpaper. It sets the app's theme and font size for each render, and restores these settings and defaults when it finishes. Before each render it clears the terminal's items, which previews that send commands to the core add with the time they are sent, so that `PreviewTerminalLayout` shows an empty terminal. Previews show `previewDate` (`ChatItemView.kt`) in place of the current time.
|
||||
The test runs in UTC and en_US (the JVM's default `TimeZone` and `Locale`), and replaces the theme overrides with ones without a wallpaper. It sets the app's theme and font size for each render, sets the density scale, the one-hand UI, the chat toolbar position, the message shape, the media blur and the bars' blur and opacity to their defaults, and restores these settings and defaults when it finishes. Before each render it clears the terminal's items, which previews that send commands to the core add with the time they are sent, so that `PreviewTerminalLayout` shows an empty terminal. Previews show `previewDate` (`ChatItemView.kt`) in place of the current time. The test provides `LocalFixedQRCode` (`QRCode.kt`) with the bitmap that `qrCodeBitmap` draws for a short address link in the theme of the render, so every QR code draws this bitmap.
|
||||
|
||||
The images go to `SNAPSHOT_DIR`, otherwise to `apps/multiplatform/common/build/snapshots`, as PNG files named `<file>.<function>-<light|dark>-font-<100|140|150>.png`, with `index-desktop.json` listing each file's scene, theme, font scale, and pixel width and height. The app job sets `SNAPSHOT_DIR` to `snapshots/` of the artifact `desktop-snapshots`, then the step "Compare snapshots" runs `scripts/ci/snapshots-compare.py` with the references in `apps/multiplatform/common/src/desktopTest/snapshots`, as the iOS workflow does ([iOS](../ios-sim/README.md), "Snapshots"): a missing, extra or resized render, or a pixel beyond the tolerance, fails the step, and the diff images go to `snapshots-diff/` of the same artifact.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ At the end of the app job's smoke test, `smoke-test.sh` runs `snapshots.sh <deri
|
||||
|
||||
`previewScenes()` lists every `PreviewProvider` in `apps/ios` except those in `EXCLUDED` of `apps/ios/SimpleX Snapshots/check-previews.py`, whose renders differ between runs and can be made identical only by a change of the app's code. The iOS job of `build.yml` runs `check-previews.py`, which fails when a `PreviewProvider` is neither listed nor excluded, or when a file has a `#Preview`, since the test renders previews by their type.
|
||||
|
||||
The test runs in UTC (`NSTimeZone.default`) and en_US (`-testLanguage en -testRegion US`), so the renders do not depend on the runner's time zone or locale; a preview that shows the current date or time differs between runs, so the previews and the sample contact use `previewDate` (`SimpleXChat/ChatTypes.swift`) in place of the current time. The keyboard autocorrects the profile name typed in the smoke test differently between runs, so the test sets the app's current user to the sample user.
|
||||
The test runs in UTC (`NSTimeZone.default`) and en_US (`-testLanguage en -testRegion US`), so the renders do not depend on the runner's time zone or locale; a preview that shows the current date or time differs between runs, so the previews and the sample contact use `previewDate` (`SimpleXChat/ChatTypes.swift`) in place of the current time. The test sets the environment value `fixedQRCode` (`QRCode.swift`) to the image that `generateImage` draws for a short address link, so every QR code draws this image. The keyboard autocorrects the profile name typed in the smoke test differently between runs, so the test sets the app's current user to the sample user.
|
||||
|
||||
The images go to `snapshots/` of the artifact `ios-sim-smoke-test` as 8-bit sRGB PNG files without alpha (`preferredRange = .standard`, `opaque`), named `<preview>-<light|dark>-<size>.png`, with `index-<size>.json` listing each file's scene, theme, size, pixel width and height, scale and the content size category the app saw.
|
||||
|
||||
|
||||