diff --git a/Package.swift b/Package.swift index e9a3dec09..eb6ec7623 100644 --- a/Package.swift +++ b/Package.swift @@ -75,6 +75,10 @@ let wasiLibcCSettings: [CSetting] = [ .define("_WASI_EMULATED_MMAN", .when(platforms: [.wasi])), ] +let testOnlySwiftSettings: [SwiftSetting] = [ + .define("FOUNDATION_EXIT_TESTS", .when(platforms: [.macOS, .linux])) // The latest Windows toolchain does not yet have exit tests in swift-testing +] + let package = Package( name: "swift-foundation", platforms: [.macOS("15"), .iOS("18"), .tvOS("18"), .watchOS("11")], @@ -171,7 +175,7 @@ let package = Package( "LifetimeDependenceMutableAccessors", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) ), - ] + availabilityMacros + featureSettings + ] + availabilityMacros + featureSettings + testOnlySwiftSettings ), // FoundationInternationalization @@ -204,7 +208,7 @@ let package = Package( "TestSupport", "FoundationInternationalization", ], - swiftSettings: availabilityMacros + featureSettings + swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings ), // FoundationMacros @@ -236,7 +240,7 @@ package.targets.append(contentsOf: [ "FoundationMacros", "TestSupport" ], - swiftSettings: availabilityMacros + featureSettings + swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings ) ]) #endif diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift index 01f2c5bf1..e89f1a4a0 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift @@ -10,202 +10,286 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringIndexTrackingTests: XCTestCase { - func testBasic() throws { +@Suite("AttributedString Index Tracking") +private struct AttributedStringIndexTrackingTests { + @Test + func basics() throws { var text = AttributedString("ABC. Hello, world!") let original = text - let helloRange = try XCTUnwrap(text.range(of: "Hello")) - let worldRange = try XCTUnwrap(text.range(of: "world")) + let helloRange = try #require(text.range(of: "Hello")) + let worldRange = try #require(text.range(of: "world")) - let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) { + let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) { $0.insert(AttributedString("Goodbye. "), at: $0.startIndex) }) - XCTAssertEqual(updatedRanges.count, 2) - XCTAssertEqual(text[updatedRanges[0]], original[helloRange]) - XCTAssertEqual(text[updatedRanges[1]], original[worldRange]) + #expect(updatedRanges.count == 2) + #expect(text[updatedRanges[0]] == original[helloRange]) + #expect(text[updatedRanges[1]] == original[worldRange]) } - func testInsertionWithinRange() throws { + @Test + func insertionWithinRange() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) text.transform(updating: &helloRange) { $0.insert(AttributedString("_Goodbye_"), at: $0.index($0.startIndex, offsetByCharacters: 3)) } - XCTAssertEqual(String(text[helloRange].characters), "Hel_Goodbye_lo") + #expect(String(text[helloRange].characters) == "Hel_Goodbye_lo") } - func testInsertionAtStartOfRange() throws { + @Test + func insertionAtStartOfRange() throws { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "llo")) + let helloRange = try #require(text.range(of: "llo")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.insert(AttributedString("_"), at: helloRange.lowerBound) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "llo") + #expect(String(text[updatedHelloRange].characters) == "llo") } - func testInsertionAtEndOfRange() throws { + @Test + func insertionAtEndOfRange() throws { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "llo")) + let helloRange = try #require(text.range(of: "llo")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.insert(AttributedString("_"), at: helloRange.upperBound) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "llo") + #expect(String(text[updatedHelloRange].characters) == "llo") } - func testInsertionAtEmptyRange() throws { + @Test + func insertionAtEmptyRange() throws { var text = AttributedString("ABCDE") let idx = text.index(text.startIndex, offsetByCharacters: 3) - let updatedRange = try XCTUnwrap(text.transform(updating: idx ..< idx) { + let updatedRange = try #require(text.transform(updating: idx ..< idx) { $0.insert(AttributedString("_"), at: idx) }) - XCTAssertEqual(updatedRange.lowerBound, updatedRange.upperBound) - XCTAssertEqual(text.characters[updatedRange.lowerBound], "D") + #expect(updatedRange.lowerBound == updatedRange.upperBound) + #expect(text.characters[updatedRange.lowerBound] == "D") } - func testRemovalWithinRange() throws { + @Test + func removalWithinRange() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "ll"))) + $0.removeSubrange(try #require($0.range(of: "ll"))) } - XCTAssertEqual(String(text[helloRange].characters), "Heo") + #expect(String(text[helloRange].characters) == "Heo") } - func testFullCollapse() throws { + @Test + func fullCollapse() throws { do { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) text.transform(updating: &helloRange) { $0.removeSubrange($0.startIndex ..< $0.endIndex) } - XCTAssertEqual(String(text[helloRange].characters), "") + #expect(String(text[helloRange].characters) == "") } do { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "Hello")) + let helloRange = try #require(text.range(of: "Hello")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.removeSubrange(helloRange) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "") + #expect(String(text[updatedHelloRange].characters) == "") } do { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: ", ")) + var helloRange = try #require(text.range(of: ", ")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "o, w"))) + $0.removeSubrange(try #require($0.range(of: "o, w"))) } - XCTAssertEqual(String(text[helloRange].characters), "") + #expect(String(text[helloRange].characters) == "") let collapsedIdx = text.index(text.startIndex, offsetByCharacters: 4) - XCTAssertEqual(helloRange, collapsedIdx ..< collapsedIdx) + #expect(helloRange == collapsedIdx ..< collapsedIdx) } } - func testCollapseLeft() throws { + @Test + func collapseLeft() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String(text[helloRange].characters), "He") + #expect(String(text[helloRange].characters) == "He") } - func testCollapseRight() throws { + @Test + func collapseRight() throws { var text = AttributedString("Hello, world") - var worldRange = try XCTUnwrap(text.range(of: "world")) + var worldRange = try #require(text.range(of: "world")) try text.transform(updating: &worldRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String(text[worldRange].characters), "rld") + #expect(String(text[worldRange].characters) == "rld") } - func testNesting() throws { + @Test + func nesting() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - var worldRange = try XCTUnwrap($0.range(of: "world")) + var worldRange = try #require($0.range(of: "world")) try $0.transform(updating: &worldRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String($0[worldRange].characters), "rld") + #expect(String($0[worldRange].characters) == "rld") } - XCTAssertEqual(String(text[helloRange].characters), "He") + #expect(String(text[helloRange].characters) == "He") } - func testTrackingLost() throws { + #if FOUNDATION_EXIT_TESTS + @Test + func trackingLostPreconditions() async { + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var helloRange = try #require(text.range(of: "Hello")) + text.transform(updating: &helloRange) { + $0 = AttributedString("Foo") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var helloRange = try #require(text.range(of: "Hello")) + text.transform(updating: &helloRange) { + $0 = AttributedString("Hello world") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var ranges = [try #require(text.range(of: "Hello"))] + text.transform(updating: &ranges) { + $0 = AttributedString("Foo") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var ranges = [try #require(text.range(of: "Hello"))] + text.transform(updating: &ranges) { + $0 = AttributedString("Hello world") + } + } + } + #endif + + @Test + func trackingLost() throws { let text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "Hello")) + let helloRange = try #require(text.range(of: "Hello")) do { var copy = text - XCTAssertNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = AttributedString("Foo") - }) + } == nil) } do { var copy = text - XCTAssertNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = AttributedString("Hello world") - }) + } == nil) } do { var copy = text - XCTAssertNotNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = $0 - }) + } != nil) } do { var copy = text - XCTAssertNotNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { var reference = $0 reference.testInt = 2 $0 = $0 - }) - XCTAssertNil(copy.testInt) + } != nil) + #expect(copy.testInt == nil) } } - func testAttributeMutation() throws { + @Test + func attributeMutation() throws { var text = AttributedString("Hello, world!") let original = text - let helloRange = try XCTUnwrap(text.range(of: "Hello")) - let worldRange = try XCTUnwrap(text.range(of: "world")) + let helloRange = try #require(text.range(of: "Hello")) + let worldRange = try #require(text.range(of: "world")) - let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) { + let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) { $0.testInt = 2 }) - XCTAssertEqual(updatedRanges.count, 2) - XCTAssertEqual(AttributedString(text[updatedRanges[0]]), original[helloRange].settingAttributes(AttributeContainer.testInt(2))) - XCTAssertEqual(AttributedString(text[updatedRanges[1]]), original[worldRange].settingAttributes(AttributeContainer.testInt(2))) + #expect(updatedRanges.count == 2) + #expect(AttributedString(text[updatedRanges[0]]) == original[helloRange].settingAttributes(AttributeContainer.testInt(2))) + #expect(AttributedString(text[updatedRanges[1]]) == original[worldRange].settingAttributes(AttributeContainer.testInt(2))) + } + + #if FOUNDATION_EXIT_TESTS + @Test + func invalidInputRanges() async { + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + let other = text + AttributedString("Extra text") + let range = other.startIndex ..< other.endIndex + _ = text.transform(updating: range) { _ in + + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + let other = text + AttributedString("Extra text") + let range = other.endIndex ..< other.endIndex + _ = text.transform(updating: range) { _ in + + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + _ = text.transform(updating: []) { _ in + + } + } } + #endif }