mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-11-01 07:53:26 +00:00
Fix swift build on Xcode 27 and clear its Swift 6.4 warnings (#1795)
* Drop letterless string catalog keys so swift build works on Xcode 27 * Test that every manual catalog key can become a Swift symbol * Silence Xcode 27's new import and capture warnings * Drop the try markers Xcode 27 flags as unnecessary in tests * Drop the try on the ECDH call that stopped throwing in #1782 * Tag the group hashtag with the app language and format the header count Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e18c073c2c
commit
44d6445bdf
@ -19195,206 +19195,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"#%@" : {
|
||||
"comment" : "Non-localizable channel hashtag format used in code",
|
||||
"extractionState" : "manual",
|
||||
"shouldTranslate" : false,
|
||||
"localizations" : {
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "#%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@" : {
|
||||
"comment" : "Non-localizable symbol used in code",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"bn" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"fa" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"fil" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ne" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"pt" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ta" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"ur" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%@"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"%@ active" : {
|
||||
"comment" : "A label at the bottom of the people list sheet showing the number of active users.",
|
||||
"extractionState" : "stale",
|
||||
|
||||
@ -731,7 +731,7 @@ struct NostrProtocol {
|
||||
}
|
||||
|
||||
// Perform ECDH
|
||||
let sharedSecret = try keyAgreementPrivateKey.sharedSecretFromKeyAgreement(
|
||||
let sharedSecret = keyAgreementPrivateKey.sharedSecretFromKeyAgreement(
|
||||
with: keyAgreementPublicKey,
|
||||
format: .compressed
|
||||
)
|
||||
|
||||
@ -2488,7 +2488,7 @@ final class BLEService: NSObject {
|
||||
}
|
||||
// Signature verification and registry lookups run on messageQueue
|
||||
// like the live receive path.
|
||||
self.messageQueue.async {
|
||||
self.messageQueue.async { [self] in
|
||||
guard self.isCurrentPanicLifecycleGeneration(generation) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ final class NostrTransport: Transport, @unchecked Sendable {
|
||||
}
|
||||
|
||||
private func refreshReachablePeers() {
|
||||
Task { @MainActor in
|
||||
Task { @MainActor [self] in
|
||||
let favorites = dependencies.loadFavorites()
|
||||
let reachable = favorites.values
|
||||
.filter { $0.peerNostrPublicKey != nil }
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct MeshEmptyStateView: View {
|
||||
|
||||
@ -277,7 +277,10 @@ struct ContentHeaderView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.system(size: headerPeerIconSize, weight: .regular))
|
||||
Text("\(headerOtherPeersCount)")
|
||||
// Format style, not Text("\(count)"): same locale digits
|
||||
// and grouping, and no letterless key for Xcode's
|
||||
// localization export to add back to the catalog.
|
||||
Text(headerOtherPeersCount, format: .number)
|
||||
.font(.system(size: headerPeerCountFontSize, weight: .regular, design: theme.bodyFontDesign))
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
|
||||
@ -228,7 +228,7 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.onAppear {
|
||||
.onAppear { [conversationUIModel, voiceRecordingVM] in
|
||||
conversationUIModel.setCurrentColorScheme(colorScheme)
|
||||
conversationUIModel.setCurrentTheme(appTheme)
|
||||
voiceRecordingVM.sessionProvider = { [weak conversationUIModel] in
|
||||
|
||||
@ -30,7 +30,7 @@ struct GroupChatList: View {
|
||||
|
||||
ForEach(groups) { group in
|
||||
HStack(spacing: 4) {
|
||||
Text("#\(group.name)")
|
||||
hashtagText(for: group)
|
||||
.bitchatFont(size: 14)
|
||||
.foregroundColor(palette.primary)
|
||||
.lineLimit(1)
|
||||
@ -69,6 +69,19 @@ struct GroupChatList: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// "#name", not localized: there is nothing to translate, and a
|
||||
/// letterless "#%@" catalog key breaks string catalog symbol generation
|
||||
/// (SwiftPM's default build system). It renders as the localized lookup
|
||||
/// did: the name is isolated (the lookup isolates names containing
|
||||
/// right-to-left text; isolating every name renders the same), so the
|
||||
/// "#" stays on the same side of RTL names, and the text is tagged with
|
||||
/// the app language, as text from a localization table is.
|
||||
private func hashtagText(for group: GroupChatRow) -> Text {
|
||||
var hashtag = AttributedString("#\u{2068}\(group.name)\u{2069}")
|
||||
hashtag.languageIdentifier = Bundle.main.preferredLocalizations.first ?? "en"
|
||||
return Text(hashtag)
|
||||
}
|
||||
|
||||
private func accessibilityDescription(for group: GroupChatRow) -> String {
|
||||
var parts: [String] = [
|
||||
group.name,
|
||||
|
||||
@ -16,11 +16,15 @@ struct LocalizationCoverageTests {
|
||||
var allLocales: Set<String> { coverage.values.reduce(into: []) { $0.formUnion($1) } }
|
||||
}
|
||||
|
||||
private static func loadCatalog(_ relativePath: String) throws -> Catalog {
|
||||
let url = repoRoot.appendingPathComponent(relativePath)
|
||||
let data = try Data(contentsOf: url)
|
||||
/// The catalog's `strings` table: key -> entry.
|
||||
private static func loadStrings(_ relativePath: String) throws -> [String: Any] {
|
||||
let data = try Data(contentsOf: repoRoot.appendingPathComponent(relativePath))
|
||||
let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any])
|
||||
let strings = try #require(root["strings"] as? [String: Any])
|
||||
return try #require(root["strings"] as? [String: Any])
|
||||
}
|
||||
|
||||
private static func loadCatalog(_ relativePath: String) throws -> Catalog {
|
||||
let strings = try loadStrings(relativePath)
|
||||
|
||||
var coverage: [String: Set<String>] = [:]
|
||||
for (key, value) in strings {
|
||||
@ -70,6 +74,59 @@ struct LocalizationCoverageTests {
|
||||
#expect(missing.isEmpty, "share extension is missing locales: \(missing.joined(separator: ", "))")
|
||||
}
|
||||
|
||||
/// SwiftPM's default build system on Swift 6.4+ (swiftbuild) generates a
|
||||
/// Swift symbol for every manually managed catalog key, and fails the
|
||||
/// whole build on a key it cannot name ("%@", "#%@", "%lld/%lld"). CI may
|
||||
/// still run the native build system, which never generates symbols, so
|
||||
/// check the rule here rather than wait for the runner image to move.
|
||||
/// This covers nameability only: the tool also fails on two manual keys
|
||||
/// that generate the same symbol ("1" and "_1") and on skipped positional
|
||||
/// arguments ("%1$@ %3$@"), which this does not check.
|
||||
@Test func everyManualKeyCanDeriveAGeneratedSymbol() throws {
|
||||
for path in [Self.mainCatalogPath, Self.shareExtensionCatalogPath] {
|
||||
let unnameable = try Self.loadStrings(path).compactMap { key, value -> String? in
|
||||
guard (value as? [String: Any])?["extractionState"] as? String == "manual" else { return nil }
|
||||
return Self.canDeriveSymbol(from: key) ? nil : key
|
||||
}.sorted()
|
||||
#expect(
|
||||
unnameable.isEmpty,
|
||||
"""
|
||||
manual keys in \(path) that string catalog symbol generation \
|
||||
cannot name, which breaks `swift build`: \(unnameable). Use \
|
||||
Text(verbatim:) at the call site, or give the key a name.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors `xcstringstool generate-symbols`: printf-style format specifiers
|
||||
/// split the key into words, and a character (grapheme cluster) can only
|
||||
/// be part of the symbol name if every scalar in it can be part of a Swift
|
||||
/// identifier. This approximates that, erring towards rejection, with
|
||||
/// letters, numbers, marks, `_`, `$`, the joiners ZWJ and ZWNJ, and emoji
|
||||
/// outside the Basic Multilingual Plane. So 🔒, 👋🏽 and 🧑🎄 count, but BMP
|
||||
/// emoji (✅ ⚡ ❤️) and sequences built on one (🏃♂️ ✌🏽) do not.
|
||||
private static func canDeriveSymbol(from key: String) -> Bool {
|
||||
let words = key.replacingOccurrences(
|
||||
of: #"%(\d+\$)?[-+0]*\d*(\.\d+)?(hh|h|ll|l|q|L|z|t|j)?[@dDiuUxXoOfFeEgGcCsSpaA]|%%"#,
|
||||
with: " ",
|
||||
options: .regularExpression
|
||||
)
|
||||
return words.contains { character in
|
||||
character.unicodeScalars.allSatisfy { scalar in
|
||||
switch scalar.properties.generalCategory {
|
||||
case .uppercaseLetter, .lowercaseLetter, .titlecaseLetter, .modifierLetter, .otherLetter,
|
||||
.decimalNumber, .letterNumber, .otherNumber,
|
||||
.nonspacingMark, .spacingMark, .enclosingMark:
|
||||
return true
|
||||
default:
|
||||
return scalar == "_" || scalar == "$" || scalar == "\u{200C}" || scalar == "\u{200D}"
|
||||
|| (scalar.value >= 0x10000 && scalar.properties.isEmoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The catalog tests above validate the CATALOG; this one validates the
|
||||
/// CODE. A `String(localized:)` whose key is absent from the catalog its
|
||||
/// target actually ships compiles and runs fine — it just silently ships
|
||||
|
||||
@ -741,14 +741,14 @@ struct BLEAnnounceHandlerTests {
|
||||
)
|
||||
let handler = BLEAnnounceHandler(environment: environment)
|
||||
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) throws -> BitchatPacket {
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) -> BitchatPacket? {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: nickname,
|
||||
noisePublicKey: victimNoiseKey,
|
||||
signingPublicKey: signer.getSigningPublicKeyData(),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
guard let payload = announcement.encode() else { return nil }
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
@ -758,11 +758,11 @@ struct BLEAnnounceHandlerTests {
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
return try #require(signer.signPacket(packet))
|
||||
return signer.signPacket(packet)
|
||||
}
|
||||
|
||||
// Legitimate announce from the victim is accepted and pinned.
|
||||
let victimAnnounce = try makeSignedAnnounce(nickname: "victim", signer: victim)
|
||||
let victimAnnounce = try #require(makeSignedAnnounce(nickname: "victim", signer: victim))
|
||||
handler.handle(victimAnnounce, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
@ -770,7 +770,7 @@ struct BLEAnnounceHandlerTests {
|
||||
#expect(box.persistedIdentities.count == 1)
|
||||
|
||||
// Attacker announce with a valid self-signature must be rejected.
|
||||
let attackerAnnounce = try makeSignedAnnounce(nickname: "attacker", signer: attacker)
|
||||
let attackerAnnounce = try #require(makeSignedAnnounce(nickname: "attacker", signer: attacker))
|
||||
handler.handle(attackerAnnounce, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
@ -778,7 +778,7 @@ struct BLEAnnounceHandlerTests {
|
||||
#expect(box.persistedIdentities.count == 1)
|
||||
|
||||
// The victim's subsequent announces (same pinned key) still work.
|
||||
let victimRename = try makeSignedAnnounce(nickname: "victim-renamed", signer: victim)
|
||||
let victimRename = try #require(makeSignedAnnounce(nickname: "victim-renamed", signer: victim))
|
||||
handler.handle(victimRename, from: peerID)
|
||||
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim-renamed")
|
||||
@ -862,14 +862,14 @@ struct BLEAnnounceHandlerTests {
|
||||
}
|
||||
let handler = BLEAnnounceHandler(environment: makeEnvironment(identityManager: identityManager))
|
||||
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) throws -> BitchatPacket {
|
||||
func makeSignedAnnounce(nickname: String, signer: NoiseEncryptionService) -> BitchatPacket? {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: nickname,
|
||||
noisePublicKey: victimNoiseKey,
|
||||
signingPublicKey: signer.getSigningPublicKeyData(),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
guard let payload = announcement.encode() else { return nil }
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
@ -879,7 +879,7 @@ struct BLEAnnounceHandlerTests {
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
return try #require(signer.signPacket(packet))
|
||||
return signer.signPacket(packet)
|
||||
}
|
||||
|
||||
func persistedIdentity() -> CryptographicIdentity? {
|
||||
@ -888,7 +888,7 @@ struct BLEAnnounceHandlerTests {
|
||||
}
|
||||
|
||||
// 1. Victim announces: pinned in the registry and persisted.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
handler.handle(try #require(makeSignedAnnounce(nickname: "victim", signer: victim)), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(persistedIdentity()?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
|
||||
@ -898,13 +898,13 @@ struct BLEAnnounceHandlerTests {
|
||||
|
||||
// 3. Attacker replay with own signing key: rejected via the persisted
|
||||
// pin, and neither the registry nor the persisted identity change.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "attacker", signer: attacker), from: peerID)
|
||||
handler.handle(try #require(makeSignedAnnounce(nickname: "attacker", signer: attacker)), from: peerID)
|
||||
#expect(box.registry.info(for: peerID) == nil)
|
||||
#expect(persistedIdentity()?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
#expect(identityManager.getSocialIdentity(for: victimNoiseKey.sha256Fingerprint())?.claimedNickname == "victim")
|
||||
|
||||
// 4. Victim re-announces with the same signing key: accepted again.
|
||||
handler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
handler.handle(try #require(makeSignedAnnounce(nickname: "victim", signer: victim)), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.nickname == "victim")
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
|
||||
@ -919,7 +919,7 @@ struct BLEAnnounceHandlerTests {
|
||||
)
|
||||
box.registry = BLEPeerRegistry()
|
||||
let restartedHandler = BLEAnnounceHandler(environment: makeEnvironment(identityManager: reloadedManager))
|
||||
restartedHandler.handle(try makeSignedAnnounce(nickname: "attacker", signer: attacker), from: peerID)
|
||||
restartedHandler.handle(try #require(makeSignedAnnounce(nickname: "attacker", signer: attacker)), from: peerID)
|
||||
#expect(box.registry.info(for: peerID) == nil)
|
||||
#expect(
|
||||
reloadedManager.getCryptoIdentitiesByPeerIDPrefix(peerID).first?.signingPublicKey
|
||||
@ -927,7 +927,7 @@ struct BLEAnnounceHandlerTests {
|
||||
)
|
||||
|
||||
// ...while the victim is accepted after the restart.
|
||||
restartedHandler.handle(try makeSignedAnnounce(nickname: "victim", signer: victim), from: peerID)
|
||||
restartedHandler.handle(try #require(makeSignedAnnounce(nickname: "victim", signer: victim)), from: peerID)
|
||||
#expect(box.registry.info(for: peerID)?.signingPublicKey == victim.getSigningPublicKeyData())
|
||||
}
|
||||
|
||||
|
||||
@ -6,14 +6,14 @@ import BitFoundation
|
||||
@Suite(.serialized)
|
||||
struct ComposerDraftStoreTests {
|
||||
/// Isolate each test from leftover in-memory drafts.
|
||||
private func withCleanStore(_ body: () throws -> Void) rethrows {
|
||||
private func withCleanStore(_ body: () -> Void) {
|
||||
ComposerDraftStore.replaceAllForTesting([:])
|
||||
defer { ComposerDraftStore.replaceAllForTesting([:]) }
|
||||
try body()
|
||||
body()
|
||||
}
|
||||
|
||||
@Test func emptyDraftIsNotStored() throws {
|
||||
try withCleanStore {
|
||||
@Test func emptyDraftIsNotStored() {
|
||||
withCleanStore {
|
||||
ComposerDraftStore.save("hello", for: .mesh)
|
||||
ComposerDraftStore.save(" ", for: .mesh)
|
||||
// Whitespace-only is still a draft the user typed; empty string clears.
|
||||
@ -23,8 +23,8 @@ struct ComposerDraftStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func draftsAreIsolatedPerConversation() throws {
|
||||
try withCleanStore {
|
||||
@Test func draftsAreIsolatedPerConversation() {
|
||||
withCleanStore {
|
||||
let peer = PeerID(str: "aabbccddeeff0011")
|
||||
ComposerDraftStore.save("mesh draft", for: .mesh)
|
||||
ComposerDraftStore.save("geo draft", for: .location(geohash: "u4pruy"))
|
||||
@ -37,8 +37,8 @@ struct ComposerDraftStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func geohashKeysAreCaseInsensitive() throws {
|
||||
try withCleanStore {
|
||||
@Test func geohashKeysAreCaseInsensitive() {
|
||||
withCleanStore {
|
||||
ComposerDraftStore.save("city chat", for: .location(geohash: "U4PRUY"))
|
||||
#expect(ComposerDraftStore.load(.location(geohash: "u4pruy")) == "city chat")
|
||||
}
|
||||
@ -61,16 +61,16 @@ struct ComposerDraftStoreTests {
|
||||
#expect(withoutFP == .privateChat(stableID: peer.id))
|
||||
}
|
||||
|
||||
@Test func longDraftsAreTruncated() throws {
|
||||
try withCleanStore {
|
||||
@Test func longDraftsAreTruncated() {
|
||||
withCleanStore {
|
||||
let long = String(repeating: "a", count: ComposerDraftStore.maxDraftLength + 50)
|
||||
ComposerDraftStore.save(long, for: .mesh)
|
||||
#expect(ComposerDraftStore.load(.mesh).count == ComposerDraftStore.maxDraftLength)
|
||||
}
|
||||
}
|
||||
|
||||
@Test func resetClearsAllDrafts() throws {
|
||||
try withCleanStore {
|
||||
@Test func resetClearsAllDrafts() {
|
||||
withCleanStore {
|
||||
ComposerDraftStore.save("keep quiet", for: .mesh)
|
||||
ComposerDraftStore.reset()
|
||||
#expect(ComposerDraftStore.load(.mesh).isEmpty)
|
||||
@ -78,8 +78,8 @@ struct ComposerDraftStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func maxDraftCountEvictsOldestKeys() throws {
|
||||
try withCleanStore {
|
||||
@Test func maxDraftCountEvictsOldestKeys() {
|
||||
withCleanStore {
|
||||
for index in 0..<ComposerDraftStore.maxDraftCount {
|
||||
ComposerDraftStore.save(
|
||||
"d\(index)",
|
||||
@ -92,8 +92,8 @@ struct ComposerDraftStoreTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test func clearingDraftRemovesKeySoItDoesNotResurrect() throws {
|
||||
try withCleanStore {
|
||||
@Test func clearingDraftRemovesKeySoItDoesNotResurrect() {
|
||||
withCleanStore {
|
||||
ComposerDraftStore.save("old text", for: .mesh)
|
||||
ComposerDraftStore.save("", for: .mesh)
|
||||
#expect(ComposerDraftStore.load(.mesh).isEmpty)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user