1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@testable import Grain
import XCTest
/// Count formatting appears under every gallery and on every profile, and the
/// digit proxy is what stops those counts from shifting the layout when they
/// tick over. Both are pure string work, so they are cheap to pin down exactly.
final class StringExtensionsTests: GrainTestCase {
// MARK: - compactCount
func testCountsBelowAThousandAreLeftAlone() {
XCTAssertEqual(0.compactCount, "0")
XCTAssertEqual(7.compactCount, "7")
XCTAssertEqual(999.compactCount, "999")
}
func testRoundThousandsDropTheDecimal() {
XCTAssertEqual(1000.compactCount, "1K")
XCTAssertEqual(12000.compactCount, "12K")
}
func testUnevenThousandsKeepOneDecimal() {
XCTAssertEqual(1500.compactCount, "1.5K")
XCTAssertEqual(9900.compactCount, "9.9K")
}
func testRoundMillionsDropTheDecimal() {
XCTAssertEqual(1_000_000.compactCount, "1M")
XCTAssertEqual(3_000_000.compactCount, "3M")
}
func testUnevenMillionsKeepOneDecimal() {
XCTAssertEqual(2_500_000.compactCount, "2.5M")
}
/// The thousands branch has to lose to the millions branch, or 1M reads as
/// "1000K".
func testTheMillionsBranchWinsAtExactlyAMillion() {
XCTAssertEqual(999_000.compactCount, "999K")
XCTAssertEqual(1_000_000.compactCount, "1M")
}
/// Counts are never negative in practice, but the formatter still has to
/// return something printable rather than an abbreviated negative.
func testNegativeCountsAreLeftAlone() {
XCTAssertEqual((-5).compactCount, "-5")
}
// MARK: - digitWidthProxy
/// The proxy exists to reserve width, so it must be the same length as the
/// string it stands in for, with only digits replaced.
func testDigitsBecomeEightsAndNothingElseChanges() {
XCTAssertEqual("1.5K".digitWidthProxy, "8.8K")
XCTAssertEqual("2025-01-02".digitWidthProxy, "8888-88-88")
XCTAssertEqual("1,234 favorites".digitWidthProxy, "8,888 favorites")
}
func testStringsWithoutDigitsAreUnchanged() {
XCTAssertEqual("".digitWidthProxy, "")
XCTAssertEqual("Favorites".digitWidthProxy, "Favorites")
}
func testProxyPreservesLength() {
for sample in ["0", "999", "12.3M", "3 hours ago"] {
XCTAssertEqual(sample.digitWidthProxy.count, sample.count, "Proxy for \(sample) changed length")
}
}
/// Non-ASCII digits aren't in the "0"..."9" range, so they pass through —
/// worth pinning so a future rewrite doesn't silently start mangling them.
func testNonASCIIDigitsPassThrough() {
XCTAssertEqual("٣".digitWidthProxy, "٣")
}
}