Skip to content

Commit 1f8e8c9

Browse files
committed
Revert "Revert "Add tests for AttributedString Index Tracking preconditions (#1326)" (#1337)"
This reverts commit 2df5199.
1 parent 2df5199 commit 1f8e8c9

File tree

2 files changed

+162
-74
lines changed

2 files changed

+162
-74
lines changed

Package.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ let wasiLibcCSettings: [CSetting] = [
7575
.define("_WASI_EMULATED_MMAN", .when(platforms: [.wasi])),
7676
]
7777

78+
let testOnlySwiftSettings: [SwiftSetting] = [
79+
.define("FOUNDATION_EXIT_TESTS", .when(platforms: [.macOS, .linux])) // The latest Windows toolchain does not yet have exit tests in swift-testing
80+
]
81+
7882
let package = Package(
7983
name: "swift-foundation",
8084
platforms: [.macOS("15"), .iOS("18"), .tvOS("18"), .watchOS("11")],
@@ -171,7 +175,7 @@ let package = Package(
171175
"LifetimeDependenceMutableAccessors",
172176
.when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux])
173177
),
174-
] + availabilityMacros + featureSettings
178+
] + availabilityMacros + featureSettings + testOnlySwiftSettings
175179
),
176180

177181
// FoundationInternationalization
@@ -204,7 +208,7 @@ let package = Package(
204208
"TestSupport",
205209
"FoundationInternationalization",
206210
],
207-
swiftSettings: availabilityMacros + featureSettings
211+
swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings
208212
),
209213

210214
// FoundationMacros
@@ -236,7 +240,7 @@ package.targets.append(contentsOf: [
236240
"FoundationMacros",
237241
"TestSupport"
238242
],
239-
swiftSettings: availabilityMacros + featureSettings
243+
swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings
240244
)
241245
])
242246
#endif

Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift

Lines changed: 155 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,202 +10,286 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#if canImport(TestSupport)
14-
import TestSupport
13+
import Testing
14+
15+
#if canImport(FoundationEssentials)
16+
import FoundationEssentials
17+
#else
18+
import Foundation
1519
#endif
1620

17-
final class AttributedStringIndexTrackingTests: XCTestCase {
18-
func testBasic() throws {
21+
@Suite("AttributedString Index Tracking")
22+
private struct AttributedStringIndexTrackingTests {
23+
@Test
24+
func basics() throws {
1925
var text = AttributedString("ABC. Hello, world!")
2026
let original = text
21-
let helloRange = try XCTUnwrap(text.range(of: "Hello"))
22-
let worldRange = try XCTUnwrap(text.range(of: "world"))
27+
let helloRange = try #require(text.range(of: "Hello"))
28+
let worldRange = try #require(text.range(of: "world"))
2329

24-
let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) {
30+
let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) {
2531
$0.insert(AttributedString("Goodbye. "), at: $0.startIndex)
2632
})
2733

28-
XCTAssertEqual(updatedRanges.count, 2)
29-
XCTAssertEqual(text[updatedRanges[0]], original[helloRange])
30-
XCTAssertEqual(text[updatedRanges[1]], original[worldRange])
34+
#expect(updatedRanges.count == 2)
35+
#expect(text[updatedRanges[0]] == original[helloRange])
36+
#expect(text[updatedRanges[1]] == original[worldRange])
3137
}
3238

33-
func testInsertionWithinRange() throws {
39+
@Test
40+
func insertionWithinRange() throws {
3441
var text = AttributedString("Hello, world")
35-
var helloRange = try XCTUnwrap(text.range(of: "Hello"))
42+
var helloRange = try #require(text.range(of: "Hello"))
3643

3744
text.transform(updating: &helloRange) {
3845
$0.insert(AttributedString("_Goodbye_"), at: $0.index($0.startIndex, offsetByCharacters: 3))
3946
}
4047

41-
XCTAssertEqual(String(text[helloRange].characters), "Hel_Goodbye_lo")
48+
#expect(String(text[helloRange].characters) == "Hel_Goodbye_lo")
4249
}
4350

44-
func testInsertionAtStartOfRange() throws {
51+
@Test
52+
func insertionAtStartOfRange() throws {
4553
var text = AttributedString("Hello, world")
46-
let helloRange = try XCTUnwrap(text.range(of: "llo"))
54+
let helloRange = try #require(text.range(of: "llo"))
4755

48-
let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) {
56+
let updatedHelloRange = try #require(text.transform(updating: helloRange) {
4957
$0.insert(AttributedString("_"), at: helloRange.lowerBound)
5058
})
5159

52-
XCTAssertEqual(String(text[updatedHelloRange].characters), "llo")
60+
#expect(String(text[updatedHelloRange].characters) == "llo")
5361
}
5462

55-
func testInsertionAtEndOfRange() throws {
63+
@Test
64+
func insertionAtEndOfRange() throws {
5665
var text = AttributedString("Hello, world")
57-
let helloRange = try XCTUnwrap(text.range(of: "llo"))
66+
let helloRange = try #require(text.range(of: "llo"))
5867

59-
let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) {
68+
let updatedHelloRange = try #require(text.transform(updating: helloRange) {
6069
$0.insert(AttributedString("_"), at: helloRange.upperBound)
6170
})
6271

63-
XCTAssertEqual(String(text[updatedHelloRange].characters), "llo")
72+
#expect(String(text[updatedHelloRange].characters) == "llo")
6473
}
6574

66-
func testInsertionAtEmptyRange() throws {
75+
@Test
76+
func insertionAtEmptyRange() throws {
6777
var text = AttributedString("ABCDE")
6878
let idx = text.index(text.startIndex, offsetByCharacters: 3)
6979

70-
let updatedRange = try XCTUnwrap(text.transform(updating: idx ..< idx) {
80+
let updatedRange = try #require(text.transform(updating: idx ..< idx) {
7181
$0.insert(AttributedString("_"), at: idx)
7282
})
7383

74-
XCTAssertEqual(updatedRange.lowerBound, updatedRange.upperBound)
75-
XCTAssertEqual(text.characters[updatedRange.lowerBound], "D")
84+
#expect(updatedRange.lowerBound == updatedRange.upperBound)
85+
#expect(text.characters[updatedRange.lowerBound] == "D")
7686
}
7787

78-
func testRemovalWithinRange() throws {
88+
@Test
89+
func removalWithinRange() throws {
7990
var text = AttributedString("Hello, world")
80-
var helloRange = try XCTUnwrap(text.range(of: "Hello"))
91+
var helloRange = try #require(text.range(of: "Hello"))
8192

8293
try text.transform(updating: &helloRange) {
83-
$0.removeSubrange(try XCTUnwrap($0.range(of: "ll")))
94+
$0.removeSubrange(try #require($0.range(of: "ll")))
8495
}
8596

86-
XCTAssertEqual(String(text[helloRange].characters), "Heo")
97+
#expect(String(text[helloRange].characters) == "Heo")
8798
}
8899

89-
func testFullCollapse() throws {
100+
@Test
101+
func fullCollapse() throws {
90102
do {
91103
var text = AttributedString("Hello, world")
92-
var helloRange = try XCTUnwrap(text.range(of: "Hello"))
104+
var helloRange = try #require(text.range(of: "Hello"))
93105

94106
text.transform(updating: &helloRange) {
95107
$0.removeSubrange($0.startIndex ..< $0.endIndex)
96108
}
97109

98-
XCTAssertEqual(String(text[helloRange].characters), "")
110+
#expect(String(text[helloRange].characters) == "")
99111
}
100112

101113
do {
102114
var text = AttributedString("Hello, world")
103-
let helloRange = try XCTUnwrap(text.range(of: "Hello"))
115+
let helloRange = try #require(text.range(of: "Hello"))
104116

105-
let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) {
117+
let updatedHelloRange = try #require(text.transform(updating: helloRange) {
106118
$0.removeSubrange(helloRange)
107119
})
108120

109-
XCTAssertEqual(String(text[updatedHelloRange].characters), "")
121+
#expect(String(text[updatedHelloRange].characters) == "")
110122
}
111123

112124
do {
113125
var text = AttributedString("Hello, world")
114-
var helloRange = try XCTUnwrap(text.range(of: ", "))
126+
var helloRange = try #require(text.range(of: ", "))
115127

116128
try text.transform(updating: &helloRange) {
117-
$0.removeSubrange(try XCTUnwrap($0.range(of: "o, w")))
129+
$0.removeSubrange(try #require($0.range(of: "o, w")))
118130
}
119131

120-
XCTAssertEqual(String(text[helloRange].characters), "")
132+
#expect(String(text[helloRange].characters) == "")
121133
let collapsedIdx = text.index(text.startIndex, offsetByCharacters: 4)
122-
XCTAssertEqual(helloRange, collapsedIdx ..< collapsedIdx)
134+
#expect(helloRange == collapsedIdx ..< collapsedIdx)
123135
}
124136
}
125137

126-
func testCollapseLeft() throws {
138+
@Test
139+
func collapseLeft() throws {
127140
var text = AttributedString("Hello, world")
128-
var helloRange = try XCTUnwrap(text.range(of: "Hello"))
141+
var helloRange = try #require(text.range(of: "Hello"))
129142

130143
try text.transform(updating: &helloRange) {
131-
$0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo")))
144+
$0.removeSubrange(try #require($0.range(of: "llo, wo")))
132145
}
133146

134-
XCTAssertEqual(String(text[helloRange].characters), "He")
147+
#expect(String(text[helloRange].characters) == "He")
135148
}
136149

137-
func testCollapseRight() throws {
150+
@Test
151+
func collapseRight() throws {
138152
var text = AttributedString("Hello, world")
139-
var worldRange = try XCTUnwrap(text.range(of: "world"))
153+
var worldRange = try #require(text.range(of: "world"))
140154

141155
try text.transform(updating: &worldRange) {
142-
$0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo")))
156+
$0.removeSubrange(try #require($0.range(of: "llo, wo")))
143157
}
144158

145-
XCTAssertEqual(String(text[worldRange].characters), "rld")
159+
#expect(String(text[worldRange].characters) == "rld")
146160
}
147161

148-
func testNesting() throws {
162+
@Test
163+
func nesting() throws {
149164
var text = AttributedString("Hello, world")
150-
var helloRange = try XCTUnwrap(text.range(of: "Hello"))
165+
var helloRange = try #require(text.range(of: "Hello"))
151166
try text.transform(updating: &helloRange) {
152-
var worldRange = try XCTUnwrap($0.range(of: "world"))
167+
var worldRange = try #require($0.range(of: "world"))
153168
try $0.transform(updating: &worldRange) {
154-
$0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo")))
169+
$0.removeSubrange(try #require($0.range(of: "llo, wo")))
155170
}
156-
XCTAssertEqual(String($0[worldRange].characters), "rld")
171+
#expect(String($0[worldRange].characters) == "rld")
157172
}
158-
XCTAssertEqual(String(text[helloRange].characters), "He")
173+
#expect(String(text[helloRange].characters) == "He")
159174
}
160175

161-
func testTrackingLost() throws {
176+
#if FOUNDATION_EXIT_TESTS
177+
@Test
178+
func trackingLostPreconditions() async {
179+
await #expect(processExitsWith: .failure) {
180+
var text = AttributedString("Hello, world")
181+
var helloRange = try #require(text.range(of: "Hello"))
182+
text.transform(updating: &helloRange) {
183+
$0 = AttributedString("Foo")
184+
}
185+
}
186+
187+
await #expect(processExitsWith: .failure) {
188+
var text = AttributedString("Hello, world")
189+
var helloRange = try #require(text.range(of: "Hello"))
190+
text.transform(updating: &helloRange) {
191+
$0 = AttributedString("Hello world")
192+
}
193+
}
194+
195+
await #expect(processExitsWith: .failure) {
196+
var text = AttributedString("Hello, world")
197+
var ranges = [try #require(text.range(of: "Hello"))]
198+
text.transform(updating: &ranges) {
199+
$0 = AttributedString("Foo")
200+
}
201+
}
202+
203+
await #expect(processExitsWith: .failure) {
204+
var text = AttributedString("Hello, world")
205+
var ranges = [try #require(text.range(of: "Hello"))]
206+
text.transform(updating: &ranges) {
207+
$0 = AttributedString("Hello world")
208+
}
209+
}
210+
}
211+
#endif
212+
213+
@Test
214+
func trackingLost() throws {
162215
let text = AttributedString("Hello, world")
163-
let helloRange = try XCTUnwrap(text.range(of: "Hello"))
216+
let helloRange = try #require(text.range(of: "Hello"))
164217

165218
do {
166219
var copy = text
167-
XCTAssertNil(copy.transform(updating: helloRange) {
220+
#expect(copy.transform(updating: helloRange) {
168221
$0 = AttributedString("Foo")
169-
})
222+
} == nil)
170223
}
171224

172225
do {
173226
var copy = text
174-
XCTAssertNil(copy.transform(updating: helloRange) {
227+
#expect(copy.transform(updating: helloRange) {
175228
$0 = AttributedString("Hello world")
176-
})
229+
} == nil)
177230
}
178231

179232
do {
180233
var copy = text
181-
XCTAssertNotNil(copy.transform(updating: helloRange) {
234+
#expect(copy.transform(updating: helloRange) {
182235
$0 = $0
183-
})
236+
} != nil)
184237
}
185238

186239
do {
187240
var copy = text
188-
XCTAssertNotNil(copy.transform(updating: helloRange) {
241+
#expect(copy.transform(updating: helloRange) {
189242
var reference = $0
190243
reference.testInt = 2
191244
$0 = $0
192-
})
193-
XCTAssertNil(copy.testInt)
245+
} != nil)
246+
#expect(copy.testInt == nil)
194247
}
195248
}
196249

197-
func testAttributeMutation() throws {
250+
@Test
251+
func attributeMutation() throws {
198252
var text = AttributedString("Hello, world!")
199253
let original = text
200-
let helloRange = try XCTUnwrap(text.range(of: "Hello"))
201-
let worldRange = try XCTUnwrap(text.range(of: "world"))
254+
let helloRange = try #require(text.range(of: "Hello"))
255+
let worldRange = try #require(text.range(of: "world"))
202256

203-
let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) {
257+
let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) {
204258
$0.testInt = 2
205259
})
206260

207-
XCTAssertEqual(updatedRanges.count, 2)
208-
XCTAssertEqual(AttributedString(text[updatedRanges[0]]), original[helloRange].settingAttributes(AttributeContainer.testInt(2)))
209-
XCTAssertEqual(AttributedString(text[updatedRanges[1]]), original[worldRange].settingAttributes(AttributeContainer.testInt(2)))
261+
#expect(updatedRanges.count == 2)
262+
#expect(AttributedString(text[updatedRanges[0]]) == original[helloRange].settingAttributes(AttributeContainer.testInt(2)))
263+
#expect(AttributedString(text[updatedRanges[1]]) == original[worldRange].settingAttributes(AttributeContainer.testInt(2)))
264+
}
265+
266+
#if FOUNDATION_EXIT_TESTS
267+
@Test
268+
func invalidInputRanges() async {
269+
await #expect(processExitsWith: .failure) {
270+
var text = AttributedString("Hello, world")
271+
let other = text + AttributedString("Extra text")
272+
let range = other.startIndex ..< other.endIndex
273+
_ = text.transform(updating: range) { _ in
274+
275+
}
276+
}
277+
278+
await #expect(processExitsWith: .failure) {
279+
var text = AttributedString("Hello, world")
280+
let other = text + AttributedString("Extra text")
281+
let range = other.endIndex ..< other.endIndex
282+
_ = text.transform(updating: range) { _ in
283+
284+
}
285+
}
286+
287+
await #expect(processExitsWith: .failure) {
288+
var text = AttributedString("Hello, world")
289+
_ = text.transform(updating: []) { _ in
290+
291+
}
292+
}
210293
}
294+
#endif
211295
}

0 commit comments

Comments
 (0)