From f0e3dc9c230e5df0f467868b552bb46846883f83 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 5 Jun 2025 10:38:52 -0700 Subject: [PATCH 1/7] Convert CoW tests to swift-testing --- .../AttributedStringCOWTests.swift | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift index 2f8fd4b52..d31a835c4 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift @@ -10,14 +10,12 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials +#else +@testable import Foundation #endif extension AttributedStringProtocol { @@ -27,7 +25,8 @@ extension AttributedStringProtocol { } /// Tests for `AttributedString` to confirm expected CoW behavior -final class TestAttributedStringCOW: XCTestCase { +@Suite("AttributedString Copy on Write") +private struct AttributedStringCOWTests { // MARK: - Utility Functions @@ -38,32 +37,32 @@ final class TestAttributedStringCOW: XCTestCase { return str } - func assertCOWCopy(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWCopy(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { let str = createAttributedString() var copy = str operation(©) - XCTAssertNotEqual(str, copy, "Mutation operation did not copy when multiple references exist", file: file, line: line) + #expect(str != copy, "Mutation operation did not copy when multiple references exist", sourceLocation: sourceLocation) } - func assertCOWCopyManual(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWCopyManual(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { var str = createAttributedString() let gutsPtr = Unmanaged.passUnretained(str._guts) operation(&str) let newGutsPtr = Unmanaged.passUnretained(str._guts) - XCTAssertNotEqual(gutsPtr.toOpaque(), newGutsPtr.toOpaque(), "Mutation operation with manual copy did not perform copy", file: file, line: line) + #expect(gutsPtr.toOpaque() != newGutsPtr.toOpaque(), "Mutation operation with manual copy did not perform copy", sourceLocation: sourceLocation) } - func assertCOWNoCopy(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWNoCopy(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { var str = createAttributedString() let gutsPtr = Unmanaged.passUnretained(str._guts) operation(&str) let newGutsPtr = Unmanaged.passUnretained(str._guts) - XCTAssertEqual(gutsPtr.toOpaque(), newGutsPtr.toOpaque(), "Mutation operation copied when only one reference exists", file: file, line: line) + #expect(gutsPtr.toOpaque() == newGutsPtr.toOpaque(), "Mutation operation copied when only one reference exists", sourceLocation: sourceLocation) } - func assertCOWBehavior(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { - assertCOWCopy(file: file, line: line, operation) - assertCOWNoCopy(file: file, line: line, operation) + func assertCOWBehavior(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { + assertCOWCopy(sourceLocation: sourceLocation, operation) + assertCOWNoCopy(sourceLocation: sourceLocation, operation) } func makeSubrange(_ str: AttributedString) -> Range { @@ -76,13 +75,13 @@ final class TestAttributedStringCOW: XCTestCase { return RangeSet([rangeA, rangeB]) } - lazy var container: AttributeContainer = { + let container: AttributeContainer = { var container = AttributeContainer() container.testInt = 2 return container }() - lazy var containerB: AttributeContainer = { + let containerB: AttributeContainer = { var container = AttributeContainer() container.testBool = true return container @@ -90,7 +89,8 @@ final class TestAttributedStringCOW: XCTestCase { // MARK: - Tests - func testTopLevelType() { + @Test + func topLevelType() { assertCOWBehavior { (str) in str.setAttributes(container) } @@ -126,7 +126,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testSubstring() { + @Test + func substring() { assertCOWBehavior { (str) in str[makeSubrange(str)].setAttributes(container) } @@ -147,7 +148,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testDiscontiguousSubstring() { + @Test + func discontiguousSubstring() { assertCOWBehavior { (str) in str[makeSubranges(str)].setAttributes(container) } @@ -172,7 +174,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testCharacters() { + @Test + func characters() { let char: Character = "a" assertCOWBehavior { (str) in @@ -195,7 +198,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testUnicodeScalars() { + @Test + func unicodeScalars() { let scalar: UnicodeScalar = "a" assertCOWBehavior { (str) in @@ -203,7 +207,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testGenericProtocol() { + @Test + func genericProtocol() { assertCOWBehavior { $0.genericSetAttribute() } @@ -212,7 +217,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testIndexTracking() { + @Test + func indexTracking() { assertCOWBehavior { _ = $0.transform(updating: $0.startIndex ..< $0.endIndex) { $0.testInt = 2 @@ -243,7 +249,7 @@ final class TestAttributedStringCOW: XCTestCase { storage = $0 } } - XCTAssertNotEqual(storage, "") + #expect(storage != "") // Ensure the same semantics hold even when the closure throws storage = AttributedString() @@ -255,6 +261,6 @@ final class TestAttributedStringCOW: XCTestCase { throw CocoaError(.fileReadUnknown) } } - XCTAssertNotEqual(storage, "") + #expect(storage != "") } } From d3ff9c9836f9e8e7a2190b0601b9ee417103a8be Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 5 Jun 2025 10:39:11 -0700 Subject: [PATCH 2/7] Convert discontiguous tests to swift-testing --- .../AttributedStringDiscontiguousTests.swift | 130 ++++++++++-------- 1 file changed, 73 insertions(+), 57 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift index 79b7fa344..ae247167b 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift @@ -10,61 +10,70 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringDiscontiguousTests: XCTestCase { - func testEmptySlice() { +@Suite("Discontiguous AttributedString") +private struct AttributedStringDiscontiguousTests { + @Test + func emptySlice() { let str = AttributedString() let slice = str[RangeSet()] - XCTAssertTrue(slice.runs.isEmpty) - XCTAssertTrue(slice.characters.isEmpty) - XCTAssertTrue(slice.unicodeScalars.isEmpty) - XCTAssertEqual(slice, slice) - XCTAssertEqual(slice.runs.startIndex, slice.runs.endIndex) - XCTAssertEqual(slice.characters.startIndex, slice.characters.endIndex) - XCTAssertEqual(slice.unicodeScalars.startIndex, slice.unicodeScalars.endIndex) - XCTAssertEqual(AttributedString("abc")[RangeSet()], AttributedString("def")[RangeSet()]) + #expect(slice.runs.isEmpty) + #expect(slice.characters.isEmpty) + #expect(slice.unicodeScalars.isEmpty) + #expect(slice == slice) + #expect(slice.runs.startIndex == slice.runs.endIndex) + #expect(slice.characters.startIndex == slice.characters.endIndex) + #expect(slice.unicodeScalars.startIndex == slice.unicodeScalars.endIndex) + #expect(AttributedString("abc")[RangeSet()] == AttributedString("def")[RangeSet()]) for r in slice.runs { - XCTFail("Enumerating empty runs should not have produced \(r)") + Issue.record("Enumerating empty runs should not have produced \(r)") } for c in slice.characters { - XCTFail("Enumerating empty characters should not have produced \(c)") + Issue.record("Enumerating empty characters should not have produced \(c)") } for s in slice.unicodeScalars { - XCTFail("Enumerating empty unicode scalars should not have produced \(s)") + Issue.record("Enumerating empty unicode scalars should not have produced \(s)") } } - func testCharacters() { + @Test + func characters() { let str = AttributedString("abcdefgabc") let fullSlice = str[str.startIndex ..< str.endIndex].characters let fullDiscontiguousSlice = str[RangeSet(str.startIndex ..< str.endIndex)].characters - XCTAssertTrue(fullSlice.elementsEqual(fullDiscontiguousSlice)) + #expect(fullSlice.elementsEqual(fullDiscontiguousSlice)) let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 3) let rangeB = str.index(str.endIndex, offsetByCharacters: -3) ..< str.endIndex let rangeSet = RangeSet([rangeA, rangeB]) let slice = str[rangeSet].characters - XCTAssertEqual(Array(slice), ["a", "b", "c", "a", "b", "c"]) + #expect(Array(slice) == ["a", "b", "c", "a", "b", "c"]) } - func testUnicodeScalars() { + @Test + func unicodeScalars() { let str = AttributedString("abcdefgabc") let fullSlice = str[str.startIndex ..< str.endIndex].unicodeScalars let fullDiscontiguousSlice = str[RangeSet(str.startIndex ..< str.endIndex)].unicodeScalars - XCTAssertTrue(fullSlice.elementsEqual(fullDiscontiguousSlice)) + #expect(fullSlice.elementsEqual(fullDiscontiguousSlice)) let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByUnicodeScalars: 3) let rangeB = str.index(str.endIndex, offsetByUnicodeScalars: -3) ..< str.endIndex let rangeSet = RangeSet([rangeA, rangeB]) let slice = str[rangeSet].unicodeScalars - XCTAssertEqual(Array(slice), ["a", "b", "c", "a", "b", "c"]) + #expect(Array(slice) == ["a", "b", "c", "a", "b", "c"]) } - func testAttributes() { + @Test + func attributes() { let str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -78,7 +87,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].testInt = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -88,7 +97,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].test.testInt = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -98,7 +107,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range][AttributeScopes.TestAttributes.TestIntAttribute.self] = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -110,15 +119,15 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].testInt = nil } - XCTAssertEqual(a, b) + #expect(a == b) } do { var a = str a.testInt = 2 - XCTAssertEqual(a[ranges].testInt, 2) + #expect(a[ranges].testInt == 2) a[rangeA].testInt = 3 - XCTAssertEqual(a[ranges].testInt, nil) + #expect(a[ranges].testInt == nil) } do { @@ -130,7 +139,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].mergeAttributes(AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -142,7 +151,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].setAttributes(AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -154,7 +163,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].replaceAttributes(AttributeContainer(), with: AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -166,11 +175,12 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].replaceAttributes(AttributeContainer.testString("foo"), with: AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } } - func testReinitialization() { + @Test + func reinitialization() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -179,10 +189,11 @@ final class AttributedStringDiscontiguousTests: XCTestCase { str[ranges].testInt = 2 let reinitialized = AttributedString(str[ranges]) - XCTAssertEqual(reinitialized, AttributedString("ace", attributes: AttributeContainer.testInt(2))) + #expect(reinitialized == AttributedString("ace", attributes: AttributeContainer.testInt(2))) } - func testReslicing() { + @Test + func reslicing() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -190,14 +201,15 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let ranges = RangeSet([rangeA, rangeB, rangeC]) str[ranges].testInt = 2 - XCTAssertEqual(str[ranges], str[ranges][ranges]) - XCTAssertEqual(AttributedString(str[ranges][RangeSet([rangeA, rangeB])]), AttributedString("ac", attributes: AttributeContainer.testInt(2))) - XCTAssertEqual(AttributedString(str[ranges][rangeA.lowerBound ..< rangeB.upperBound]), AttributedString("ac", attributes: AttributeContainer.testInt(2))) + #expect(str[ranges] == str[ranges][ranges]) + #expect(AttributedString(str[ranges][RangeSet([rangeA, rangeB])]) == AttributedString("ac", attributes: AttributeContainer.testInt(2))) + #expect(AttributedString(str[ranges][rangeA.lowerBound ..< rangeB.upperBound]) == AttributedString("ac", attributes: AttributeContainer.testInt(2))) - XCTAssertEqual(str[RangeSet()][RangeSet()], str[RangeSet()]) + #expect(str[RangeSet()][RangeSet()] == str[RangeSet()]) } - func testRuns() { + @Test + func runs() { var str = AttributedString("AAA", attributes: AttributeContainer.testInt(2)) str += AttributedString("BBB", attributes: AttributeContainer.testInt(3).testString("foo")) str += AttributedString("CC", attributes: AttributeContainer.testInt(3).testString("bar")) @@ -216,13 +228,14 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let runs = str[rangeSet].runs let expectedRanges = [rangeA, rangeB_first, rangeB_second, rangeC, rangeD, rangeE] - XCTAssertEqual(runs.count, expectedRanges.count) - XCTAssertEqual(runs.reversed().count, expectedRanges.reversed().count) - XCTAssertEqual(runs.map(\.range), expectedRanges) - XCTAssertEqual(runs.reversed().map(\.range), expectedRanges.reversed()) + #expect(runs.count == expectedRanges.count) + #expect(runs.reversed().count == expectedRanges.reversed().count) + #expect(runs.map(\.range) == expectedRanges) + #expect(runs.reversed().map(\.range) == expectedRanges.reversed()) } - func testCoalescedRuns() { + @Test + func coalescedRuns() { struct EquatableBox: Equatable, CustomStringConvertible { let t: T let u: U @@ -260,15 +273,16 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let runs = str[rangeSet].runs let testIntExpectation = [EquatableBox(2, rangeA), EquatableBox(3, rangeB), EquatableBox(3, rangeC), EquatableBox(nil, rangeD), EquatableBox(nil, rangeE)] - XCTAssertEqual(runs[\.testInt].map(EquatableBox.init), testIntExpectation) - XCTAssertEqual(runs[\.testInt].reversed().map(EquatableBox.init), testIntExpectation.reversed()) + #expect(runs[\.testInt].map(EquatableBox.init) == testIntExpectation) + #expect(runs[\.testInt].reversed().map(EquatableBox.init) == testIntExpectation.reversed()) let testStringExpectation = [EquatableBox(nil, rangeA), EquatableBox("foo", rangeB_first), EquatableBox("bar", rangeB_second), EquatableBox("baz", rangeC), EquatableBox(nil, rangeD), EquatableBox(nil, rangeE)] - XCTAssertEqual(runs[\.testString].map(EquatableBox.init), testStringExpectation) - XCTAssertEqual(runs[\.testString].reversed().map(EquatableBox.init), testStringExpectation.reversed()) + #expect(runs[\.testString].map(EquatableBox.init) == testStringExpectation) + #expect(runs[\.testString].reversed().map(EquatableBox.init) == testStringExpectation.reversed()) } - func testRemoveSubranges() { + @Test + func removeSubranges() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -280,10 +294,11 @@ final class AttributedStringDiscontiguousTests: XCTestCase { str.removeSubranges(ranges) let result = AttributedString("bdfg", attributes: AttributeContainer.testBool(true)) - XCTAssertEqual(str, result) + #expect(str == result) } - func testSliceSetter() { + @Test + func sliceSetter() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -296,13 +311,13 @@ final class AttributedStringDiscontiguousTests: XCTestCase { do { var copy = str copy[ranges] = copy[ranges] - XCTAssertEqual(copy, str) + #expect(copy == str) } do { var copy = str copy[ranges] = str[ranges] - XCTAssertEqual(copy, str) + #expect(copy == str) } do { @@ -313,11 +328,12 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let ranges2 = RangeSet([rangeA2, rangeB2, rangeC2]) var copy = str copy[ranges] = str2[ranges2] - XCTAssertEqual(String(copy.characters), "ZbYdXfg") + #expect(String(copy.characters) == "ZbYdXfg") } } - func testGraphemesAcrossDiscontiguousRanges() { + @Test + func graphemesAcrossDiscontiguousRanges() { let str = "a\n\u{301}" let attrStr = AttributedString(str) let strRangeA = str.startIndex ..< str.index(after: str.startIndex) // Range of 'a' @@ -335,6 +351,6 @@ final class AttributedStringDiscontiguousTests: XCTestCase { // (2) The behavior is consistent between String and AttributedString.CharacterView let strSlice = str[strRanges] let attrStrSlice = attrStr[attrStrRanges].characters - XCTAssert(strSlice.elementsEqual(attrStrSlice), "Characters \(Array(strSlice)) and \(Array(attrStrSlice)) do not match") + #expect(strSlice.elementsEqual(attrStrSlice), "Characters \(Array(strSlice)) and \(Array(attrStrSlice)) do not match") } } From 121eb89cd5508ccaf7eddcde63fc1b8cbe5f076d Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 5 Jun 2025 10:39:47 -0700 Subject: [PATCH 3/7] Convert index validity tests to swift-testing --- .../AttributedStringIndexValidityTests.swift | 194 +++++++++--------- 1 file changed, 102 insertions(+), 92 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift index 9237c0fbc..5369467ea 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift @@ -10,116 +10,125 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringIndexValidityTests: XCTestCase { - public func testStartEndRange() { +@Suite("AttributedString Index Validity") +private struct AttributedStringIndexValidityTests { + @Test + public func startEndRange() { let str = AttributedString("Hello, world") - XCTAssertTrue(str.startIndex.isValid(within: str)) - XCTAssertFalse(str.endIndex.isValid(within: str)) - XCTAssertTrue((str.startIndex ..< str.endIndex).isValid(within: str)) - XCTAssertTrue((str.startIndex ..< str.startIndex).isValid(within: str)) - XCTAssertTrue((str.endIndex ..< str.endIndex).isValid(within: str)) + #expect(str.startIndex.isValid(within: str)) + #expect(!str.endIndex.isValid(within: str)) + #expect((str.startIndex ..< str.endIndex).isValid(within: str)) + #expect((str.startIndex ..< str.startIndex).isValid(within: str)) + #expect((str.endIndex ..< str.endIndex).isValid(within: str)) let subStart = str.index(afterCharacter: str.startIndex) let subEnd = str.index(beforeCharacter: str.endIndex) do { let substr = str[str.startIndex ..< str.endIndex] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[subStart ..< str.endIndex] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[str.startIndex ..< subEnd] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[subStart ..< subEnd] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.startIndex).isValid(within: substr)) - XCTAssertTrue((substr.endIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect((substr.startIndex ..< substr.startIndex).isValid(within: substr)) + #expect((substr.endIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(str.startIndex ..< str.endIndex)] - XCTAssertTrue(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertTrue((str.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect((str.startIndex ..< str.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(subStart ..< str.endIndex)] - XCTAssertTrue(subStart.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertTrue((subStart ..< str.endIndex).isValid(within: substr)) + #expect(subStart.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect((subStart ..< str.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(str.startIndex ..< subEnd)] - XCTAssertTrue(str.startIndex.isValid(within: substr)) - XCTAssertFalse(subEnd.isValid(within: substr)) - XCTAssertTrue((str.startIndex ..< subEnd).isValid(within: substr)) + #expect(str.startIndex.isValid(within: substr)) + #expect(!subEnd.isValid(within: substr)) + #expect((str.startIndex ..< subEnd).isValid(within: substr)) } do { let substr = str[RangeSet(subStart ..< subEnd)] - XCTAssertTrue(subStart.isValid(within: substr)) - XCTAssertFalse(subEnd.isValid(within: substr)) - XCTAssertTrue((subStart ..< subEnd).isValid(within: substr)) - XCTAssertTrue((subStart ..< subStart).isValid(within: substr)) - XCTAssertTrue((subEnd ..< subEnd).isValid(within: substr)) + #expect(subStart.isValid(within: substr)) + #expect(!subEnd.isValid(within: substr)) + #expect((subStart ..< subEnd).isValid(within: substr)) + #expect((subStart ..< subStart).isValid(within: substr)) + #expect((subEnd ..< subEnd).isValid(within: substr)) } } - public func testExhaustiveIndices() { + @Test + public func exhaustiveIndices() { let str = AttributedString("Hello Cafe\u{301} 👍🏻🇺🇸 World") for idx in str.characters.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.unicodeScalars.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.utf8.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.utf16.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } } - public func testOutOfBoundsContiguous() { + @Test + public func outOfBoundsContiguous() { let str = AttributedString("Hello, world") let subStart = str.index(afterCharacter: str.startIndex) let subEnd = str.index(beforeCharacter: str.endIndex) let substr = str[subStart ..< subEnd] - XCTAssertFalse(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.endIndex).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< substr.startIndex).isValid(within: substr)) - XCTAssertFalse((substr.startIndex ..< str.endIndex).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.startIndex).isValid(within: substr)) - XCTAssertFalse((str.endIndex ..< str.endIndex).isValid(within: substr)) + #expect(!str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect(!(str.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(!(str.startIndex ..< substr.startIndex).isValid(within: substr)) + #expect(!(substr.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(!(str.startIndex ..< str.startIndex).isValid(within: substr)) + #expect(!(str.endIndex ..< str.endIndex).isValid(within: substr)) } - public func testOutOfBoundsDiscontiguous() { + @Test + public func outOfBoundsDiscontiguous() { let str = AttributedString("Hello, world") let idxA = str.index(afterCharacter: str.startIndex) let idxB = str.index(afterCharacter: idxA) @@ -128,66 +137,67 @@ final class AttributedStringIndexValidityTests: XCTestCase { let middleIdx = str.index(afterCharacter: idxB) let substr = str[RangeSet([idxA ..< idxB, idxC ..< idxD])] - XCTAssertFalse(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertFalse(idxD.isValid(within: substr)) - XCTAssertFalse(middleIdx.isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< idxA).isValid(within: substr)) - XCTAssertFalse((idxA ..< middleIdx).isValid(within: substr)) - XCTAssertFalse((middleIdx ..< idxD).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.startIndex).isValid(within: substr)) - XCTAssertFalse((str.endIndex ..< str.endIndex).isValid(within: substr)) + #expect(!str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect(!idxD.isValid(within: substr)) + #expect(!middleIdx.isValid(within: substr)) + #expect(!(str.startIndex ..< idxA).isValid(within: substr)) + #expect(!(idxA ..< middleIdx).isValid(within: substr)) + #expect(!(middleIdx ..< idxD).isValid(within: substr)) + #expect(!(str.startIndex ..< str.startIndex).isValid(within: substr)) + #expect(!(str.endIndex ..< str.endIndex).isValid(within: substr)) } - public func testMutationInvalidation() { - func checkInPlace(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { + @Test + public func mutationInvalidation() { + func checkInPlace(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { var str = AttributedString("Hello World") let idxA = str.startIndex let idxB = str.index(afterCharacter: idxA) - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", sourceLocation: sourceLocation) mutation(&str) - XCTAssertFalse(idxA.isValid(within: str), "Initial index A was valid in in-place mutated", file: file, line: line) - XCTAssertFalse(idxB.isValid(within: str), "Initial index B was valid in in-place mutated", file: file, line: line) - XCTAssertFalse((idxA ..< idxB).isValid(within: str), "Initial range was valid in in-place mutated", file: file, line: line) - XCTAssertFalse(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was valid in in-place mutated", file: file, line: line) + #expect(!idxA.isValid(within: str), "Initial index A was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!idxB.isValid(within: str), "Initial index B was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!(idxA ..< idxB).isValid(within: str), "Initial range was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was valid in in-place mutated", sourceLocation: sourceLocation) } - func checkCopy(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { + func checkCopy(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { let str = AttributedString("Hello World") let idxA = str.startIndex let idxB = str.index(afterCharacter: idxA) var copy = str - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", file: file, line: line) - XCTAssertTrue(idxA.isValid(within: copy), "Initial index A was invalid in copy", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: copy), "Initial index B was invalid in copy", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: copy), "Initial range was invalid in copy", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was invalid in copy", file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", sourceLocation: sourceLocation) + #expect(idxA.isValid(within: copy), "Initial index A was invalid in copy", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: copy), "Initial index B was invalid in copy", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: copy), "Initial range was invalid in copy", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was invalid in copy", sourceLocation: sourceLocation) mutation(©) - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original after copy", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original after copy", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original after copy", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original after copy", file: file, line: line) - XCTAssertFalse(idxA.isValid(within: copy), "Initial index A was valid in copy", file: file, line: line) - XCTAssertFalse(idxB.isValid(within: copy), "Initial index B was valid in copy", file: file, line: line) - XCTAssertFalse((idxA ..< idxB).isValid(within: copy), "Initial range was valid in copy", file: file, line: line) - XCTAssertFalse(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was valid in copy", file: file, line: line) - } - - func check(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { - checkInPlace(mutation, file: file, line: line) - checkCopy(mutation, file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original after copy", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original after copy", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original after copy", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original after copy", sourceLocation: sourceLocation) + #expect(!idxA.isValid(within: copy), "Initial index A was valid in copy", sourceLocation: sourceLocation) + #expect(!idxB.isValid(within: copy), "Initial index B was valid in copy", sourceLocation: sourceLocation) + #expect(!(idxA ..< idxB).isValid(within: copy), "Initial range was valid in copy", sourceLocation: sourceLocation) + #expect(!RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was valid in copy", sourceLocation: sourceLocation) + } + + func check(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { + checkInPlace(mutation, sourceLocation: sourceLocation) + checkCopy(mutation, sourceLocation: sourceLocation) } check { From 41ecea1d869f1297f8b1410bf6b4fc02e9978981 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 5 Jun 2025 10:45:47 -0700 Subject: [PATCH 4/7] Convert constraining behavior tests to swift-testing --- ...butedStringConstrainingBehaviorTests.swift | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift index 24698e8ed..f3f41d360 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift @@ -10,73 +10,78 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -class TestAttributedStringConstrainingBehavior: XCTestCase { +@Suite("AttributedString Constraining Behavior") +private struct AttributedStringConstrainingBehaviorTests { func verify( string: AttributedString, matches expected: [(String, K.Value?)], for key: KeyPath, - file: StaticString = #filePath, line: UInt = #line - ) + sourceLocation: SourceLocation = #_sourceLocation + ) where K.Value : Sendable { let runs = string.runs[key] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val, range), expectation) in zip(runs, expected) { let slice = String.UnicodeScalarView(string.unicodeScalars[range]) - XCTAssertTrue(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run: \(slice.debugDescription) vs \(expectation.0.debugDescription)", file: file, line: line) - XCTAssertEqual(val, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) + #expect(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run: \(slice.debugDescription) vs \(expectation.0.debugDescription)", sourceLocation: sourceLocation) + #expect(val == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val, range), expectation) in zip(runs.reversed(), expected.reversed()) { let slice = String.UnicodeScalarView(string.unicodeScalars[range]) - XCTAssertTrue(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run while reverse iterating: \(slice.debugDescription) vs \(expectation.0.debugDescription)", file: file, line: line) - XCTAssertEqual(val, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run while reverse iterating: \(slice.debugDescription) vs \(expectation.0.debugDescription)", sourceLocation: sourceLocation) + #expect(val == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } - func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?)], for key: KeyPath, _ key2: KeyPath, file: StaticString = #filePath, line: UInt = #line) + func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?)], for key: KeyPath, _ key2: KeyPath, sourceLocation: SourceLocation = #_sourceLocation) where K.Value : Sendable, K2.Value : Sendable { let runs = string.runs[key, key2] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val1, val2, range), expectation) in zip(runs, expected) { - XCTAssertEqual(String(string.characters[range]),expectation.0, "Unexpected range of run", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val1, val2, range), expectation) in zip(runs.reversed(), expected.reversed()) { - XCTAssertEqual(String(string.characters[range]), expectation.0, "Unexpected range of run while reverse iterating", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run while reverse iterating", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } - func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?, K3.Value?)], for key: KeyPath, _ key2: KeyPath, _ key3: KeyPath, file: StaticString = #filePath, line: UInt = #line) + func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?, K3.Value?)], for key: KeyPath, _ key2: KeyPath, _ key3: KeyPath, sourceLocation: SourceLocation = #_sourceLocation) where K.Value : Sendable, K2.Value : Sendable, K3.Value : Sendable { let runs = string.runs[key, key2, key3] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val1, val2, val3, range), expectation) in zip(runs, expected) { - XCTAssertEqual(String(string.characters[range]),expectation.0, "Unexpected range of run", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val3, expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0)", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val3 == expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val1, val2, val3, range), expectation) in zip(runs.reversed(), expected.reversed()) { - XCTAssertEqual(String(string.characters[range]), expectation.0, "Unexpected range of run while reverse iterating", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val3, expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run while reverse iterating", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val3 == expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } // MARK: Extending Run Tests - func testExtendingRunAddCharacters() { + @Test func extendingRunAddCharacters() { let str = AttributedString("Hello, world", attributes: .init().testInt(2).testNonExtended(1)) var result = str @@ -103,7 +108,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("He", 2, 1), ("Hi!", 2, nil), ("rld", 2, 1)], for: \.testInt, \.testNonExtended) } - func testExtendingRunAddUnicodeScalars() { + @Test func extendingRunAddUnicodeScalars() { let str = AttributedString("Hello, world", attributes: .init().testInt(2).testNonExtended(1)) let scalarsStr = "A\u{0301}B" @@ -127,7 +132,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: - Paragraph Constrained Tests - func testParagraphAttributeExpanding() { + @Test func paragraphAttributeExpanding() { var str = AttributedString("Hello, world\nNext Paragraph") var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testParagraphConstrained = 2 @@ -148,7 +153,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("Hello, world\n", 4), ("Next Paragraph", 4)], for: \.testParagraphConstrained) } - func testParagraphAttributeRemoval() { + @Test func paragraphAttributeRemoval() { var str = AttributedString("Hello, world\nNext Paragraph", attributes: .init().testParagraphConstrained(2)) var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testParagraphConstrained = nil @@ -167,7 +172,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("Hello, world\n", nil), ("Next Paragraph", nil)], for: \.testParagraphConstrained) } - func testParagraphAttributeContainerApplying() { + @Test func paragraphAttributeContainerApplying() { var container = AttributeContainer.testParagraphConstrained(2).testString("Hello") var str = AttributedString("Hello, world\nNext Paragraph") var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -195,7 +200,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("H", 4, nil, 1), ("el", 4, "Hello", 1), ("lo, w", 4, nil, 1), ("orld\n", 4, nil, 2), ("N", 4, nil, 2), ("ext Paragrap", 4, nil, 1), ("h", 4, "Hello", 2)], for: \.testParagraphConstrained, \.testString, \.testInt) } - func testParagraphAttributeContainerReplacing() { + @Test func paragraphAttributeContainerReplacing() { var str = AttributedString("Hello, world\nNext Paragraph") let range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testInt = 2 @@ -216,7 +221,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("H", 3, 2, nil), ("el", 3, nil, true), ("lo, world\n", 3, 2, nil), ("Next Paragraph", nil, 2, nil)], for: \.testParagraphConstrained, \.testInt, \.testBool) } - func testParagraphTextMutation() { + @Test func paragraphTextMutation() { let str = AttributedString("Hello, world\n", attributes: .init().testParagraphConstrained(1)) + AttributedString("Next Paragraph", attributes: .init().testParagraphConstrained(2)) var result = str @@ -260,7 +265,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("Hello, wTest\n", 1), ("Replacementxt Paragraph", 1)], for: \.testParagraphConstrained) } - func testParagraphAttributedTextMutation() { + @Test func paragraphAttributedTextMutation() { let str = AttributedString("Hello, world\n", attributes: .init().testParagraphConstrained(1)) + AttributedString("Next Paragraph", attributes: .init().testParagraphConstrained(2)) let singleReplacement = AttributedString("Test", attributes: .init().testParagraphConstrained(5).testSecondParagraphConstrained(6).testBool(true)) let multiReplacement = AttributedString("Test\nInserted", attributes: .init().testParagraphConstrained(5).testSecondParagraphConstrained(6).testBool(true)) @@ -311,7 +316,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testParagraphFromUntrustedRuns() throws { + @Test func paragraphFromUntrustedRuns() throws { let str = NSMutableAttributedString(string: "Hello ", attributes: [.testParagraphConstrained : NSNumber(2)]) str.append(NSAttributedString(string: "World", attributes: [.testParagraphConstrained : NSNumber(3), .testSecondParagraphConstrained : NSNumber(4)])) @@ -320,7 +325,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #endif // FOUNDATION_FRAMEWORK - func testParagraphFromReplacedSubrange() { + @Test func paragraphFromReplacedSubrange() { let str = AttributedString("Before\nHello, world\nNext Paragraph\nAfter", attributes: .init().testParagraphConstrained(1)) // Range of "world\nNext" @@ -344,7 +349,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: - Character Constrained Tests - func testCharacterAttributeApply() { + @Test func characterAttributeApply() { let str = AttributedString("*__*__**__*") var result = str @@ -362,7 +367,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("*", nil, 1), ("__", nil, 1), ("*", nil, 1), ("__", nil, 1), ("*", nil, 1), ("*", nil, 1), ("__", nil, 1), ("*", 3, 1)], for: \.testCharacterConstrained, \.testInt) } - func testCharacterAttributeSubCharacterApply() { + @Test func characterAttributeSubCharacterApply() { let str = AttributedString("ABC \u{FFFD} DEF") var result = str @@ -394,7 +399,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } - func testCharacterAttributeContainerReplacing() { + @Test func characterAttributeContainerReplacing() { var str = AttributedString("*__*__**__*") let range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 4) str[range].testInt = 2 @@ -415,7 +420,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("*", nil, 2, nil), ("__", nil, nil, true), ("*", 3, nil, true), ("__", nil, 2, nil), ("*", nil, 2, nil), ("*", nil, 2, nil), ("__", nil, 2, nil), ("*", nil, 2, nil)], for: \.testCharacterConstrained, \.testInt, \.testBool) } - func testCharacterTextMutation() { + @Test func characterTextMutation() { let str = AttributedString("*__*__**__*", attributes: .init().testCharacterConstrained(2)) var result = str @@ -444,7 +449,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testCharacterFromUntrustedRuns() throws { + @Test func characterFromUntrustedRuns() throws { let str = NSMutableAttributedString(string: "*__*__**__*", attributes: [.testCharacterConstrained : NSNumber(2)]) str.append(NSAttributedString(string: "_*")) @@ -455,7 +460,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: Invalidation Tests - func testInvalidationAttributeChange() { + @Test func invalidationAttributeChange() { let str = AttributedString("Hello, world", attributes: .init().testInt(1).testAttributeDependent(2)) var result = str @@ -489,7 +494,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("Hello, world", 2, nil)], for: \.testInt, \.testAttributeDependent) } - func testInvalidationCharacterChange() { + @Test func invalidationCharacterChange() { let str = AttributedString("Hello, world", attributes: .init().testInt(1).testCharacterDependent(2)) var result = str @@ -575,7 +580,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("H", nil, nil, "Hello"), ("ello, world", 1, nil, nil)], for: \.testInt, \.testCharacterDependent, \.testString) } - func testInvalidationCharacterInsertionBetweenRuns() { + @Test func invalidationCharacterInsertionBetweenRuns() { var str = AttributedString("Hello", attributes: .init().testInt(1).testCharacterDependent(2)) str += AttributedString("World", attributes: .init().testInt(1).testCharacterDependent(3)) From 65815ae25a7b4cbff62ecb972d85dbabf1250ed2 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 5 Jun 2025 11:41:01 -0700 Subject: [PATCH 5/7] Convert base attributed string tests to swift-testing --- .../AttributedStringTests.swift | 1350 +++++++++-------- 1 file changed, 684 insertions(+), 666 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift index 25a44fe90..04f9447ec 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift @@ -10,13 +10,11 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif // FOUNDATION_FRAMEWORK +#endif #if FOUNDATION_FRAMEWORK @testable @_spi(AttributedString) import Foundation @@ -33,21 +31,22 @@ import UIKit #if canImport(AppKit) import AppKit #endif -#endif // FOUNDATION_FRAMEWORK +#endif /// Regression and coverage tests for `AttributedString` and its associated objects -final class TestAttributedString: XCTestCase { +@Suite("AttributedString") +private struct AttributedStringTests { // MARK: - Enumeration Tests - func testEmptyEnumeration() { + @Test func emptyEnumeration() { for _ in AttributedString().runs { - XCTFail("Empty AttributedString should not enumerate any attributes") + Issue.record("Empty AttributedString should not enumerate any attributes") } do { let str = AttributedString("Foo") for _ in str[str.startIndex ..< str.startIndex].runs { - XCTFail("Empty AttributedSubstring should not enumerate any attributes") + Issue.record("Empty AttributedSubstring should not enumerate any attributes") } } @@ -55,76 +54,94 @@ final class TestAttributedString: XCTestCase { let str = AttributedString("Foo", attributes: AttributeContainer.testInt(2)) let i = str.index(afterCharacter: str.startIndex) for _ in str[i ..< i].runs { - XCTFail("Empty AttributedSubstring should not enumerate any attributes") + Issue.record("Empty AttributedSubstring should not enumerate any attributes") } } } - func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice1, string: AttributedString, expectation: [(String, T.Value?)]) where T.Value : Sendable { + func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice1, string: AttributedString, expectation: [(String, T.Value?)], sourceLocation: SourceLocation = #_sourceLocation) where T.Value : Sendable { // Test that the attribute is correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attribute is correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } - func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice2, string: AttributedString, expectation: [(String, T.Value?, U.Value?)]) where T.Value : Sendable, U.Value : Sendable { + func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice2, string: AttributedString, expectation: [(String, T.Value?, U.Value?)], sourceLocation: SourceLocation = #_sourceLocation) where T.Value : Sendable, U.Value : Sendable { // Test that the attributes are correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, attribute2, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") - XCTAssertEqual(attribute2, expected.2, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute2 == expected.2, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attributes are correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, attribute2, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") - XCTAssertEqual(attribute2, expected.2, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute2 == expected.2, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } #if FOUNDATION_FRAMEWORK - func verifyAttributes(_ runs: AttributedString.Runs.NSAttributesSlice, string: AttributedString, expectation: [(String, AttributeContainer)], file: StaticString = #filePath, line: UInt = #line) { + func verifyAttributes(_ runs: AttributedString.Runs.NSAttributesSlice, string: AttributedString, expectation: [(String, AttributeContainer)], sourceLocation: SourceLocation = #_sourceLocation) { // Test that the attribute is correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation", file: file, line: line) - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation", file: file, line: line) + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found", file: file, line: line) + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attribute is correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation", file: file, line: line) - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation", file: file, line: line) + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found", file: file, line: line) + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } #endif // FOUNDATION_FRAMEWORK - func testSimpleEnumeration() { + @Test func simpleEnumeration() throws { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += " " attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -132,23 +149,29 @@ final class TestAttributedString: XCTestCase { let expectation = [("Hello", 1, nil), (" ", nil, nil), ("World", nil, 2.0)] var expectationIterator = expectation.makeIterator() for run in attrStr.runs { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) expectationIterator = expectation.reversed().makeIterator() for run in attrStr.runs.reversed() { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) let attrView = attrStr.runs verifyAttributes(attrView[\.testInt], string: attrStr, expectation: [("Hello", 1), (" World", nil)]) @@ -157,7 +180,7 @@ final class TestAttributedString: XCTestCase { verifyAttributes(attrView[\.testInt, \.testDouble], string: attrStr, expectation: [("Hello", 1, nil), (" ", nil, nil), ("World", nil, 2.0)]) } - func testSliceEnumeration() { + @Test func sliceEnumeration() throws { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -167,23 +190,29 @@ final class TestAttributedString: XCTestCase { let expectation = [("lo", 1, nil), (" ", nil, nil), ("Wo", nil, 2.0)] var expectationIterator = expectation.makeIterator() for run in attrStrSlice.runs { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) expectationIterator = expectation.reversed().makeIterator() for run in attrStrSlice.runs.reversed() { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) let attrView = attrStrSlice.runs verifyAttributes(attrView[\.testInt], string: attrStr, expectation: [("lo", 1), (" Wo", nil)]) @@ -193,7 +222,7 @@ final class TestAttributedString: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testNSSliceEnumeration() { + @Test func nsSliceEnumeration() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -221,23 +250,23 @@ final class TestAttributedString: XCTestCase { // MARK: - Attribute Tests - func testSimpleAttribute() { + @Test func simpleAttribute() { let attrStr = AttributedString("Foo", attributes: AttributeContainer().testInt(42)) let (value, range) = attrStr.runs[\.testInt][attrStr.startIndex] - XCTAssertEqual(value, 42) - XCTAssertEqual(range, attrStr.startIndex ..< attrStr.endIndex) + #expect(value == 42) + #expect(range == attrStr.startIndex ..< attrStr.endIndex) } - func testConstructorAttribute() { + @Test func constructorAttribute() { // TODO: Re-evaluate whether we want these. let attrStr = AttributedString("Hello", attributes: AttributeContainer().testString("Helvetica").testInt(2)) var expected = AttributedString("Hello") expected.testString = "Helvetica" expected.testInt = 2 - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testAddAndRemoveAttribute() { + @Test func addAndRemoveAttribute() { let attr : Int = 42 let attr2 : Double = 1.0 var attrStr = AttributedString("Test") @@ -245,70 +274,70 @@ final class TestAttributedString: XCTestCase { attrStr.testDouble = attr2 let expected1 = AttributedString("Test", attributes: AttributeContainer().testInt(attr).testDouble(attr2)) - XCTAssertEqual(attrStr, expected1) + #expect(attrStr == expected1) attrStr.testDouble = nil let expected2 = AttributedString("Test", attributes: AttributeContainer().testInt(attr)) - XCTAssertEqual(attrStr, expected2) + #expect(attrStr == expected2) } - func testAddingAndRemovingAttribute() { + @Test func addingAndRemovingAttribute() { let container = AttributeContainer().testInt(1).testDouble(2.2) let attrStr = AttributedString("Test").mergingAttributes(container) let expected = AttributedString("Test", attributes: AttributeContainer().testInt(1).testDouble(2.2)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) var doubleRemoved = attrStr doubleRemoved.testDouble = nil - XCTAssertEqual(doubleRemoved, AttributedString("Test", attributes: AttributeContainer().testInt(1))) + #expect(doubleRemoved == AttributedString("Test", attributes: AttributeContainer().testInt(1))) } - func testScopedAttributes() { + @Test func scopedAttributes() { var str = AttributedString("Hello, world", attributes: AttributeContainer().testInt(2).testDouble(3.4)) - XCTAssertEqual(str.test.testInt, 2) - XCTAssertEqual(str.test.testDouble, 3.4) - XCTAssertEqual(str.runs[str.runs.startIndex].test.testInt, 2) + #expect(str.test.testInt == 2) + #expect(str.test.testDouble == 3.4) + #expect(str.runs[str.runs.startIndex].test.testInt == 2) str.test.testInt = 4 - XCTAssertEqual(str, AttributedString("Hello, world", attributes: AttributeContainer.testInt(4).testDouble(3.4))) + #expect(str == AttributedString("Hello, world", attributes: AttributeContainer.testInt(4).testDouble(3.4))) let range = str.startIndex ..< str.characters.index(after: str.startIndex) str[range].test.testBool = true - XCTAssertNil(str.test.testBool) - XCTAssertNotNil(str[range].test.testBool) - XCTAssertTrue(str[range].test.testBool!) + #expect(str.test.testBool == nil) + #expect(str[range].test.testBool != nil) + #expect(str[range].test.testBool == true) } - func testRunAttributes() { + @Test func runAttributes() { var str = AttributedString("String", attributes: .init().testString("test1")) str += "None" str += AttributedString("String+Int", attributes: .init().testString("test2").testInt(42)) let attributes = str.runs.map { $0.attributes } - XCTAssertEqual(attributes.count, 3) - XCTAssertEqual(attributes[0], .init().testString("test1")) - XCTAssertEqual(attributes[1], .init()) - XCTAssertEqual(attributes[2], .init().testString("test2").testInt(42)) + #expect(attributes.count == 3) + #expect(attributes[0] == .init().testString("test1")) + #expect(attributes[1] == .init()) + #expect(attributes[2] == .init().testString("test2").testInt(42)) } // MARK: - Comparison Tests - func testAttributedStringEquality() { - XCTAssertEqual(AttributedString(), AttributedString()) - XCTAssertEqual(AttributedString("abc"), AttributedString("abc")) - XCTAssertEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("abc", attributes: AttributeContainer().testInt(1))) - XCTAssertNotEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("abc", attributes: AttributeContainer().testInt(2))) - XCTAssertNotEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("def", attributes: AttributeContainer().testInt(1))) + @Test func attributedStringEquality() { + #expect(AttributedString() == AttributedString()) + #expect(AttributedString("abc") == AttributedString("abc")) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) == AttributedString("abc", attributes: AttributeContainer().testInt(1))) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) != AttributedString("abc", attributes: AttributeContainer().testInt(2))) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) != AttributedString("def", attributes: AttributeContainer().testInt(1))) var a = AttributedString("abc", attributes: AttributeContainer().testInt(1)) a += AttributedString("def", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(a, AttributedString("abcdef", attributes: AttributeContainer().testInt(1))) + #expect(a == AttributedString("abcdef", attributes: AttributeContainer().testInt(1))) a = AttributedString("ab", attributes: AttributeContainer().testInt(1)) a += AttributedString("cdef", attributes: AttributeContainer().testInt(2)) var b = AttributedString("abcd", attributes: AttributeContainer().testInt(1)) b += AttributedString("ef", attributes: AttributeContainer().testInt(2)) - XCTAssertNotEqual(a, b) + #expect(a != b) a = AttributedString("abc") a += AttributedString("defghi", attributes: AttributeContainer().testInt(2)) @@ -316,22 +345,22 @@ final class TestAttributedString: XCTestCase { b = AttributedString("abc") b += AttributedString("def", attributes: AttributeContainer().testInt(2)) b += "ghijkl" - XCTAssertNotEqual(a, b) + #expect(a != b) let a1 = AttributedString("Café", attributes: AttributeContainer().testInt(1)) let a2 = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(a1, a2) + #expect(a1 == a2) let a3 = (AttributedString("Cafe", attributes: AttributeContainer().testInt(1)) + AttributedString("\u{301}", attributes: AttributeContainer().testInt(2))) - XCTAssertNotEqual(a1, a3) - XCTAssertNotEqual(a2, a3) - XCTAssertTrue(a1.characters.elementsEqual(a3.characters)) - XCTAssertTrue(a2.characters.elementsEqual(a3.characters)) + #expect(a1 != a3) + #expect(a2 != a3) + #expect(a1.characters.elementsEqual(a3.characters)) + #expect(a2.characters.elementsEqual(a3.characters)) } - func testAttributedSubstringEquality() { + @Test func attributedSubstringEquality() { let emptyStr = AttributedString("01234567890123456789") let index0 = emptyStr.characters.startIndex @@ -346,22 +375,22 @@ final class TestAttributedString: XCTestCase { halfhalfStr[index0 ..< index10].testInt = 1 halfhalfStr[index10 ..< index20].testDouble = 2.0 - XCTAssertEqual(emptyStr[index0 ..< index0], emptyStr[index0 ..< index0]) - XCTAssertEqual(emptyStr[index0 ..< index5], emptyStr[index0 ..< index5]) - XCTAssertEqual(emptyStr[index0 ..< index20], emptyStr[index0 ..< index20]) - XCTAssertEqual(singleAttrStr[index0 ..< index20], singleAttrStr[index0 ..< index20]) - XCTAssertEqual(halfhalfStr[index0 ..< index20], halfhalfStr[index0 ..< index20]) + #expect(emptyStr[index0 ..< index0] == emptyStr[index0 ..< index0]) + #expect(emptyStr[index0 ..< index5] == emptyStr[index0 ..< index5]) + #expect(emptyStr[index0 ..< index20] == emptyStr[index0 ..< index20]) + #expect(singleAttrStr[index0 ..< index20] == singleAttrStr[index0 ..< index20]) + #expect(halfhalfStr[index0 ..< index20] == halfhalfStr[index0 ..< index20]) - XCTAssertEqual(emptyStr[index0 ..< index10], singleAttrStr[index10 ..< index20]) - XCTAssertEqual(halfhalfStr[index0 ..< index10], singleAttrStr[index0 ..< index10]) + #expect(emptyStr[index0 ..< index10] == singleAttrStr[index10 ..< index20]) + #expect(halfhalfStr[index0 ..< index10] == singleAttrStr[index0 ..< index10]) - XCTAssertNotEqual(emptyStr[index0 ..< index10], singleAttrStr[index0 ..< index10]) - XCTAssertNotEqual(emptyStr[index0 ..< index10], singleAttrStr[index0 ..< index20]) + #expect(emptyStr[index0 ..< index10] != singleAttrStr[index0 ..< index10]) + #expect(emptyStr[index0 ..< index10] != singleAttrStr[index0 ..< index20]) - XCTAssertTrue(emptyStr[index0 ..< index5] == AttributedString("01234")) + #expect(emptyStr[index0 ..< index5] == AttributedString("01234")) } - func testRunEquality() { + @Test func runEquality() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testInt(2)) @@ -380,25 +409,25 @@ final class TestAttributedString: XCTestCase { } // Same string, same range, different attributes - XCTAssertNotEqual(run(0, in: attrStr), run(0, in: attrStr2)) + #expect(run(0, in: attrStr) != run(0, in: attrStr2)) // Different strings, same range, same attributes - XCTAssertEqual(run(1, in: attrStr), run(1, in: attrStr2)) + #expect(run(1, in: attrStr) == run(1, in: attrStr2)) // Same string, same range, same attributes - XCTAssertEqual(run(2, in: attrStr), run(2, in: attrStr2)) + #expect(run(2, in: attrStr) == run(2, in: attrStr2)) // Different string, different range, same attributes - XCTAssertEqual(run(2, in: attrStr), run(0, in: attrStr2)) + #expect(run(2, in: attrStr) == run(0, in: attrStr2)) // Same string, different range, same attributes - XCTAssertEqual(run(0, in: attrStr), run(3, in: attrStr2)) + #expect(run(0, in: attrStr) == run(3, in: attrStr2)) // A runs collection of the same order but different run lengths - XCTAssertNotEqual(attrStr.runs, attrStr3.runs) + #expect(attrStr.runs != attrStr3.runs) } - func testSubstringRunEquality() { + @Test func substringRunEquality() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testInt(2)) @@ -407,16 +436,16 @@ final class TestAttributedString: XCTestCase { attrStr2 += AttributedString("_") attrStr2 += AttributedString("World", attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr[attrStr.runs.last!.range].runs, attrStr2[attrStr2.runs.first!.range].runs) - XCTAssertEqual(attrStr[attrStr.runs.last!.range].runs, attrStr2[attrStr2.runs.last!.range].runs) + #expect(attrStr[attrStr.runs.last!.range].runs == attrStr2[attrStr2.runs.first!.range].runs) + #expect(attrStr[attrStr.runs.last!.range].runs == attrStr2[attrStr2.runs.last!.range].runs) let rangeA = attrStr.runs.first!.range.upperBound ..< attrStr.endIndex let rangeB = attrStr2.runs.first!.range.upperBound ..< attrStr.endIndex let rangeC = attrStr.startIndex ..< attrStr.runs.last!.range.lowerBound let rangeD = attrStr.runs.first!.range - XCTAssertEqual(attrStr[rangeA].runs, attrStr2[rangeB].runs) - XCTAssertNotEqual(attrStr[rangeC].runs, attrStr2[rangeB].runs) - XCTAssertNotEqual(attrStr[rangeD].runs, attrStr2[rangeB].runs) + #expect(attrStr[rangeA].runs == attrStr2[rangeB].runs) + #expect(attrStr[rangeC].runs != attrStr2[rangeB].runs) + #expect(attrStr[rangeD].runs != attrStr2[rangeB].runs) // Test starting/ending runs that only differ outside of the range do not prevent equality attrStr2[attrStr.runs.first!.range].testInt = 1 @@ -424,29 +453,29 @@ final class TestAttributedString: XCTestCase { attrStr2.characters.append(contentsOf: "45") let rangeE = attrStr.startIndex ..< attrStr.endIndex let rangeF = attrStr2.characters.index(attrStr2.startIndex, offsetBy: 3) ..< attrStr2.characters.index(attrStr2.startIndex, offsetBy: 14) - XCTAssertEqual(attrStr[rangeE].runs, attrStr2[rangeF].runs) + #expect(attrStr[rangeE].runs == attrStr2[rangeF].runs) } // MARK: - Mutation Tests - func testDirectMutationCopyOnWrite() { + @Test func directMutationCopyOnWrite() { var attrStr = AttributedString("ABC") let copy = attrStr attrStr += "D" - XCTAssertEqual(copy, AttributedString("ABC")) - XCTAssertNotEqual(attrStr, copy) + #expect(copy == AttributedString("ABC")) + #expect(attrStr != copy) } - func testAttributeMutationCopyOnWrite() { + @Test func attributeMutationCopyOnWrite() { var attrStr = AttributedString("ABC") let copy = attrStr attrStr.testInt = 1 - XCTAssertNotEqual(attrStr, copy) + #expect(attrStr != copy) } - func testSliceAttributeMutation() { + @Test func sliceAttributeMutation() { let attr : Int = 42 let attr2 : Double = 1.0 @@ -460,12 +489,12 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Hello", attributes: AttributeContainer().testInt(attr).testDouble(attr2)) expected += AttributedString(" World", attributes: AttributeContainer().testInt(attr)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) - XCTAssertNotEqual(copy, attrStr) + #expect(copy != attrStr) } - func testEnumerationAttributeMutation() { + @Test func enumerationAttributeMutation() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString("B", attributes: AttributeContainer().testDouble(2.0)) attrStr += AttributedString("C", attributes: AttributeContainer().testInt(3)) @@ -479,10 +508,10 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("A") expected += AttributedString("B", attributes: AttributeContainer().testDouble(2.0)) expected += "C" - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testMutateMultipleAttributes() { + @Test func mutateMultipleAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -494,7 +523,7 @@ final class TestAttributedString: XCTestCase { $2.value = nil } let removal1expected = AttributedString("ABC") - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1 = attrStr.transformingAttributes(\.testInt, \.testDouble, \.testBool) { @@ -511,7 +540,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(42).testBool(false)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(42).testDouble(3)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(3).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1 = attrStr.transformingAttributes(\.testInt, \.testDouble, \.testBool) { @@ -529,7 +558,7 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testDouble(2).testInt(42)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2).testBool(false)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testBool(false).testInt(42)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) // Test change range var changeRange1First = true @@ -546,10 +575,10 @@ final class TestAttributedString: XCTestCase { var changeRange1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(changeRange1expected, changeRange1) + #expect(changeRange1expected == changeRange1) } - func testMutateAttributes() { + @Test func mutateAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -561,7 +590,7 @@ final class TestAttributedString: XCTestCase { var removal1expected = AttributedString("A", attributes: AttributeContainer().testBool(true)) removal1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) removal1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1 = attrStr.transformingAttributes(\.testBool) { @@ -572,7 +601,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(false)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1 = attrStr.transformingAttributes(\.testBool) { @@ -583,7 +612,7 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testDouble(42)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testDouble(43)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) // Test change range let changeRange1 = attrStr.transformingAttributes(\.testInt) { @@ -595,7 +624,7 @@ final class TestAttributedString: XCTestCase { var changeRange1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) changeRange1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(changeRange1expected, changeRange1) + #expect(changeRange1expected == changeRange1) // Now try extending it let changeRange2 = attrStr.transformingAttributes(\.testInt) { @@ -607,10 +636,10 @@ final class TestAttributedString: XCTestCase { var changeRange2expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange2expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeRange2expected += AttributedString("C", attributes: AttributeContainer().testInt(1).testDouble(2).testBool(false)) - XCTAssertEqual(changeRange2expected, changeRange2) + #expect(changeRange2expected == changeRange2) } - func testReplaceAttributes() { + @Test func replaceAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -624,7 +653,7 @@ final class TestAttributedString: XCTestCase { var removal1expected = AttributedString("A", attributes: AttributeContainer().testBool(true)) removal1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) removal1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1Find = AttributeContainer().testBool(false) @@ -635,7 +664,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1Find = AttributeContainer().testBool(false) @@ -646,26 +675,26 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testDouble(43)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) } - func testSliceMutation() { + @Test func sliceMutation() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) let start = attrStr.characters.index(attrStr.startIndex, offsetBy: 6) attrStr.replaceSubrange(start ..< attrStr.characters.index(start, offsetBy:5), with: AttributedString("Goodbye", attributes: AttributeContainer().testInt(2))) var expected = AttributedString("Hello ", attributes: AttributeContainer().testInt(1)) expected += AttributedString("Goodbye", attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr, expected) - XCTAssertNotEqual(attrStr, AttributedString("Hello Goodbye", attributes: AttributeContainer().testInt(1))) + #expect(attrStr == expected) + #expect(attrStr != AttributedString("Hello Goodbye", attributes: AttributeContainer().testInt(1))) } - func testOverlappingSliceMutation() { + @Test func overlappingSliceMutation() throws { var attrStr = AttributedString("Hello, world!") - attrStr[attrStr.range(of: "Hello")!].testInt = 1 - attrStr[attrStr.range(of: "world")!].testInt = 2 - attrStr[attrStr.range(of: "o, wo")!].testBool = true + attrStr[try #require(attrStr.range(of: "Hello"))].testInt = 1 + attrStr[try #require(attrStr.range(of: "world"))].testInt = 2 + attrStr[try #require(attrStr.range(of: "o, wo"))].testBool = true var expected = AttributedString("Hell", attributes: AttributeContainer().testInt(1)) expected += AttributedString("o", attributes: AttributeContainer().testInt(1).testBool(true)) @@ -673,43 +702,43 @@ final class TestAttributedString: XCTestCase { expected += AttributedString("wo", attributes: AttributeContainer().testBool(true).testInt(2)) expected += AttributedString("rld", attributes: AttributeContainer().testInt(2)) expected += AttributedString("!") - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testCharacters_replaceSubrange() { + @Test func characters_replaceSubrange() throws { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) - attrStr.characters.replaceSubrange(attrStr.range(of: " ")!, with: " Good ") + attrStr.characters.replaceSubrange(try #require(attrStr.range(of: " ")), with: " Good ") let expected = AttributedString("Hello Good World", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testCharactersMutation_append() { + @Test func charactersMutation_append() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) attrStr.characters.append(contentsOf: " Goodbye") let expected = AttributedString("Hello World Goodbye", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testUnicodeScalars_replaceSubrange() { + @Test func unicodeScalars_replaceSubrange() { var attrStr = AttributedString("La Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let unicode = attrStr.unicodeScalars attrStr.unicodeScalars.replaceSubrange(unicode.index(unicode.startIndex, offsetBy: 3) ..< unicode.index(unicode.startIndex, offsetBy: 7), with: "Ole".unicodeScalars) let expected = AttributedString("La Ole\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testUnicodeScalarsMutation_append() { + @Test func unicodeScalarsMutation_append() { var attrStr = AttributedString("Cafe", attributes: AttributeContainer().testInt(1)) attrStr.unicodeScalars.append("\u{301}") let expected = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testSubCharacterAttributeSetting() { + @Test func subCharacterAttributeSetting() { var attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let cafRange = attrStr.characters.startIndex ..< attrStr.characters.index(attrStr.characters.startIndex, offsetBy: 3) let eRange = cafRange.upperBound ..< attrStr.unicodeScalars.index(after: cafRange.upperBound) @@ -721,10 +750,10 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Caf", attributes: AttributeContainer().testInt(1).testDouble(1.5)) expected += AttributedString("e", attributes: AttributeContainer().testInt(1).testDouble(2.5)) expected += AttributedString("\u{301}", attributes: AttributeContainer().testInt(1).testDouble(3.5)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testReplaceSubrange_rangeExpression() { + @Test func replaceSubrange_rangeExpression() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) // Test with PartialRange, which conforms to RangeExpression but is not a Range @@ -733,20 +762,20 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Goodbye") expected += AttributedString(" World", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testSettingAttributes() { + @Test func settingAttributes() { var attrStr = AttributedString("Hello World", attributes: .init().testInt(1)) attrStr += AttributedString(". My name is Foundation!", attributes: .init().testBool(true)) let result = attrStr.settingAttributes(.init().testBool(false)) let expected = AttributedString("Hello World. My name is Foundation!", attributes: .init().testBool(false)) - XCTAssertEqual(result, expected) + #expect(result == expected) } - func testAddAttributedString() { + @Test func addAttributedString() { let attrStr = AttributedString("Hello ", attributes: .init().testInt(1)) let attrStr2 = AttributedString("World", attributes: .init().testInt(2)) let original = attrStr @@ -755,22 +784,22 @@ final class TestAttributedString: XCTestCase { var concat = AttributedString("Hello ", attributes: .init().testInt(1)) concat += AttributedString("World", attributes: .init().testInt(2)) let combine = attrStr + attrStr2 - XCTAssertEqual(attrStr, original) - XCTAssertEqual(attrStr2, original2) - XCTAssertEqual(String(combine.characters), "Hello World") - XCTAssertEqual(String(concat.characters), "Hello World") + #expect(attrStr == original) + #expect(attrStr2 == original2) + #expect(String(combine.characters) == "Hello World") + #expect(String(concat.characters) == "Hello World") let testInts = [1, 2] for str in [concat, combine] { var i = 0 for run in str.runs { - XCTAssertEqual(run.testInt, testInts[i]) + #expect(run.testInt == testInts[i]) i += 1 } } } - func testReplaceSubrangeWithSubstrings() { + @Test func replaceSubrangeWithSubstrings() { let baseString = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -788,7 +817,7 @@ final class TestAttributedString: XCTestCase { + AttributedString("D", attributes: .init().testInt(4)) + AttributedString("Z", attributes: .init().testString("foo")) - XCTAssertEqual(targetString, expected) + #expect(targetString == expected) targetString = AttributedString("XYZ", attributes: .init().testString("foo")) targetString.append(substring) @@ -797,20 +826,20 @@ final class TestAttributedString: XCTestCase { + AttributedString("C", attributes: .init().testInt(3)) + AttributedString("D", attributes: .init().testInt(4)) - XCTAssertEqual(targetString, expected) + #expect(targetString == expected) } func assertStringIsCoalesced(_ str: AttributedString) { var prev: AttributedString.Runs.Run? for run in str.runs { if let prev = prev { - XCTAssertNotEqual(prev.attributes, run.attributes) + #expect(prev.attributes != run.attributes) } prev = run } } - func testCoalescing() { + @Test func coalescing() { let str = AttributedString("Hello", attributes: .init().testInt(1)) let appendSame = str + AttributedString("World", attributes: .init().testInt(1)) let appendDifferent = str + AttributedString("World", attributes: .init().testInt(2)) @@ -818,67 +847,67 @@ final class TestAttributedString: XCTestCase { assertStringIsCoalesced(str) assertStringIsCoalesced(appendSame) assertStringIsCoalesced(appendDifferent) - XCTAssertEqual(appendSame.runs.count, 1) - XCTAssertEqual(appendDifferent.runs.count, 2) + #expect(appendSame.runs.count == 1) + #expect(appendDifferent.runs.count == 2) // Ensure replacing whole string keeps coalesced var str2 = str str2.replaceSubrange(str2.startIndex ..< str2.endIndex, with: AttributedString("Hello", attributes: .init().testInt(2))) assertStringIsCoalesced(str2) - XCTAssertEqual(str2.runs.count, 1) + #expect(str2.runs.count == 1) // Ensure replacing subranges splits runs and doesn't coalesce when not equal var str3 = str str3.replaceSubrange(str3.characters.index(after: str3.startIndex) ..< str3.endIndex, with: AttributedString("ello", attributes: .init().testInt(2))) assertStringIsCoalesced(str3) - XCTAssertEqual(str3.runs.count, 2) + #expect(str3.runs.count == 2) var str4 = str str4.replaceSubrange(str4.startIndex ..< str4.characters.index(before: str4.endIndex), with: AttributedString("Hell", attributes: .init().testInt(2))) assertStringIsCoalesced(str4) - XCTAssertEqual(str4.runs.count, 2) + #expect(str4.runs.count == 2) var str5 = str str5.replaceSubrange(str5.characters.index(after: str5.startIndex) ..< str5.characters.index(before: str4.endIndex), with: AttributedString("ell", attributes: .init().testInt(2))) assertStringIsCoalesced(str5) - XCTAssertEqual(str5.runs.count, 3) + #expect(str5.runs.count == 3) // Ensure changing attributes back to match bordering runs coalesces with edge of subrange var str6 = str5 str6.replaceSubrange(str6.characters.index(after: str6.startIndex) ..< str6.endIndex, with: AttributedString("ello", attributes: .init().testInt(1))) assertStringIsCoalesced(str6) - XCTAssertEqual(str6.runs.count, 1) + #expect(str6.runs.count == 1) var str7 = str5 str7.replaceSubrange(str7.startIndex ..< str7.characters.index(before: str7.endIndex), with: AttributedString("Hell", attributes: .init().testInt(1))) assertStringIsCoalesced(str7) - XCTAssertEqual(str7.runs.count, 1) + #expect(str7.runs.count == 1) var str8 = str5 str8.replaceSubrange(str8.characters.index(after: str8.startIndex) ..< str8.characters.index(before: str8.endIndex), with: AttributedString("ell", attributes: .init().testInt(1))) assertStringIsCoalesced(str8) - XCTAssertEqual(str8.runs.count, 1) + #expect(str8.runs.count == 1) var str9 = str5 str9.testInt = 1 assertStringIsCoalesced(str9) - XCTAssertEqual(str9.runs.count, 1) + #expect(str9.runs.count == 1) var str10 = str5 str10[str10.characters.index(after: str10.startIndex) ..< str10.characters.index(before: str10.endIndex)].testInt = 1 assertStringIsCoalesced(str10) - XCTAssertEqual(str10.runs.count, 1) + #expect(str10.runs.count == 1) } - func testReplaceWithEmptyElements() { + @Test func replaceWithEmptyElements() { var str = AttributedString("Hello, world") let range = str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5) str.characters.replaceSubrange(range, with: []) - XCTAssertEqual(str, AttributedString(", world")) + #expect(str == AttributedString(", world")) } - func testDescription() { + @Test func description() { let string = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -903,27 +932,27 @@ E { \tTestInt = 5 } """ - XCTAssertEqual(desc, expected) + #expect(desc == expected) let runsDesc = String(describing: string.runs) - XCTAssertEqual(runsDesc, expected) + #expect(runsDesc == expected) } - func testContainerDescription() { + @Test func containerDescription() { let cont = AttributeContainer().testBool(false).testInt(1).testDouble(2.0).testString("3") let desc = String(describing: cont) // Don't get bitten by any potential changes in the hashing algorithm. - XCTAssertTrue(desc.hasPrefix("{\n")) - XCTAssertTrue(desc.hasSuffix("\n}")) - XCTAssertTrue(desc.contains("\tTestDouble = 2.0\n")) - XCTAssertTrue(desc.contains("\tTestInt = 1\n")) - XCTAssertTrue(desc.contains("\tTestString = 3\n")) - XCTAssertTrue(desc.contains("\tTestBool = false\n")) + #expect(desc.hasPrefix("{\n")) + #expect(desc.hasSuffix("\n}")) + #expect(desc.contains("\tTestDouble = 2.0\n")) + #expect(desc.contains("\tTestInt = 1\n")) + #expect(desc.contains("\tTestString = 3\n")) + #expect(desc.contains("\tTestBool = false\n")) } - func testRunAndSubstringDescription() { + @Test func runAndSubstringDescription() { let string = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -952,134 +981,134 @@ E { \tTestInt = 5 } """] - XCTAssertEqual(runsDescs, expected) + #expect(runsDescs == expected) let subDescs = string.runs.map() { String(describing: string[$0.range]) } - XCTAssertEqual(subDescs, expected) + #expect(subDescs == expected) } - func testReplacingAttributes() { + @Test func replacingAttributes() { var str = AttributedString("Hello", attributes: .init().testInt(2)) str += AttributedString("World", attributes: .init().testString("Test")) var result = str.replacingAttributes(.init().testInt(2).testString("NotTest"), with: .init().testBool(false)) - XCTAssertEqual(result, str) + #expect(result == str) result = str.replacingAttributes(.init().testInt(2), with: .init().testBool(false)) var expected = AttributedString("Hello", attributes: .init().testBool(false)) expected += AttributedString("World", attributes: .init().testString("Test")) - XCTAssertEqual(result, expected) + #expect(result == expected) } - func testScopedAttributeContainer() { + @Test func scopedAttributeContainer() { var str = AttributedString("Hello, world") - XCTAssertNil(str.test.testInt) - XCTAssertNil(str.testInt) + #expect(str.test.testInt == nil) + #expect(str.testInt == nil) str.test.testInt = 2 - XCTAssertEqual(str.test.testInt, 2) - XCTAssertEqual(str.testInt, 2) + #expect(str.test.testInt == 2) + #expect(str.testInt == 2) str.test.testInt = nil - XCTAssertNil(str.test.testInt) - XCTAssertNil(str.testInt) + #expect(str.test.testInt == nil) + #expect(str.testInt == nil) let range = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 5) let otherRange = range.upperBound ..< str.endIndex str[range].test.testBool = true - XCTAssertEqual(str[range].test.testBool, true) - XCTAssertEqual(str[range].testBool, true) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) + #expect(str[range].test.testBool == true) + #expect(str[range].testBool == true) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) str[range].test.testBool = nil - XCTAssertNil(str[range].test.testBool) - XCTAssertNil(str[range].testBool) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) + #expect(str[range].test.testBool == nil) + #expect(str[range].testBool == nil) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) str.test.testBool = true str[range].test.testBool = nil - XCTAssertNil(str[range].test.testBool) - XCTAssertNil(str[range].testBool) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) - XCTAssertEqual(str[otherRange].test.testBool, true) - XCTAssertEqual(str[otherRange].testBool, true) + #expect(str[range].test.testBool == nil) + #expect(str[range].testBool == nil) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) + #expect(str[otherRange].test.testBool == true) + #expect(str[otherRange].testBool == true) } - func testMergeAttributes() { + @Test func mergeAttributes() { let originalAttributes = AttributeContainer.testInt(2).testBool(true) let newAttributes = AttributeContainer.testString("foo") let overlappingAttributes = AttributeContainer.testInt(3).testDouble(4.3) let str = AttributedString("Hello, world", attributes: originalAttributes) - XCTAssertEqual(str.mergingAttributes(newAttributes, mergePolicy: .keepNew), AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) - XCTAssertEqual(str.mergingAttributes(newAttributes, mergePolicy: .keepCurrent), AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) - XCTAssertEqual(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepNew), AttributedString("Hello, world", attributes: overlappingAttributes.testBool(true))) - XCTAssertEqual(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepCurrent), AttributedString("Hello, world", attributes: originalAttributes.testDouble(4.3))) + #expect(str.mergingAttributes(newAttributes, mergePolicy: .keepNew) == AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) + #expect(str.mergingAttributes(newAttributes, mergePolicy: .keepCurrent) == AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) + #expect(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepNew) == AttributedString("Hello, world", attributes: overlappingAttributes.testBool(true))) + #expect(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepCurrent) == AttributedString("Hello, world", attributes: originalAttributes.testDouble(4.3))) } - func testMergeAttributeContainers() { + @Test func mergeAttributeContainers() { let originalAttributes = AttributeContainer.testInt(2).testBool(true) let newAttributes = AttributeContainer.testString("foo") let overlappingAttributes = AttributeContainer.testInt(3).testDouble(4.3) - XCTAssertEqual(originalAttributes.merging(newAttributes, mergePolicy: .keepNew), newAttributes.testInt(2).testBool(true)) - XCTAssertEqual(originalAttributes.merging(newAttributes, mergePolicy: .keepCurrent), newAttributes.testInt(2).testBool(true)) - XCTAssertEqual(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepNew), overlappingAttributes.testBool(true)) - XCTAssertEqual(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepCurrent), originalAttributes.testDouble(4.3)) + #expect(originalAttributes.merging(newAttributes, mergePolicy: .keepNew) == newAttributes.testInt(2).testBool(true)) + #expect(originalAttributes.merging(newAttributes, mergePolicy: .keepCurrent) == newAttributes.testInt(2).testBool(true)) + #expect(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepNew) == overlappingAttributes.testBool(true)) + #expect(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepCurrent) == originalAttributes.testDouble(4.3)) } - func testChangingSingleCharacterUTF8Length() { + @Test func changingSingleCharacterUTF8Length() throws { var attrstr = AttributedString("\u{1F3BA}\u{1F3BA}") // UTF-8 Length of 8 attrstr.characters[attrstr.startIndex] = "A" // Changes UTF-8 Length to 5 - XCTAssertEqual(attrstr.runs.count, 1) - let runRange = attrstr.runs.first!.range + #expect(attrstr.runs.count == 1) + let runRange = try #require(attrstr.runs.first).range let substring = String(attrstr[runRange].characters) - XCTAssertEqual(substring, "A\u{1F3BA}") + #expect(substring == "A\u{1F3BA}") } // MARK: - Substring Tests - func testSubstringBase() { + @Test func substringBase() { let str = AttributedString("Hello World", attributes: .init().testInt(1)) var substr = str[str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5)] - XCTAssertEqual(substr.base, str) + #expect(substr.base == str) substr.testInt = 3 - XCTAssertNotEqual(substr.base, str) + #expect(substr.base != str) var str2 = AttributedString("Hello World", attributes: .init().testInt(1)) let range = str2.startIndex ..< str2.characters.index(str2.startIndex, offsetBy: 5) - XCTAssertEqual(str2[range].base, str2) + #expect(str2[range].base == str2) str2[range].testInt = 3 - XCTAssertEqual(str2[range].base, str2) + #expect(str2[range].base == str2) } - func testSubstringGetAttribute() { + @Test func substringGetAttribute() { let str = AttributedString("Hello World", attributes: .init().testInt(1)) let range = str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5) - XCTAssertEqual(str[range].testInt, 1) - XCTAssertNil(str[range].testString) + #expect(str[range].testInt == 1) + #expect(str[range].testString == nil) var str2 = AttributedString("Hel", attributes: .init().testInt(1)) str2 += AttributedString("lo World", attributes: .init().testInt(2).testBool(true)) let range2 = str2.startIndex ..< str2.characters.index(str2.startIndex, offsetBy: 5) - XCTAssertNil(str2[range2].testInt) - XCTAssertNil(str2[range2].testBool) + #expect(str2[range2].testInt == nil) + #expect(str2[range2].testBool == nil) } - func testSubstringDescription() { + @Test func substringDescription() { var str = AttributedString("Hello", attributes: .init().testInt(2)) str += " " str += AttributedString("World", attributes: .init().testInt(3)) for run in str.runs { let desc = str[run.range].description - XCTAssertFalse(desc.isEmpty) + #expect(!desc.isEmpty) } } - func testSubstringReplaceAttributes() { + @Test func substringReplaceAttributes() { var str = AttributedString("Hello", attributes: .init().testInt(2).testString("Foundation")) str += " " str += AttributedString("World", attributes: .init().testInt(3)) @@ -1091,32 +1120,32 @@ E { expected += AttributedString("llo", attributes: .init().testBool(true)) expected += " " expected += AttributedString("World", attributes: .init().testInt(3)) - XCTAssertEqual(str, expected) + #expect(str == expected) } - func testSubstringEquality() { + @Test func substringEquality() { let str = AttributedString("") let range = str.startIndex ..< str.endIndex - XCTAssertEqual(str[range], str[range]) + #expect(str[range] == str[range]) let str2 = "A" + AttributedString("A", attributes: .init().testInt(2)) let substringA = str2[str2.startIndex ..< str2.index(afterCharacter: str2.startIndex)] let substringB = str2[str2.index(afterCharacter: str2.startIndex) ..< str2.endIndex] - XCTAssertNotEqual(substringA, substringB) - XCTAssertEqual(substringA, substringA) - XCTAssertEqual(substringB, substringB) + #expect(substringA != substringB) + #expect(substringA == substringA) + #expect(substringB == substringB) } - func testInitializationFromSubstring() { + @Test func initializationFromSubstring() throws { var attrStr = AttributedString("yolo^+1 result<:s>^", attributes: AttributeContainer.testInt(2).testString("Hello")) - attrStr.replaceSubrange(attrStr.range(of: "<:s>")!, with: AttributedString("")) - attrStr[attrStr.range(of: "1 result")!].testInt = 3 + attrStr.replaceSubrange(try #require(attrStr.range(of: "<:s>")), with: AttributedString("")) + attrStr[try #require(attrStr.range(of: "1 result"))].testInt = 3 - let range = attrStr.range(of: "+1 result")! + let range = try #require(attrStr.range(of: "+1 result")) let subFinal = attrStr[range] let attrFinal = AttributedString(subFinal) - XCTAssertTrue(attrFinal.characters.elementsEqual(subFinal.characters)) - XCTAssertEqual(attrFinal.runs, subFinal.runs) + #expect(attrFinal.characters.elementsEqual(subFinal.characters)) + #expect(attrFinal.runs == subFinal.runs) var attrStr2 = AttributedString("xxxxxxxx", attributes: .init().testInt(1)) attrStr2 += AttributedString("y", attributes: .init().testInt(2)) @@ -1125,7 +1154,7 @@ E { let subrange = attrStr2.index(attrStr2.startIndex, offsetByCharacters: 5) ..< attrStr2.endIndex let substring2 = attrStr2[subrange] let recreated = AttributedString(substring2) - XCTAssertEqual(recreated.runs.count, 3) + #expect(recreated.runs.count == 3) } #if FOUNDATION_FRAMEWORK @@ -1137,7 +1166,7 @@ E { var attributedString = AttributedString() } - func testJSONEncoding() throws { + @Test func jsonEncoding() throws { let encoder = JSONEncoder() var attrStr = AttributedString("Hello", attributes: AttributeContainer().testBool(true).testString("blue").testInt(1)) attrStr += AttributedString(" World", attributes: AttributeContainer().testInt(2).testDouble(3.0).testString("http://www.apple.com")) @@ -1147,10 +1176,10 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(CodableType.self, from: json) - XCTAssertEqual(decoded.attributedString, attrStr) + #expect(decoded.attributedString == attrStr) } - func testDecodingThenConvertingToNSAttributedString() throws { + @Test func decodingThenConvertingToNSAttributedString() throws { let encoder = JSONEncoder() var attrStr = AttributedString("Hello", attributes: AttributeContainer().testBool(true)) attrStr += AttributedString(" World", attributes: AttributeContainer().testInt(2)) @@ -1161,10 +1190,10 @@ E { let decoded = try decoder.decode(CodableType.self, from: json) let decodedns = try NSAttributedString(decoded.attributedString, including: AttributeScopes.TestAttributes.self) let ns = try NSAttributedString(attrStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(ns, decodedns) + #expect(ns == decodedns) } - func testCustomAttributeCoding() throws { + @Test func customAttributeCoding() throws { struct MyAttributes : AttributeScope { var customCodable : AttributeScopes.TestAttributes.CustomCodableAttribute } @@ -1183,10 +1212,10 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(CodableType.self, from: json) - XCTAssertEqual(decoded.attributedString, attrStr) + #expect(decoded.attributedString == attrStr) } - func testCustomCodableTypeWithCodableAttributedString() throws { + @Test func customCodableTypeWithCodableAttributedString() throws { struct MyType : Codable, Equatable { var other: NonCodableType var str: AttributedString @@ -1222,10 +1251,10 @@ E { let data = try encoder.encode(type) let decoder = JSONDecoder() let decoded = try decoder.decode(MyType.self, from: data) - XCTAssertEqual(type, decoded) + #expect(type == decoded) } - func testCodingErrorsPropagateUpToCallSite() { + @Test func codingErrorsPropagateUpToCallSite() { enum CustomAttribute : CodableAttributedStringKey { typealias Value = String static let name = "CustomAttribute" @@ -1250,12 +1279,12 @@ E { var str = AttributedString("Hello, world") str[CustomAttribute.self] = "test" let encoder = JSONEncoder() - XCTAssertThrowsError(try encoder.encode(Obj(str: str)), "Attribute encoding error did not throw at call site") { err in - XCTAssert(err is TestError, "Encoding did not throw the proper error") + #expect(throws: TestError.self) { + try encoder.encode(Obj(str: str)) } } - func testEncodeWithPartiallyCodableScope() throws { + @Test func encodeWithPartiallyCodableScope() throws { enum NonCodableAttribute : AttributedStringKey { typealias Value = Int static let name = "NonCodableAttributes" @@ -1279,10 +1308,10 @@ E { var expected = str expected[NonCodableAttribute.self] = nil - XCTAssertEqual(decoded.str, expected) + #expect(decoded.str == expected) } - func testAutomaticCoding() throws { + @Test func automaticCoding() throws { struct Obj : Codable, Equatable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var attrStr = AttributedString() @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var optAttrStr : AttributedString? = nil @@ -1313,7 +1342,7 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } // non-nil @@ -1321,17 +1350,16 @@ E { let val = Obj(testValueWithNils: false) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } } - func testManualCoding() throws { + @Test func manualCoding() throws { struct Obj : Codable, Equatable { var attrStr : AttributedString var optAttrStr : AttributedString? @@ -1381,11 +1409,10 @@ E { let val = Obj(testValueWithNils: true) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } // non-nil @@ -1393,43 +1420,39 @@ E { let val = Obj(testValueWithNils: false) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) - } - - } - - func testDecodingCorruptedData() throws { - let jsonStrings = [ - "{\"attributedString\": 2}", - "{\"attributedString\": []}", - "{\"attributedString\": [\"Test\"]}", - "{\"attributedString\": [\"Test\", 0]}", - "{\"attributedString\": [\"\", {}, \"Test\", {}]}", - "{\"attributedString\": [\"Test\", {}, \"\", {}]}", - "{\"attributedString\": [\"\", {\"TestInt\": 1}]}", - "{\"attributedString\": {}}", - "{\"attributedString\": {\"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": []}}", - "{\"attributedString\": {\"runs\": [], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\"], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\", 1], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\", {}, \"Test\", {}], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": \"Test\", {}, \"\", {}, \"attributeTable\": []}}", - ] - + #expect(decoded == val) + } + + } + + @Test(arguments: [ + "{\"attributedString\": 2}", + "{\"attributedString\": []}", + "{\"attributedString\": [\"Test\"]}", + "{\"attributedString\": [\"Test\", 0]}", + "{\"attributedString\": [\"\", {}, \"Test\", {}]}", + "{\"attributedString\": [\"Test\", {}, \"\", {}]}", + "{\"attributedString\": [\"\", {\"TestInt\": 1}]}", + "{\"attributedString\": {}}", + "{\"attributedString\": {\"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": []}}", + "{\"attributedString\": {\"runs\": [], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\"], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\", 1], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\", {}, \"Test\", {}], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": \"Test\", {}, \"\", {}, \"attributeTable\": []}}", + ]) + func decodingCorruptedData(string: String) throws { let decoder = JSONDecoder() - for string in jsonStrings { - XCTAssertThrowsError(try decoder.decode(CodableType.self, from: string.data(using: .utf8)!), "Corrupt data did not throw error for json data: \(string)") { err in - XCTAssertTrue(err is DecodingError, "Decoding threw an error that was not a DecodingError") - } + #expect(throws: DecodingError.self) { + try decoder.decode(CodableType.self, from: string.data(using: .utf8)!) } } - func testCodableRawRepresentableAttribute() throws { + @Test func codableRawRepresentableAttribute() throws { struct Attribute : CodableAttributedStringKey { static let name = "MyAttribute" enum Value: String, Codable, Hashable { @@ -1454,24 +1477,23 @@ E { let encoded = try encoder.encode(Object(str: str)) let decoder = JSONDecoder() let decoded = try decoder.decode(Object.self, from: encoded) - XCTAssertEqual(decoded.str[Attribute.self], .two) + #expect(decoded.str[Attribute.self] == .two) } - func testContainerEncoding() throws { + @Test func containerEncoding() throws { struct ContainerContainer : Codable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var container = AttributeContainer() } let obj = ContainerContainer(container: AttributeContainer().testInt(1).testBool(true)) let encoder = JSONEncoder() let data = try encoder.encode(obj) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(ContainerContainer.self, from: data) - XCTAssertEqual(obj.container, decoded.container) + #expect(obj.container == decoded.container) } - func testDefaultAttributesCoding() throws { + @Test func defaultAttributesCoding() throws { struct DefaultContainer : Codable, Equatable { var str : AttributedString } @@ -1481,25 +1503,25 @@ E { let encoded = try encoder.encode(cont) let decoder = JSONDecoder() let decoded = try decoder.decode(DefaultContainer.self, from: encoded) - XCTAssertEqual(cont, decoded) + #expect(cont == decoded) } - func testDecodingMultibyteCharacters() throws { + @Test func decodingMultibyteCharacters() throws { let json = "{\"str\": [\"🎺ABC\", {\"TestInt\": 2}]}" struct Object : Codable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var str: AttributedString = AttributedString() } let decoder = JSONDecoder() let str = try decoder.decode(Object.self, from: json.data(using: .utf8)!).str - XCTAssertEqual(str.runs.count, 1) - XCTAssertEqual(str.testInt, 2) + #expect(str.runs.count == 1) + #expect(str.testInt == 2) let idx = str.index(beforeCharacter: str.endIndex) - XCTAssertEqual(str.runs[idx].testInt, 2) + #expect(str.runs[idx].testInt == 2) } // MARK: - Conversion Tests - func testConversionToObjC() throws { + @Test func conversionToObjC() throws { var ourString = AttributedString("Hello", attributes: AttributeContainer().testInt(2)) ourString += AttributedString(" ") ourString += AttributedString("World", attributes: AttributeContainer().testString("Courier")) @@ -1507,10 +1529,10 @@ E { let theirString = NSMutableAttributedString(string: "Hello World") theirString.addAttributes([.testInt: NSNumber(value: 2)], range: NSMakeRange(0, 5)) theirString.addAttributes([.testString: "Courier"], range: NSMakeRange(6, 5)) - XCTAssertEqual(theirString, ourObjCString) + #expect(theirString == ourObjCString) } - func testConversionFromObjC() throws { + @Test func conversionFromObjC() throws { let nsString = NSMutableAttributedString(string: "Hello!") let rangeA = NSMakeRange(0, 3) let rangeB = NSMakeRange(3, 3) @@ -1520,10 +1542,10 @@ E { var string = AttributedString("Hel") string.testString = "Courier" string += AttributedString("lo!", attributes: AttributeContainer().testBool(true)) - XCTAssertEqual(string, convertedString) + #expect(string == convertedString) } - func testRoundTripConversion_boxed() throws { + @Test func roundTripConversion_boxed() throws { struct MyCustomType : Hashable { var num: Int var str: String @@ -1544,10 +1566,10 @@ E { let nsString = try NSAttributedString(attrString, including: MyCustomScope.self) let converted = try AttributedString(nsString, including: MyCustomScope.self) - XCTAssertEqual(converted[MyCustomAttribute.self], customVal) + #expect(converted[MyCustomAttribute.self] == customVal) } - func testRoundTripConversion_customConversion() throws { + @Test func roundTripConversion_customConversion() throws { struct MyCustomType : Hashable { } enum MyCustomAttribute : ObjectiveCConvertibleAttributedStringKey { @@ -1567,13 +1589,13 @@ E { attrString[MyCustomAttribute.self] = customVal let nsString = try NSAttributedString(attrString, including: MyCustomScope.self) - XCTAssertTrue(nsString.attribute(.init(MyCustomAttribute.name), at: 0, effectiveRange: nil) is NSUUID) + #expect(nsString.attribute(.init(MyCustomAttribute.name), at: 0, effectiveRange: nil) is NSUUID) let converted = try AttributedString(nsString, including: MyCustomScope.self) - XCTAssertEqual(converted[MyCustomAttribute.self], customVal) + #expect(converted[MyCustomAttribute.self] == customVal) } - func testIncompleteConversionFromObjC() throws { + @Test func incompleteConversionFromObjC() throws { struct TestStringAttributeOnly : AttributeScope { var testString: AttributeScopes.TestAttributes.TestStringAttribute // Missing TestBoolAttribute } @@ -1587,10 +1609,10 @@ E { var expected = AttributedString("Hel", attributes: AttributeContainer().testString("Courier")) expected += AttributedString("lo!") - XCTAssertEqual(converted, expected) + #expect(converted == expected) } - func testIncompleteConversionToObjC() throws { + @Test func incompleteConversionToObjC() throws { struct TestStringAttributeOnly : AttributeScope { var testString: AttributeScopes.TestAttributes.TestStringAttribute // Missing TestBoolAttribute } @@ -1600,10 +1622,10 @@ E { let converted = try NSAttributedString(attrStr, including: TestStringAttributeOnly.self) let attrs = converted.attributes(at: 0, effectiveRange: nil) - XCTAssertFalse(attrs.keys.contains(.testBool)) + #expect(!attrs.keys.contains(.testBool)) } - func testConversionNestedScope() throws { + @Test func conversionNestedScope() throws { struct SuperScope : AttributeScope { var subscope : SubScope var testString: AttributeScopes.TestAttributes.TestStringAttribute @@ -1622,10 +1644,10 @@ E { var expected = AttributedString("Hel", attributes: AttributeContainer().testString("Courier")) expected += AttributedString("lo!", attributes: AttributeContainer().testBool(true)) - XCTAssertEqual(converted, expected) + #expect(converted == expected) } - func testConversionAttributeContainers() throws { + @Test func conversionAttributeContainers() throws { let container = AttributeContainer.testInt(2).testDouble(3.1).testString("Hello") let dictionary = try Dictionary(container, including: \.test) @@ -1634,18 +1656,20 @@ E { .testDouble: 3.1, .testString: "Hello" ] - XCTAssertEqual(dictionary.keys, expected.keys) - XCTAssertEqual(dictionary[.testInt] as! Int, expected[.testInt] as! Int) - XCTAssertEqual(dictionary[.testDouble] as! Double, expected[.testDouble] as! Double) - XCTAssertEqual(dictionary[.testString] as! String, expected[.testString] as! String) + #expect(dictionary.keys == expected.keys) + #expect(dictionary[.testInt] as? Int == expected[.testInt] as? Int) + #expect(dictionary[.testDouble] as? Double == expected[.testDouble] as? Double) + #expect(dictionary[.testString] as? String == expected[.testString] as? String) let container2 = try AttributeContainer(dictionary, including: \.test) - XCTAssertEqual(container, container2) + #expect(container == container2) } - func testConversionFromInvalidObjectiveCValueTypes() throws { + @Test func conversionFromInvalidObjectiveCValueTypes() throws { let nsStr = NSAttributedString(string: "Hello", attributes: [.testInt : "I am not an Int"]) - XCTAssertThrowsError(try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self)) + #expect(throws: (any Error).self) { + try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self) + } struct ConvertibleAttribute: ObjectiveCConvertibleAttributedStringKey { struct Value : Hashable { @@ -1667,10 +1691,12 @@ E { } let nsStr2 = NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key(ConvertibleAttribute.name) : 12345]) - XCTAssertThrowsError(try AttributedString(nsStr2, including: Scope.self)) + #expect(throws: (any Error).self) { + try AttributedString(nsStr2, including: Scope.self) + } } - func testConversionToUTF16() throws { + @Test func conversionToUTF16() throws { // Ensure that we're correctly using UTF16 offsets with NSAS and UTF8 offsets with AS without mixing the two let multiByteCharacters = ["\u{2029}", "\u{1D11E}", "\u{1D122}", "\u{1F91A}\u{1F3FB}"] @@ -1679,28 +1705,28 @@ E { let nsStr = NSAttributedString(string: str, attributes: [.testInt: 2]) let convertedAttrStr = try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(str.utf8.count, convertedAttrStr._guts.runs.first!.length) - XCTAssertEqual(attrStr, convertedAttrStr) + #expect(str.utf8.count == convertedAttrStr._guts.runs.first!.length) + #expect(attrStr == convertedAttrStr) let convertedNSStr = try NSAttributedString(attrStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(nsStr, convertedNSStr) + #expect(nsStr == convertedNSStr) } } - func testConversionWithoutScope() throws { + @Test func conversionWithoutScope() throws { // Ensure simple conversion works (no errors when loading AppKit/UIKit/SwiftUI) let attrStr = AttributedString() let nsStr = NSAttributedString(attrStr) - XCTAssertEqual(nsStr, NSAttributedString()) + #expect(nsStr == NSAttributedString()) let attrStrReverse = AttributedString(nsStr) - XCTAssertEqual(attrStrReverse, attrStr) + #expect(attrStrReverse == attrStr) // Ensure foundation attributes are converted let attrStr2 = AttributedString("Hello", attributes: .init().link(URL(string: "http://apple.com")!)) let nsStr2 = NSAttributedString(attrStr2) - XCTAssertEqual(nsStr2, NSAttributedString(string: "Hello", attributes: [.link : URL(string: "http://apple.com")! as NSURL])) + #expect(nsStr2 == NSAttributedString(string: "Hello", attributes: [.link : URL(string: "http://apple.com")! as NSURL])) let attrStr2Reverse = AttributedString(nsStr2) - XCTAssertEqual(attrStr2Reverse, attrStr2) + #expect(attrStr2Reverse == attrStr2) // Ensure attributes that throw are dropped enum Attribute : ObjectiveCConvertibleAttributedStringKey { @@ -1727,13 +1753,11 @@ E { container[Attribute.self] = 3 let str = AttributedString("Hello", attributes: container) let result = try? NSAttributedString(str, attributeTable: Scope.attributeKeyTypes(), options: .dropThrowingAttributes) // The same call that the no-scope initializer will make - XCTAssertEqual(result, NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key("TestInt") : 2])) + #expect(result == NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key("TestInt") : 2])) } - func testConversionWithoutScope_Accessibility() throws { -#if !canImport(Accessibility) - throw XCTSkip("Unable to import the Accessibility framework") -#else + #if canImport(Accessibility) + @Test func conversionWithoutScope_Accessibility() throws { let attributedString = AttributedString("Hello", attributes: .init().accessibilityTextCustom(["ABC"])) let nsAttributedString = NSAttributedString(attributedString) #if os(macOS) @@ -1741,66 +1765,60 @@ E { #else let attribute = NSAttributedString.Key.accessibilityTextCustom #endif - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [attribute : ["ABC"]])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [attribute : ["ABC"]])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_AppKit() throws { -#if !canImport(AppKit) - throw XCTSkip("Unable to import the AppKit framework") -#else + #if canImport(AppKit) + @Test func conversionWithoutScope_AppKit() throws { var container = AttributeContainer() container.appKit.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_UIKit() throws { -#if !canImport(UIKit) - throw XCTSkip("Unable to import the UIKit framework") -#else + #if canImport(UIKit) + @Test func conversionWithoutScope_UIKit() throws { var container = AttributeContainer() container.uiKit.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_SwiftUI() throws { -#if !canImport(SwiftUI) - throw XCTSkip("Unable to import the SwiftUI framework") -#else + #if canImport(SwiftUI) + @Test func conversionWithoutScope_SwiftUI() throws { var container = AttributeContainer() container.swiftUI.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.init("SwiftUI.Kern") : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.init("SwiftUI.Kern") : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionCoalescing() throws { + @Test func conversionCoalescing() throws { let nsStr = NSMutableAttributedString("Hello, world") nsStr.setAttributes([.link : NSURL(string: "http://apple.com")!, .testInt : NSNumber(integerLiteral: 2)], range: NSRange(location: 0, length: 6)) nsStr.setAttributes([.testInt : NSNumber(integerLiteral: 2)], range: NSRange(location: 6, length: 6)) let attrStr = try AttributedString(nsStr, including: \.test) - XCTAssertEqual(attrStr.runs.count, 1) - XCTAssertEqual(attrStr.runs.first!.range, attrStr.startIndex ..< attrStr.endIndex) - XCTAssertEqual(attrStr.testInt, 2) - XCTAssertNil(attrStr.link) + #expect(attrStr.runs.count == 1) + #expect(attrStr.runs.first!.range == attrStr.startIndex ..< attrStr.endIndex) + #expect(attrStr.testInt == 2) + #expect(attrStr.link == nil) } - func testUnalignedConversion() throws { + @Test func unalignedConversion() throws { let tests: [(NSRange, Int)] = [ (NSRange(location: 0, length: 12), 1), (NSRange(location: 5, length: 2), 3), @@ -1816,7 +1834,7 @@ E { let nsAttributedString = NSMutableAttributedString("Test \u{1F3BA} Test") nsAttributedString.addAttribute(.testInt, value: NSNumber(1), range: test.0) let attrStr = try AttributedString(nsAttributedString, including: \.test) - XCTAssertEqual(attrStr.runs.count, test.1, "Replacement of range \(NSStringFromRange(test.0)) caused a run count of \(attrStr.runs.count)") + #expect(attrStr.runs.count == test.1, "Replacement of range \(NSStringFromRange(test.0)) caused a run count of \(attrStr.runs.count)") } } @@ -1824,14 +1842,14 @@ E { // MARK: - View Tests - func testCharViewIndexing_backwardsFromEndIndex() { + @Test func charViewIndexing_backwardsFromEndIndex() { let testString = AttributedString("abcdefghi") let testChars = testString.characters let testIndex = testChars.index(testChars.endIndex, offsetBy: -1) - XCTAssertEqual(testChars[testIndex], "i") + #expect(testChars[testIndex] == "i") } - func testAttrViewIndexing() { + @Test func attrViewIndexing() { var attrStr = AttributedString("A") attrStr += "B" attrStr += "C" @@ -1845,28 +1863,28 @@ E { i += 1 curIdx = attrStrRuns.index(after: curIdx) } - XCTAssertEqual(i, 1) - XCTAssertEqual(attrStrRuns.count, 1) + #expect(i == 1) + #expect(attrStrRuns.count == 1) } - func testUnicodeScalarsViewIndexing() { + @Test func unicodeScalarsViewIndexing() { let attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let unicode = attrStr.unicodeScalars - XCTAssertEqual(unicode[unicode.index(before: unicode.endIndex)], "\u{301}") - XCTAssertEqual(unicode[unicode.index(unicode.endIndex, offsetBy: -2)], "e") + #expect(unicode[unicode.index(before: unicode.endIndex)] == "\u{301}") + #expect(unicode[unicode.index(unicode.endIndex, offsetBy: -2)] == "e") } - func testCharacterSlicing() { + @Test func characterSlicing() { let a: AttributedString = "\u{1f1fa}\u{1f1f8}" // Regional indicators U & S let i = a.unicodeScalars.index(after: a.startIndex) let b = a.characters[..( _ a: some Sequence, _ b: some Sequence, - file: StaticString = #filePath, line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { - XCTAssertTrue( + #expect( a.elementsEqual(b), "'\(Array(a))' does not equal '\(Array(b))'", - file: file, line: line) + sourceLocation: sourceLocation) } check(str, astr.characters) @@ -1923,28 +1941,28 @@ E { } } - func testUnicodeScalarsSlicing() { + @Test func unicodeScalarsSlicing() { let attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let range = attrStr.startIndex ..< attrStr.endIndex let substringScalars = attrStr[range].unicodeScalars let slicedScalars = attrStr.unicodeScalars[range] let expected: [UnicodeScalar] = ["C", "a", "f", "e", "\u{301}"] - XCTAssertEqual(substringScalars.count, expected.count) - XCTAssertEqual(slicedScalars.count, expected.count) + #expect(substringScalars.count == expected.count) + #expect(slicedScalars.count == expected.count) var indexA = substringScalars.startIndex var indexB = slicedScalars.startIndex var indexExpect = expected.startIndex while indexA != substringScalars.endIndex && indexB != slicedScalars.endIndex { - XCTAssertEqual(substringScalars[indexA], expected[indexExpect]) - XCTAssertEqual(slicedScalars[indexB], expected[indexExpect]) + #expect(substringScalars[indexA] == expected[indexExpect]) + #expect(slicedScalars[indexB] == expected[indexExpect]) indexA = substringScalars.index(after: indexA) indexB = slicedScalars.index(after: indexB) indexExpect = expected.index(after: indexExpect) } } - func testProtocolRunIndexing() { + @Test func protocolRunIndexing() { var str = AttributedString("Foo", attributes: .init().testInt(1)) str += AttributedString("Bar", attributes: .init().testInt(2)) str += AttributedString("Baz", attributes: .init().testInt(3)) @@ -1952,38 +1970,38 @@ E { let runIndices = str.runs.map(\.range.lowerBound) + [str.endIndex] for (i, index) in runIndices.enumerated().dropLast() { - XCTAssertEqual(str.index(afterRun: index), runIndices[i + 1]) + #expect(str.index(afterRun: index) == runIndices[i + 1]) } for (i, index) in runIndices.enumerated().reversed().dropLast() { - XCTAssertEqual(str.index(beforeRun: index), runIndices[i - 1]) + #expect(str.index(beforeRun: index) == runIndices[i - 1]) } for (i, a) in runIndices.enumerated() { for (j, b) in runIndices.enumerated() { - XCTAssertEqual(str.index(a, offsetByRuns: j - i), b) + #expect(str.index(a, offsetByRuns: j - i) == b) } } } // MARK: - Other Tests - func testInitWithSequence() { + @Test func initWithSequence() { let expected = AttributedString("Hello World", attributes: AttributeContainer().testInt(2)) let sequence: [Character] = ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d"] let container = AttributeContainer().testInt(2) let attrStr = AttributedString(sequence, attributes: container) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) let attrStr2 = AttributedString(sequence, attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr2, expected) + #expect(attrStr2 == expected) let attrStr3 = AttributedString(sequence, attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr3, expected) + #expect(attrStr3 == expected) } - func testLongestEffectiveRangeOfAttribute() { + @Test func longestEffectiveRangeOfAttribute() { var str = AttributedString("Abc") str += AttributedString("def", attributes: AttributeContainer.testInt(2).testString("World")) str += AttributedString("ghi", attributes: AttributeContainer.testInt(2).testBool(true)) @@ -1994,27 +2012,27 @@ E { let expectedRange = str.characters.index(str.startIndex, offsetBy: 3) ..< str.characters.index(str.startIndex, offsetBy: 12) let (value, range) = str.runs[\.testInt][idx] - XCTAssertEqual(value, 2) - XCTAssertEqual(range, expectedRange) + #expect(value == 2) + #expect(range == expectedRange) } - func testAttributeContainer() { + @Test func attributeContainer() { var container = AttributeContainer().testBool(true).testInt(1) - XCTAssertEqual(container.testBool, true) - XCTAssertNil(container.testString) + #expect(container.testBool == true) + #expect(container.testString == nil) let attrString = AttributedString("Hello", attributes: container) for run in attrString.runs { - XCTAssertEqual("Hello", String(attrString.characters[run.range])) - XCTAssertEqual(run.testBool, true) - XCTAssertEqual(run.testInt, 1) + #expect("Hello" == String(attrString.characters[run.range])) + #expect(run.testBool == true) + #expect(run.testInt == 1) } container.testBool = nil - XCTAssertNil(container.testBool) + #expect(container.testBool == nil) } - func testAttributeContainerEquality() { + @Test func attributeContainerEquality() { let containerA = AttributeContainer().testInt(2).testString("test") let containerB = AttributeContainer().testInt(2).testString("test") let containerC = AttributeContainer().testInt(3).testString("test") @@ -2022,13 +2040,13 @@ E { var containerE = AttributeContainer() containerE.testInt = 4 - XCTAssertEqual(containerA, containerB) - XCTAssertNotEqual(containerB, containerC) - XCTAssertNotEqual(containerC, containerD) - XCTAssertEqual(containerD, containerE) + #expect(containerA == containerB) + #expect(containerB != containerC) + #expect(containerC != containerD) + #expect(containerD == containerE) } - func testAttributeContainerSetOnSubstring() { + @Test func attributeContainerSetOnSubstring() { let container = AttributeContainer().testBool(true).testInt(1) var attrString = AttributedString("Hello world", attributes: container) @@ -2038,19 +2056,19 @@ E { let runs = attrString.runs let run = runs[ runs.startIndex ] - XCTAssertEqual(String(attrString.characters[run.range]), "Hell") - XCTAssertEqual(run.testString, "yellow") + #expect(String(attrString.characters[run.range]) == "Hell") + #expect(run.testString == "yellow") } - func testSlice() { + @Test func slice() { let attrStr = AttributedString("Hello World") let chars = attrStr.characters let start = chars.index(chars.startIndex, offsetBy: 6) let slice = attrStr[start ..< chars.index(start, offsetBy:5)] - XCTAssertEqual(AttributedString(slice), AttributedString("World")) + #expect(AttributedString(slice) == AttributedString("World")) } - func testCreateStringsFromCharactersWithUnicodeScalarIndexes() { + @Test func createStringsFromCharactersWithUnicodeScalarIndexes() { var attrStr = AttributedString("Caf", attributes: AttributeContainer().testString("a")) attrStr += AttributedString("e", attributes: AttributeContainer().testString("b")) attrStr += AttributedString("\u{301}", attributes: AttributeContainer().testString("c")) @@ -2059,14 +2077,14 @@ E { let strs1 = attrStr.runs.map { String(String.UnicodeScalarView(attrStr.unicodeScalars[$0.range])) } - XCTAssertEqual(strs1, ["Caf", "e", "\u{301}"]) + #expect(strs1 == ["Caf", "e", "\u{301}"]) // The characters view rounds indices down to the nearest character boundary. let strs2 = attrStr.runs.map { String(attrStr.characters[$0.range]) } - XCTAssertEqual(strs2, ["Caf", "", "e\u{301}"]) + #expect(strs2 == ["Caf", "", "e\u{301}"]) } - func testSettingAttributeOnSlice() throws { + @Test func settingAttributeOnSlice() throws { var attrString = AttributedString("This is a string.") var range = attrString.startIndex ..< attrString.characters.index(attrString.startIndex, offsetBy: 1) var myInt = 1 @@ -2082,7 +2100,7 @@ E { myInt = 8 for (attribute, _) in attrString.runs[\.testInt] { if let value = attribute { - XCTAssertEqual(myInt, value) + #expect(myInt == value) myInt += 1 } } @@ -2091,25 +2109,25 @@ E { newAttrString.testInt = nil for (attribute, _) in newAttrString.runs[\.testInt] { - XCTAssertEqual(attribute, nil) + #expect(attribute == nil) } let startIndex = attrString.startIndex attrString.characters[startIndex] = "D" - XCTAssertEqual(attrString.characters[startIndex], "D") + #expect(attrString.characters[startIndex] == "D") } - func testExpressibleByStringLiteral() { + @Test func expressibleByStringLiteral() { let variable : AttributedString = "Test" - XCTAssertEqual(variable, AttributedString("Test")) + #expect(variable == AttributedString("Test")) func takesAttrStr(_ str: AttributedString) { - XCTAssertEqual(str, AttributedString("Test")) + #expect(str == AttributedString("Test")) } takesAttrStr("Test") } - func testHashing() { + @Test func hashing() { let attrStr = AttributedString("Hello, world.", attributes: .init().testInt(2).testBool(false)) let attrStr2 = AttributedString("Hello, world.", attributes: .init().testInt(2).testBool(false)) @@ -2119,12 +2137,12 @@ E { dictionary[attrStr2] = 456 - XCTAssertEqual(attrStr, attrStr2) - XCTAssertEqual(dictionary[attrStr], 456) - XCTAssertEqual(dictionary[attrStr2], 456) + #expect(attrStr == attrStr2) + #expect(dictionary[attrStr] == 456) + #expect(dictionary[attrStr2] == 456) } - func testHashingSubstring() { + @Test func hashingSubstring() { let a: AttributedString = "aXa" let b: AttributedString = "bXb" @@ -2137,16 +2155,16 @@ E { let substrA = a[i1 ..< i2] let substrB = b[j1 ..< j2] - XCTAssertEqual(substrA, substrB) + #expect(substrA == substrB) var hasherA = Hasher() hasherA.combine(substrA) var hasherB = Hasher() hasherB.combine(substrB) - XCTAssertEqual(hasherA.finalize(), hasherB.finalize()) + #expect(hasherA.finalize() == hasherB.finalize()) } - func testHashingContainer() { + @Test func hashingContainer() { let containerA = AttributeContainer.testInt(2).testBool(false) let containerB = AttributeContainer.testInt(2).testBool(false) @@ -2156,196 +2174,196 @@ E { dictionary[containerB] = 456 - XCTAssertEqual(containerA, containerB) - XCTAssertEqual(dictionary[containerA], 456) - XCTAssertEqual(dictionary[containerB], 456) + #expect(containerA == containerB) + #expect(dictionary[containerA] == 456) + #expect(dictionary[containerB] == 456) } - func testUTF16String() { + @Test func utf16String() { let multiByteCharacters = ["\u{2029}", "\u{1D11E}", "\u{1D122}", "\u{1F91A}\u{1F3FB}"] for str in multiByteCharacters { var attrStr = AttributedString("A" + str) attrStr += AttributedString("B", attributes: .init().testInt(2)) attrStr += AttributedString("C", attributes: .init().testInt(3)) - XCTAssertTrue(attrStr == attrStr) - XCTAssertTrue(attrStr.runs == attrStr.runs) + #expect(attrStr == attrStr) + #expect(attrStr.runs == attrStr.runs) } } - func testPlusOperators() { + @Test func plusOperators() { let ab = AttributedString("a") + AttributedString("b") - XCTAssertEqual(ab, AttributedString("ab")) + #expect(ab == AttributedString("ab")) let ab_sub = AttributedString("a") + ab[ab.characters.index(before: ab.endIndex) ..< ab.endIndex] - XCTAssertEqual(ab_sub, ab) + #expect(ab_sub == ab) let ab_lit = AttributedString("a") + "b" - XCTAssertEqual(ab_lit, ab) + #expect(ab_lit == ab) var abc = ab abc += AttributedString("c") - XCTAssertEqual(abc, AttributedString("abc")) + #expect(abc == AttributedString("abc")) var abc_sub = ab abc_sub += abc[abc.characters.index(before: abc.endIndex) ..< abc.endIndex] - XCTAssertEqual(abc_sub, abc) + #expect(abc_sub == abc) var abc_lit = ab abc_lit += "c" - XCTAssertEqual(abc_lit, abc) + #expect(abc_lit == abc) } - func testSearch() { + @Test func search() throws { let testString = AttributedString("abcdefghi") - XCTAssertNil(testString.range(of: "baba")) + #expect(testString.range(of: "baba") == nil) - let abc = testString.range(of: "abc")! - XCTAssertEqual(abc.lowerBound, testString.startIndex) - XCTAssertEqual(String(testString[abc].characters), "abc") + let abc = try #require(testString.range(of: "abc")) + #expect(abc.lowerBound == testString.startIndex) + #expect(String(testString[abc].characters) == "abc") - let def = testString.range(of: "def")! - XCTAssertEqual(def.lowerBound, testString.index(testString.startIndex, offsetByCharacters: 3)) - XCTAssertEqual(String(testString[def].characters), "def") + let def = try #require(testString.range(of: "def")) + #expect(def.lowerBound == testString.index(testString.startIndex, offsetByCharacters: 3)) + #expect(String(testString[def].characters) == "def") - let ghi = testString.range(of: "ghi")! - XCTAssertEqual(ghi.lowerBound, testString.index(testString.startIndex, offsetByCharacters: 6)) - XCTAssertEqual(String(testString[ghi].characters), "ghi") + let ghi = try #require(testString.range(of: "ghi")) + #expect(ghi.lowerBound == testString.index(testString.startIndex, offsetByCharacters: 6)) + #expect(String(testString[ghi].characters) == "ghi") - XCTAssertNil(testString.range(of: "ghij")) + #expect(testString.range(of: "ghij") == nil) let substring = testString[testString.index(afterCharacter: testString.startIndex)..(nsRange, in: str) - XCTAssertNotNil(strRange) - XCTAssertEqual(strRange, str.unicodeScalars.index(str.startIndex, offsetBy: 8) ..< str.unicodeScalars.index(str.startIndex, offsetBy: 9)) - XCTAssertEqual(str[strRange!], "e") + let strRange = try #require(Range(nsRange, in: str)) + #expect(strRange != nil) + #expect(strRange == str.unicodeScalars.index(str.startIndex, offsetBy: 8) ..< str.unicodeScalars.index(str.startIndex, offsetBy: 9)) + #expect(str[strRange!] == "e") var attrStrRange = Range(nsRange, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(attrStrRange, attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("e")) + #expect(attrStrRange != nil) + #expect(attrStrRange == attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("e")) attrStrRange = Range(strRange!, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(attrStrRange, attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("e")) + #expect(attrStrRange != nil) + #expect(attrStrRange == attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("e")) - XCTAssertEqual(NSRange(strRange!, in: str), nsRange) - XCTAssertEqual(NSRange(attrStrRange!, in: attrStr), nsRange) - XCTAssertEqual(Range(attrStrRange!, in: str), strRange!) + #expect(NSRange(strRange!, in: str) == nsRange) + #expect(NSRange(attrStrRange!, in: attrStr) == nsRange) + #expect(Range(attrStrRange!, in: str) == strRange!) } do { @@ -2355,43 +2373,43 @@ E { let nsRange = NSRange(location: 5, length: 3) // The whole first U+1F3BA and the leading surrogate character of the second U+1F3BA let strRange = Range(nsRange, in: str) - XCTAssertNotNil(strRange) - XCTAssertEqual(str[strRange!], "\u{1F3BA}") + #expect(strRange != nil) + #expect(str[strRange!] == "\u{1F3BA}") var attrStrRange = Range(nsRange, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("\u{1F3BA}")) + #expect(attrStrRange != nil) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("\u{1F3BA}")) attrStrRange = Range(strRange!, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("\u{1F3BA}")) + #expect(attrStrRange != nil) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("\u{1F3BA}")) - XCTAssertEqual(NSRange(strRange!, in: str), nsRange) - XCTAssertEqual(NSRange(attrStrRange!, in: attrStr), nsRange) - XCTAssertEqual(Range(attrStrRange!, in: str), strRange!) + #expect(NSRange(strRange!, in: str) == nsRange) + #expect(NSRange(attrStrRange!, in: attrStr) == nsRange) + #expect(Range(attrStrRange!, in: str) == strRange!) } } - func testNSRangeConversionOnSlice() throws { + @Test func nsRangeConversionOnSlice() throws { let str = AttributedString("012345") let slice = str[str.index(str.startIndex, offsetByCharacters: 3) ..< str.endIndex] let nsRange = NSRange(location: 0, length: 2) - let range = try XCTUnwrap(Range(nsRange, in: slice)) - XCTAssertEqual(String(slice[range].characters), "34") + let range = try #require(Range(nsRange, in: slice)) + #expect(String(slice[range].characters) == "34") } #endif // FOUNDATION_FRAMEWORK - func testOOBRangeConversion() { + @Test func oobRangeConversion() { let attrStr = AttributedString("") let str = "Hello" let range = str.index(before: str.endIndex) ..< str.endIndex - XCTAssertNil(Range(range, in: attrStr)) + #expect(Range(range, in: attrStr) == nil) } #if FOUNDATION_FRAMEWORK // TODO: Support scope-specific AttributedString initialization in FoundationPreview - func testScopedCopy() { + @Test func scopedCopy() { var str = AttributedString("A") str += AttributedString("B", attributes: .init().testInt(2)) str += AttributedString("C", attributes: .init().link(URL(string: "http://apple.com")!)) @@ -2401,60 +2419,60 @@ E { let foundation: AttributeScopes.FoundationAttributes let test: AttributeScopes.TestAttributes } - XCTAssertEqual(AttributedString(str, including: FoundationAndTest.self), str) + #expect(AttributedString(str, including: FoundationAndTest.self) == str) struct None : AttributeScope { } - XCTAssertEqual(AttributedString(str, including: None.self), AttributedString("ABCD")) + #expect(AttributedString(str, including: None.self) == AttributedString("ABCD")) var expected = AttributedString("AB") expected += AttributedString("CD", attributes: .init().link(URL(string: "http://apple.com")!)) - XCTAssertEqual(AttributedString(str, including: \.foundation), expected) + #expect(AttributedString(str, including: \.foundation) == expected) expected = AttributedString("A") expected += AttributedString("B", attributes: .init().testInt(2)) expected += "C" expected += AttributedString("D", attributes: .init().testInt(3)) - XCTAssertEqual(AttributedString(str, including: \.test), expected) + #expect(AttributedString(str, including: \.test) == expected) let range = str.index(afterCharacter: str.startIndex) ..< str.index(beforeCharacter: str.endIndex) expected = AttributedString("B", attributes: .init().testInt(2)) + "C" - XCTAssertEqual(AttributedString(str[range], including: \.test), expected) + #expect(AttributedString(str[range], including: \.test) == expected) expected = "B" + AttributedString("C", attributes: .init().link(URL(string: "http://apple.com")!)) - XCTAssertEqual(AttributedString(str[range], including: \.foundation), expected) + #expect(AttributedString(str[range], including: \.foundation) == expected) - XCTAssertEqual(AttributedString(str[range], including: None.self), AttributedString("BC")) + #expect(AttributedString(str[range], including: None.self) == AttributedString("BC")) } - func testScopeIterationAPI() { + @Test func scopeIterationAPI() { struct TestScope : AttributeScope { let testInt: AttributeScopes.TestAttributes.TestIntAttribute let testBool: AttributeScopes.TestAttributes.TestBoolAttribute } let testNames = TestScope.attributeKeys.map { $0.name }.sorted() - XCTAssertEqual(testNames, [AttributeScopes.TestAttributes.TestBoolAttribute.name, AttributeScopes.TestAttributes.TestIntAttribute.name].sorted()) + #expect(testNames == [AttributeScopes.TestAttributes.TestBoolAttribute.name, AttributeScopes.TestAttributes.TestIntAttribute.name].sorted()) struct EmptyScope : AttributeScope { } var emptyIterator = EmptyScope.attributeKeys.makeIterator() - XCTAssertNil(emptyIterator.next()) + #expect(emptyIterator.next() == nil) } #endif // FOUNDATION_FRAMEWORK - func testAssignDifferentSubstring() { + @Test func assignDifferentSubstring() { var attrStr1 = AttributedString("ABCDE") let attrStr2 = AttributedString("XYZ") attrStr1[ attrStr1.range(of: "BCD")! ] = attrStr2[ attrStr2.range(of: "X")! ] - XCTAssertEqual(attrStr1, "AXE") + #expect(attrStr1 == "AXE") } - func testCOWDuringSubstringMutation() { + @Test func cowDuringSubstringMutation() { func frobnicate(_ sub: inout AttributedSubstring) { var new = sub new.testInt = 2 @@ -2465,31 +2483,31 @@ E { frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) let expected = AttributedString("A") + AttributedString("BCD", attributes: .init().testInt(2).testString("Hello")) + AttributedString("E") - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } -#if false // This causes an intentional fatalError(), which we can't test for yet, so unfortunately this test can't be enabled. - func testReassignmentDuringMutation() { - func frobnicate(_ sub: inout AttributedSubstring) { - let other = AttributedString("XYZ") - sub = other[ other.range(of: "X")! ] + #if FOUNDATION_EXIT_TESTS + @Test func reassignmentDuringMutation() async { + await #expect(processExitsWith: .failure) { + func frobnicate(_ sub: inout AttributedSubstring) { + let other = AttributedString("XYZ") + sub = other[ other.range(of: "X")! ] + } + var attrStr = AttributedString("ABCDE") + frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) } - var attrStr = AttributedString("ABCDE") - frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) - - XCTAssertEqual(attrStr, "AXE") } -#endif + #endif - func testAssignDifferentCharacterView() { + @Test func assignDifferentCharacterView() { var attrStr1 = AttributedString("ABC", attributes: .init().testInt(1)) + AttributedString("DE", attributes: .init().testInt(3)) let attrStr2 = AttributedString("XYZ", attributes: .init().testInt(2)) attrStr1.characters = attrStr2.characters - XCTAssertEqual(attrStr1, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr1 == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testCOWDuringCharactersMutation() { + @Test func cowDuringCharactersMutation() { func frobnicate(_ chars: inout AttributedString.CharacterView) { var new = chars new.replaceSubrange(chars.startIndex ..< chars.endIndex, with: "XYZ") @@ -2498,18 +2516,18 @@ E { var attrStr = AttributedString("ABCDE", attributes: .init().testInt(1)) frobnicate(&attrStr.characters) - XCTAssertEqual(attrStr, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testAssignDifferentUnicodeScalarView() { + @Test func assignDifferentUnicodeScalarView() { var attrStr1 = AttributedString("ABC", attributes: .init().testInt(1)) + AttributedString("DE", attributes: .init().testInt(3)) let attrStr2 = AttributedString("XYZ", attributes: .init().testInt(2)) attrStr1.unicodeScalars = attrStr2.unicodeScalars - XCTAssertEqual(attrStr1, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr1 == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testCOWDuringUnicodeScalarsMutation() { + @Test func cowDuringUnicodeScalarsMutation() { func frobnicate(_ chars: inout AttributedString.CharacterView) { var new = chars new.replaceSubrange(chars.startIndex ..< chars.endIndex, with: "XYZ") @@ -2518,10 +2536,10 @@ E { var attrStr = AttributedString("ABCDE", attributes: .init().testInt(1)) frobnicate(&attrStr.characters) - XCTAssertEqual(attrStr, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testUTF8View() { + @Test func utf88View() { let testStrings = [ "Hello, world", "🎺😄abc🎶def", @@ -2531,26 +2549,26 @@ E { for string in testStrings { let attrStr = AttributedString(string) - XCTAssertEqual(attrStr.utf8.count, string.utf8.count, "Counts are not equal for string \(string)") - XCTAssertTrue(attrStr.utf8.elementsEqual(string.utf8), "Full elements are not equal for string \(string)") + #expect(attrStr.utf8.count == string.utf8.count, "Counts are not equal for string \(string)") + #expect(attrStr.utf8.elementsEqual(string.utf8), "Full elements are not equal for string \(string)") for offset in 0 ..< string.utf8.count { let idxInString = string.utf8.index(string.startIndex, offsetBy: offset) let idxInAttrStr = attrStr.utf8.index(attrStr.startIndex, offsetBy: offset) - XCTAssertEqual( - string.utf8.distance(from: string.startIndex, to: idxInString), + #expect( + string.utf8.distance(from: string.startIndex, to: idxInString) == attrStr.utf8.distance(from: attrStr.startIndex, to: idxInAttrStr), "Offsets to \(idxInString) are not equal for string \(string)" ) - XCTAssertEqual(string.utf8[idxInString], attrStr.utf8[idxInAttrStr], "Elements at offset \(offset) are not equal for string \(string)") - XCTAssertTrue(string.utf8[.. Date: Thu, 5 Jun 2025 14:19:43 -0700 Subject: [PATCH 6/7] Fix build failure --- .../AttributedString/AttributedStringTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift index 04f9447ec..d2ea08584 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift @@ -2346,7 +2346,7 @@ E { let attrStr = AttributedString(str) let nsRange = NSRange(location: 8, length: 1) // Just the "e" without the accent - let strRange = try #require(Range(nsRange, in: str)) + let strRange = Range(nsRange, in: str) #expect(strRange != nil) #expect(strRange == str.unicodeScalars.index(str.startIndex, offsetBy: 8) ..< str.unicodeScalars.index(str.startIndex, offsetBy: 9)) #expect(str[strRange!] == "e") From b8698bd9b44061e6f2642ce843aeff6b86404bfc Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 6 Jun 2025 10:21:01 -0700 Subject: [PATCH 7/7] Fix test crash --- .../AttributedString/AttributedStringTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift index d2ea08584..670aabf97 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift @@ -2458,8 +2458,9 @@ E { struct EmptyScope : AttributeScope { } - var emptyIterator = EmptyScope.attributeKeys.makeIterator() - #expect(emptyIterator.next() == nil) + for key in EmptyScope.attributeKeys { + Issue.record("Empty scope should not have produced key \(key)") + } } #endif // FOUNDATION_FRAMEWORK