mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 17:25:20 +00:00
test(localization): squash divergent history to restore clean commit (#695)
- Replace 474/474 sync divergence with a single descriptive commit - Keep only intended localization test improvements vs main - Ensure readable history for code review and future merges
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import XCTest
|
import XCTest
|
||||||
|
@testable import bitchat
|
||||||
|
|
||||||
private let localizationTestsDirectoryURL = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
|
private let localizationTestsDirectoryURL = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
|
||||||
private let testsRootURL = localizationTestsDirectoryURL.deletingLastPathComponent()
|
private let testsRootURL = localizationTestsDirectoryURL.deletingLastPathComponent()
|
||||||
@@ -239,6 +240,168 @@ final class LocalizationCatalogTests: XCTestCase {
|
|||||||
let data = try Data(contentsOf: url)
|
let data = try Data(contentsOf: url)
|
||||||
return try JSONDecoder().decode(PrimaryKeyConfig.self, from: data)
|
return try JSONDecoder().decode(PrimaryKeyConfig.self, from: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Regression Tests
|
||||||
|
|
||||||
|
/// Helper method to access the app bundle for localization testing.
|
||||||
|
/// This ensures tests use the actual app's Localizable.xcstrings instead of
|
||||||
|
/// the test bundle, preventing false positives where localization keys are
|
||||||
|
/// returned instead of translated strings.
|
||||||
|
private func appBundle() -> Bundle {
|
||||||
|
Bundle(for: BitchatApp.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests to prevent regression of the pluralization format string issue
|
||||||
|
/// These tests ensure that String(localized:) properly handles
|
||||||
|
/// pluralized format strings with %#@variable@ syntax
|
||||||
|
func testPluralizedFormatStringsDoNotCrash() {
|
||||||
|
// These are the exact calls that were causing runtime errors
|
||||||
|
|
||||||
|
// Test 1: content.accessibility.people_count with Int argument
|
||||||
|
XCTAssertNoThrow({
|
||||||
|
let result = String(
|
||||||
|
localized: "content.accessibility.people_count",
|
||||||
|
bundle: appBundle(),
|
||||||
|
comment: "Accessibility label announcing number of people in header"
|
||||||
|
)
|
||||||
|
XCTAssertFalse(result.isEmpty, "Should return formatted string")
|
||||||
|
// Verify we're getting actual localized content, not just the key
|
||||||
|
XCTAssertNotEqual(result, "content.accessibility.people_count", "Should return localized string, not key")
|
||||||
|
}(), "People count localization should not throw")
|
||||||
|
|
||||||
|
// Test 2: location_notes.header with String and Int arguments
|
||||||
|
XCTAssertNoThrow({
|
||||||
|
let result = String(
|
||||||
|
localized: "location_notes.header",
|
||||||
|
bundle: appBundle(),
|
||||||
|
comment: "Header displaying the geohash and localized note count"
|
||||||
|
)
|
||||||
|
XCTAssertFalse(result.isEmpty, "Should return formatted string")
|
||||||
|
// Verify we're getting actual localized content, not just the key
|
||||||
|
XCTAssertNotEqual(result, "location_notes.header", "Should return localized string, not key")
|
||||||
|
}(), "Location notes header localization should not throw")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFormatStringArgumentMatching() {
|
||||||
|
// Verify that the format strings properly handle their expected arguments
|
||||||
|
|
||||||
|
// People count expects: %#@people@ format with Int
|
||||||
|
let peopleResult = String(
|
||||||
|
localized: "content.accessibility.people_count",
|
||||||
|
bundle: appBundle(),
|
||||||
|
comment: "Test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Should not show format specifiers in the base string
|
||||||
|
XCTAssertFalse(peopleResult.isEmpty, "Should return non-empty string")
|
||||||
|
|
||||||
|
// Location notes expects: #%@ • %#@note_count@ format with String, Int
|
||||||
|
let notesResult = String(
|
||||||
|
localized: "location_notes.header",
|
||||||
|
bundle: appBundle(),
|
||||||
|
comment: "Test"
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertFalse(notesResult.isEmpty, "Should return non-empty string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPluralizedStringsWithArguments() {
|
||||||
|
// Test the pluralized strings with actual format arguments
|
||||||
|
|
||||||
|
// Test people count with different values
|
||||||
|
let testCases = [0, 1, 2, 5]
|
||||||
|
|
||||||
|
for count in testCases {
|
||||||
|
let result = String(
|
||||||
|
format: String(localized: "content.accessibility.people_count", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
// Just verify it doesn't crash and returns a reasonable string
|
||||||
|
XCTAssertFalse(result.isEmpty,
|
||||||
|
"People count should return non-empty string for count \(count)")
|
||||||
|
XCTAssertTrue(result.contains("\(count)"),
|
||||||
|
"Result should contain the count number for count \(count)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLocationNotesHeaderWithArguments() {
|
||||||
|
let geohash = "abc123"
|
||||||
|
let testCases = [0, 1, 2, 10]
|
||||||
|
|
||||||
|
for count in testCases {
|
||||||
|
let result = String(
|
||||||
|
format: String(localized: "location_notes.header", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
geohash, count
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify basic structure is maintained
|
||||||
|
XCTAssertTrue(result.contains(geohash),
|
||||||
|
"Result should contain geohash for count \(count)")
|
||||||
|
XCTAssertTrue(result.contains("\(count)"),
|
||||||
|
"Result should contain count for count \(count)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLocationChannelsRowTitleWithArguments() {
|
||||||
|
let label = "test-channel"
|
||||||
|
let testCases = [0, 1, 2, 5]
|
||||||
|
|
||||||
|
for count in testCases {
|
||||||
|
let result = String(
|
||||||
|
format: String(localized: "location_channels.row_title", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
label, count
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify basic structure is maintained
|
||||||
|
XCTAssertTrue(result.contains(label),
|
||||||
|
"Result should contain label for count \(count)")
|
||||||
|
XCTAssertTrue(result.contains("\(count)"),
|
||||||
|
"Result should contain count for count \(count)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNoArgumentsLocalization() {
|
||||||
|
// Test that strings without arguments still work
|
||||||
|
let result = String(
|
||||||
|
localized: "common.ok",
|
||||||
|
bundle: appBundle(),
|
||||||
|
comment: "OK button text"
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertFalse(result.isEmpty, "Simple localization should work")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLocalizationEdgeCases() {
|
||||||
|
// Test edge cases that might cause issues
|
||||||
|
|
||||||
|
// Large numbers
|
||||||
|
let largeNumberResult = String(
|
||||||
|
format: String(localized: "content.accessibility.people_count", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
1000000
|
||||||
|
)
|
||||||
|
XCTAssertTrue(largeNumberResult.contains("1000000"), "Should handle large numbers")
|
||||||
|
|
||||||
|
// Zero
|
||||||
|
let zeroResult = String(
|
||||||
|
format: String(localized: "content.accessibility.people_count", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
XCTAssertTrue(zeroResult.contains("0"), "Should handle zero")
|
||||||
|
|
||||||
|
// Empty geohash (edge case)
|
||||||
|
let emptyGeohashResult = String(
|
||||||
|
format: String(localized: "location_notes.header", bundle: appBundle(), comment: "Test"),
|
||||||
|
locale: .current,
|
||||||
|
"", 1
|
||||||
|
)
|
||||||
|
XCTAssertFalse(emptyGeohashResult.isEmpty, "Should handle empty geohash")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user