From 746cb0bd20a5b1d38d40fb70e4027a4be6683f6e Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:54:55 -0600 Subject: [PATCH 01/13] Build with Xcode 15.0.1 --- .buildkite/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b19eab5c6..a65764ce5 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -4,7 +4,7 @@ common_params: - automattic/a8c-ci-toolkit#2.13.0 # Common environment values to use with the `env` key. env: &common_env - IMAGE_ID: xcode-13 + IMAGE_ID: xcode-15.0.1 # This is the default pipeline – it will build and test the app steps: From 4e32af6bc629bd9d50c962c30575ea96d6cf46ec Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:21:33 -0600 Subject: [PATCH 02/13] Add additional string range bounds checking This resolves a string overflow issue that appeared in tests in Xcode 15.0.1 --- .../Extensions/String+RangeConversion.swift | 16 ++++++++++++++++ .../Extensions/StringUTF16+RangeConversion.swift | 5 +++++ .../StringRangeMultibyteConversionTests.swift | 10 ++++++++++ 3 files changed, 31 insertions(+) diff --git a/Aztec/Classes/Extensions/String+RangeConversion.swift b/Aztec/Classes/Extensions/String+RangeConversion.swift index 1105a1d36..88734724d 100644 --- a/Aztec/Classes/Extensions/String+RangeConversion.swift +++ b/Aztec/Classes/Extensions/String+RangeConversion.swift @@ -89,6 +89,10 @@ public extension String { /// private func findValidLowerBound(for utf16Range: Range) -> String.Index { + guard isValidRange(utf16Range) else { + return String.UTF16View.Index(utf16Offset: 0, in: self) + } + guard self.utf16.count >= utf16Range.lowerBound.utf16Offset(in: self) else { return String.UTF16View.Index(utf16Offset: 0, in: self) } @@ -106,6 +110,10 @@ public extension String { /// private func findValidUpperBound(for utf16Range: Range) -> String.Index { + guard isValidRange(utf16Range) else { + return String.Index(utf16Offset: self.utf16.count, in: self) + } + guard self.utf16.count >= utf16Range.upperBound.utf16Offset(in: self) else { return String.Index(utf16Offset: self.utf16.count, in: self) } @@ -241,4 +249,12 @@ public extension String { return startIndex ..< endIndex } + + func isValidRange(_ range: Range) -> Bool { + isValidIndex(range.lowerBound) && isValidIndex(range.upperBound) + } + + func isValidIndex(_ index: String.UTF16View.Index) -> Bool { + (self.startIndex ... self.endIndex).contains(index) + } } diff --git a/Aztec/Classes/Extensions/StringUTF16+RangeConversion.swift b/Aztec/Classes/Extensions/StringUTF16+RangeConversion.swift index 18ffed50e..79e08cd8d 100644 --- a/Aztec/Classes/Extensions/StringUTF16+RangeConversion.swift +++ b/Aztec/Classes/Extensions/StringUTF16+RangeConversion.swift @@ -12,6 +12,11 @@ extension String.UTF16View { func range(from nsRange: NSRange) -> Range { let start = index(startIndex, offsetBy: nsRange.location) let offset = count < nsRange.length ? count : nsRange.length + + guard nsRange.length > 0 else { + return start ..< start + } + let end = index(start, offsetBy: offset) return start ..< end diff --git a/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift b/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift index 62562081f..bd5a8ef39 100644 --- a/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift +++ b/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift @@ -65,4 +65,14 @@ class StringRangeMultibyteConversionTests: XCTestCase { XCTAssert(validRange.lowerBound == calculatedOffByOneRange.lowerBound.utf16Offset(in: candidate)) XCTAssert(validRange.upperBound == calculatedOffByOneRange.upperBound.utf16Offset(in: candidate)) } + + func testThatStartIndexIsReportedAsValid() { + let candidate = "This is a string 👍🏽" + XCTAssertTrue(candidate.isValidIndex(candidate.startIndex)) + } + + func testThatEndIndexIsReportedAsValid() { + let candidate = "This is a string 👍🏽" + XCTAssertTrue(candidate.isValidIndex(candidate.endIndex)) + } } From e73e1c8943211da30682e92026e408a330505060 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:23:00 -0600 Subject: [PATCH 03/13] Fix tests broken by iOS 14 pasteboard restrictions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple requires user interaction to allow using the pasteboard – this change allows us to mock the pasteboard while under test to avoid that --- Aztec.xcodeproj/project.pbxproj | 14 +++----- Aztec/Classes/TextKit/TextStorage.swift | 5 +++ Aztec/Classes/TextKit/TextView.swift | 18 +++++++++-- .../TextKit/TextViewPasteboardDelegate.swift | 10 +++--- .../Extensions/UIPasteboardHelpersTests.swift | 6 ++-- AztecTests/TestingSupport/TextViewStub.swift | 4 ++- .../TestingSupport/UIKit+Extensions.swift | 11 +++++++ AztecTests/TextKit/TextViewTests.swift | 32 +++++++++---------- 8 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 AztecTests/TestingSupport/UIKit+Extensions.swift diff --git a/Aztec.xcodeproj/project.pbxproj b/Aztec.xcodeproj/project.pbxproj index 5042b71ff..c2cf19977 100644 --- a/Aztec.xcodeproj/project.pbxproj +++ b/Aztec.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1A830346228583B200798076 /* NSBundle+AztecBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A830345228583B200798076 /* NSBundle+AztecBundle.swift */; }; + 247D2C662AF5B90900301B71 /* UIKit+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 247D2C652AF5B90900301B71 /* UIKit+Extensions.swift */; }; 40359F261FD88A5F00B1C1D2 /* HRElementConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40359F251FD88A5F00B1C1D2 /* HRElementConverter.swift */; }; 40359F281FD88A7900B1C1D2 /* BRElementConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40359F271FD88A7900B1C1D2 /* BRElementConverter.swift */; }; 40A2986D1FD61B0C00AEDF3B /* ElementConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A2986C1FD61B0C00AEDF3B /* ElementConverter.swift */; }; @@ -282,6 +283,7 @@ /* Begin PBXFileReference section */ 1A830345228583B200798076 /* NSBundle+AztecBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSBundle+AztecBundle.swift"; sourceTree = ""; }; + 247D2C652AF5B90900301B71 /* UIKit+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+Extensions.swift"; sourceTree = ""; }; 40359F251FD88A5F00B1C1D2 /* HRElementConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HRElementConverter.swift; sourceTree = ""; }; 40359F271FD88A7900B1C1D2 /* BRElementConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRElementConverter.swift; sourceTree = ""; }; 40A2986C1FD61B0C00AEDF3B /* ElementConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElementConverter.swift; sourceTree = ""; }; @@ -343,7 +345,6 @@ B5E607321DA56EC700C8A389 /* TextListFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextListFormatterTests.swift; sourceTree = ""; }; B5E94D0F1FE01334000E7C20 /* FigureElementConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FigureElementConverter.swift; sourceTree = ""; }; B5F84B601E70595B0089A76C /* NSAttributedString+Analyzers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Analyzers.swift"; sourceTree = ""; }; - B5F84B621E706B720089A76C /* NSAttributedStringAnalyzerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedStringAnalyzerTests.swift; sourceTree = ""; }; E109B51B1DC33F2C0099605E /* LayoutManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutManager.swift; sourceTree = ""; }; E11B775F1DBA14B40024E455 /* BlockquoteFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockquoteFormatterTests.swift; sourceTree = ""; }; F1000CE61EAA44AA0000B15B /* String+EndOfLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+EndOfLine.swift"; sourceTree = ""; }; @@ -612,7 +613,6 @@ 5951CB9B1D8BC93600E1866F /* AztecTests */, 599F255A1D8BCD97002871D6 /* Frameworks */, 5951CB8F1D8BC93600E1866F /* Products */, - F185A59D2123C07200DDFAB1 /* Recovered References */, ); sourceTree = ""; }; @@ -1053,6 +1053,7 @@ F15415F8213447D70096D18E /* TextViewStub.swift */, B5D575851F2288E2003A62F6 /* TextViewStubAttachmentDelegate.swift */, B52220D21F86A05400D7E092 /* TextViewStubDelegate.swift */, + 247D2C652AF5B90900301B71 /* UIKit+Extensions.swift */, ); path = TestingSupport; sourceTree = ""; @@ -1263,14 +1264,6 @@ path = Conversions; sourceTree = ""; }; - F185A59D2123C07200DDFAB1 /* Recovered References */ = { - isa = PBXGroup; - children = ( - B5F84B621E706B720089A76C /* NSAttributedStringAnalyzerTests.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; F185A5A02123C0D900DDFAB1 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1709,6 +1702,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 247D2C662AF5B90900301B71 /* UIKit+Extensions.swift in Sources */, F185A5B52123C0DA00DDFAB1 /* NSMutableAttributedStringReplaceOcurrencesTests.swift in Sources */, B5D575891F2288E2003A62F6 /* TextViewTests.swift in Sources */, 594C9D741D8BE6C700D74542 /* InNodeConverterTests.swift in Sources */, diff --git a/Aztec/Classes/TextKit/TextStorage.swift b/Aztec/Classes/TextKit/TextStorage.swift index 1abfc7f50..751f9450a 100644 --- a/Aztec/Classes/TextKit/TextStorage.swift +++ b/Aztec/Classes/TextKit/TextStorage.swift @@ -402,6 +402,11 @@ open class TextStorage: NSTextStorage { } } + // MARK: – Plain Text Interaction + open func getPlainText(range: NSRange) -> String { + self.attributedSubstring(from: range).string + } + // MARK: - HTML Interaction open func getHTML(prettify: Bool = false) -> String { diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index e4e0404bf..58d40891e 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -173,6 +173,9 @@ open class TextView: UITextView { /// open var pasteboardDelegate: TextViewPasteboardDelegate = AztecTextViewPasteboardDelegate() + /// An injectable pasteboard – important for automated testing (where the user can't approve a paste operation) + /// + var pasteboard: UIPasteboard = UIPasteboard.general /// If this is true the text view will notify is delegate and notification system when changes happen by calls to methods like setHTML /// @@ -503,10 +506,13 @@ open class TextView: UITextView { open override func copy(_ sender: Any?) { let data = storage.attributedSubstring(from: selectedRange).archivedData() let html = storage.getHTML(range: selectedRange) + let plain = storage.getPlainText(range: selectedRange) + super.copy(sender) storeInPasteboard(encoded: data) storeInPasteboard(html: html) + storeInPasteboard(plain: plain) } open override func paste(_ sender: Any?) { @@ -660,7 +666,7 @@ open class TextView: UITextView { // MARK: - Pasteboard Helpers - internal func storeInPasteboard(encoded data: Data, pasteboard: UIPasteboard = UIPasteboard.general) { + internal func storeInPasteboard(encoded data: Data) { if pasteboard.numberOfItems > 0 { pasteboard.items[0][NSAttributedString.pastesboardUTI] = data; } else { @@ -668,7 +674,7 @@ open class TextView: UITextView { } } - internal func storeInPasteboard(html: String, pasteboard: UIPasteboard = UIPasteboard.general) { + internal func storeInPasteboard(html: String) { if pasteboard.numberOfItems > 0 { pasteboard.items[0][kUTTypeHTML as String] = html; } else { @@ -676,6 +682,14 @@ open class TextView: UITextView { } } + internal func storeInPasteboard(plain: String) { + if pasteboard.numberOfItems > 0 { + pasteboard.items[0][kUTTypePlainText as String] = plain; + } else { + pasteboard.addItems([[kUTTypePlainText as String: plain]]) + } + } + // MARK: - Intercept keyboard operations open override func insertText(_ text: String) { diff --git a/Aztec/Classes/TextKit/TextViewPasteboardDelegate.swift b/Aztec/Classes/TextKit/TextViewPasteboardDelegate.swift index 92a0c13ec..b66e67164 100644 --- a/Aztec/Classes/TextKit/TextViewPasteboardDelegate.swift +++ b/Aztec/Classes/TextKit/TextViewPasteboardDelegate.swift @@ -20,8 +20,8 @@ open class AztecTextViewPasteboardDelegate: TextViewPasteboardDelegate { /// - Returns: True if the paste succeeds, false if it does not. /// open func tryPastingURL(in textView: TextView) -> Bool { - guard UIPasteboard.general.hasURLs, - let url = UIPasteboard.general.url else { + guard textView.pasteboard.hasURLs, + let url = textView.pasteboard.url else { return false } @@ -44,7 +44,7 @@ open class AztecTextViewPasteboardDelegate: TextViewPasteboardDelegate { /// - Returns: True if the paste succeeds, false if it does not. /// open func tryPastingHTML(in textView: TextView) -> Bool { - guard let html = UIPasteboard.general.html(), + guard let html = textView.pasteboard.html(), textView.storage.htmlConverter.isSupported(html) else { return false } @@ -62,7 +62,7 @@ open class AztecTextViewPasteboardDelegate: TextViewPasteboardDelegate { /// - Returns: True if the paste succeeds, false if it does not. /// open func tryPastingAttributedString(in textView: TextView) -> Bool { - guard let string = UIPasteboard.general.attributedString() else { + guard let string = textView.pasteboard.attributedString() else { return false } string.loadLazyAttachments() @@ -112,7 +112,7 @@ open class AztecTextViewPasteboardDelegate: TextViewPasteboardDelegate { /// - Returns: True if the paste succeeds, false if it does not. /// open func tryPastingString(in textView: TextView) -> Bool { - guard let string = UIPasteboard.general.attributedString() else { + guard let string = textView.pasteboard.attributedString() else { return false } diff --git a/AztecTests/Extensions/UIPasteboardHelpersTests.swift b/AztecTests/Extensions/UIPasteboardHelpersTests.swift index e6ef8bf0a..b0dca6faf 100644 --- a/AztecTests/Extensions/UIPasteboardHelpersTests.swift +++ b/AztecTests/Extensions/UIPasteboardHelpersTests.swift @@ -16,8 +16,7 @@ class UIPasteboardHelpersTests: XCTestCase { } func testPasteboardAttributedString() { - let pasteboardName = UIPasteboard.Name("testPasteboard") - let pasteboard = UIPasteboard(name: pasteboardName, create: true)! + let pasteboard = UIPasteboard.forTesting let attributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 12), .foregroundColor: UIColor.red @@ -44,8 +43,7 @@ class UIPasteboardHelpersTests: XCTestCase { } func testPasteboardHTML() { - let pasteboardName = UIPasteboard.Name("testPasteboard") - let pasteboard = UIPasteboard(name: pasteboardName, create: true)! + let pasteboard = UIPasteboard.forTesting let html = "

testing

" pasteboard.setValue(html, forPasteboardType: String(kUTTypeHTML)) diff --git a/AztecTests/TestingSupport/TextViewStub.swift b/AztecTests/TestingSupport/TextViewStub.swift index 1141805a9..5db3ddc92 100644 --- a/AztecTests/TestingSupport/TextViewStub.swift +++ b/AztecTests/TestingSupport/TextViewStub.swift @@ -17,7 +17,7 @@ class TextViewStub: Aztec.TextView { return sample } - init(withHTML html: String? = nil, font: UIFont = .systemFont(ofSize: 14)) { + init(withHTML html: String? = nil, font: UIFont = .systemFont(ofSize: 14), pasteboard: UIPasteboard = .forTesting) { super.init( defaultFont: font, defaultMissingImage: UIImage()) @@ -28,6 +28,8 @@ class TextViewStub: Aztec.TextView { if let html = html { setHTML(html) } + + self.pasteboard = pasteboard } convenience init(withSampleHTML: Bool) { diff --git a/AztecTests/TestingSupport/UIKit+Extensions.swift b/AztecTests/TestingSupport/UIKit+Extensions.swift new file mode 100644 index 000000000..4ab489909 --- /dev/null +++ b/AztecTests/TestingSupport/UIKit+Extensions.swift @@ -0,0 +1,11 @@ +import UIKit + +extension UIPasteboard { + static let forTesting = UIPasteboard.withUniqueName() + + /// Remove all items from the pasteboard + /// + func reset() { + self.items = [[:]] + } +} diff --git a/AztecTests/TextKit/TextViewTests.swift b/AztecTests/TextKit/TextViewTests.swift index cc54d6670..a8c3325f3 100644 --- a/AztecTests/TextKit/TextViewTests.swift +++ b/AztecTests/TextKit/TextViewTests.swift @@ -13,7 +13,9 @@ class TextViewTests: XCTestCase { override func setUp() { super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. + + // Ensure there is no shared state between tests + UIPasteboard.forTesting.reset() } override func tearDown() { @@ -1772,15 +1774,13 @@ class TextViewTests: XCTestCase { /// doesn't cause Aztec to crash. /// func testWritingIntoAnEmptyPasteboardDoesNotCauseAztecToCrash() { - - let pasteboard = UIPasteboard.general - pasteboard.items.removeAll() + UIPasteboard.forTesting.reset() let data = "Foo".data(using: .utf8)! let textView = TextViewStub(withSampleHTML: true) - textView.storeInPasteboard(encoded: data, pasteboard: pasteboard) + textView.storeInPasteboard(encoded: data) - XCTAssertEqual(pasteboard.items.count, 1) + XCTAssertEqual(UIPasteboard.forTesting.items.count, 1) } /// This test verifies that Japanese Characters do not get hexa encoded anymore, since we actually support UTF8! @@ -1983,9 +1983,9 @@ class TextViewTests: XCTestCase { func testPasteOfURLsWithoutSelectedRange() { let textView = TextViewStub(withHTML: "") - let pasteboard = UIPasteboard.general + let url = URL(string: "http://wordpress.com")! - pasteboard.setValue(url, forPasteboardType: String(kUTTypeURL)) + UIPasteboard.forTesting.setValue(url, forPasteboardType: String(kUTTypeURL)) textView.paste(nil) @@ -1995,9 +1995,9 @@ class TextViewTests: XCTestCase { func testPasteOfURLsWithSelectedRange() { let textView = TextViewStub(withHTML: "WordPress") - let pasteboard = UIPasteboard.general + let url = URL(string: "http://wordpress.com")! - pasteboard.setValue(url, forPasteboardType: String(kUTTypeURL)) + UIPasteboard.forTesting.setValue(url, forPasteboardType: String(kUTTypeURL)) textView.selectedRange = NSRange(location: 0, length: 9) textView.paste(nil) @@ -2048,16 +2048,16 @@ class TextViewTests: XCTestCase { sourceTextView.selectedRange = NSRange(location: 0, length: sourceTextView.text.count) sourceTextView.copy(nil) - XCTAssertEqual(UIPasteboard.general.string, "This is text with attributes: bold") + XCTAssertEqual(UIPasteboard.forTesting.string, "This is text with attributes: bold") } - func testCopyHTML() { + func testCopyHTML() throws { let sourceTextView = TextViewStub(withHTML: "

This is text with attributes: bold and italic

") sourceTextView.selectedRange = NSRange(location: 0, length: sourceTextView.text.count) sourceTextView.copy(nil) - XCTAssertEqual(UIPasteboard.general.html(), "

This is text with attributes: bold and italic

") + XCTAssertEqual(UIPasteboard.forTesting.html(), "

This is text with attributes: bold and italic

") } func testCutHTML() { @@ -2066,7 +2066,7 @@ class TextViewTests: XCTestCase { sourceTextView.selectedRange = NSRange(location: 0, length: sourceTextView.text.count) sourceTextView.cut(nil) - XCTAssertEqual(UIPasteboard.general.html(), "

This is text with attributes: bold and italic

") + XCTAssertEqual(UIPasteboard.forTesting.html(), "

This is text with attributes: bold and italic

") } func testCopyPartialHTML() { @@ -2075,7 +2075,7 @@ class TextViewTests: XCTestCase { sourceTextView.selectedRange = NSRange(location: 0, length: 3) sourceTextView.copy(nil) - XCTAssertEqual(UIPasteboard.general.html(), "

bol

") + XCTAssertEqual(UIPasteboard.forTesting.html(), "

bol

") } /// Check if pasting attributed text without color, the color is set to default color @@ -2083,7 +2083,7 @@ class TextViewTests: XCTestCase { let sourceAttributedText = NSAttributedString(string: "Hello world") var pasteItems = [String:Any]() pasteItems[kUTTypePlainText as String] = try! sourceAttributedText.data(from: sourceAttributedText.rangeOfEntireString, documentAttributes: [.documentType: DocumentType.plain]) - UIPasteboard.general.setItems([pasteItems], options: [:]) + UIPasteboard.forTesting.setItems([pasteItems], options: [:]) let textView = TextViewStub(withHTML: "") textView.textColor = UIColor.red textView.paste(nil) From c0767917a2ca50bf01ed6d3cff020702ce6dc071 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:42:22 -0600 Subject: [PATCH 04/13] Test with iOS 17 and iPhone 15 Pro --- fastlane/Fastfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 252f581a6..9487880b5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -2,8 +2,8 @@ default_platform(:ios) -TEST_RUNTIME = 'iOS 15.0' -TEST_DEVICE = 'iPhone 11' +TEST_RUNTIME = 'iOS 17.0' +TEST_DEVICE = 'iPhone 15 Pro' platform :ios do desc 'Builds the project and runs tests' From 39ebb608936c52cee1dc24ba23a6ceecbe835daf Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:45:20 -0600 Subject: [PATCH 05/13] Make Gutenpack renderer tests easier to debug --- .../WordPressEditor.xcodeproj/project.pbxproj | 24 ++++++++++-------- ...g.dat => GutenpackAttachmentRender_2x.png} | Bin ...g.dat => GutenpackAttachmentRender_3x.png} | Bin .../WordPressEditorTests/Resources/README.md | 8 ------ .../GutenpackAttachmentRendererTests.swift | 6 ++--- 5 files changed, 17 insertions(+), 21 deletions(-) rename WordPressEditor/WordPressEditorTests/Resources/{GutenpackAttachmentRender_2x.png.dat => GutenpackAttachmentRender_2x.png} (100%) rename WordPressEditor/WordPressEditorTests/Resources/{GutenpackAttachmentRender_3x.png.dat => GutenpackAttachmentRender_3x.png} (100%) delete mode 100644 WordPressEditor/WordPressEditorTests/Resources/README.md diff --git a/WordPressEditor/WordPressEditor.xcodeproj/project.pbxproj b/WordPressEditor/WordPressEditor.xcodeproj/project.pbxproj index b13f3dcb5..bea775bbf 100644 --- a/WordPressEditor/WordPressEditor.xcodeproj/project.pbxproj +++ b/WordPressEditor/WordPressEditor.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 247D2C712AF5CB3200301B71 /* GutenpackAttachmentRender_2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 247D2C6F2AF5CB3200301B71 /* GutenpackAttachmentRender_2x.png */; }; + 247D2C722AF5CB3200301B71 /* GutenpackAttachmentRender_3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 247D2C702AF5CB3200301B71 /* GutenpackAttachmentRender_3x.png */; }; F105937520B2FEF1005A836E /* Gutenblock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F105937220B2FEF1005A836E /* Gutenblock.swift */; }; F105937720B317D7005A836E /* CommentNode+Gutenberg.swift in Sources */ = {isa = PBXBuildFile; fileRef = F105937620B317D7005A836E /* CommentNode+Gutenberg.swift */; }; F1065DBE20ADEECD008C72CC /* WordPressPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1065DBD20ADEECD008C72CC /* WordPressPlugin.swift */; }; @@ -58,8 +60,6 @@ F1FC70782139952C007AAFB3 /* VideoAttachmentWordPressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FC70772139952C007AAFB3 /* VideoAttachmentWordPressTests.swift */; }; F1FC708021399A3B007AAFB3 /* GutenblockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FC707F21399A3B007AAFB3 /* GutenblockTests.swift */; }; F1FC70862139A998007AAFB3 /* GutenpackAttachmentRendererTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FC70852139A998007AAFB3 /* GutenpackAttachmentRendererTests.swift */; }; - F1FC70932139B70A007AAFB3 /* GutenpackAttachmentRender_3x.png.dat in Resources */ = {isa = PBXBuildFile; fileRef = F1FC70922139B70A007AAFB3 /* GutenpackAttachmentRender_3x.png.dat */; }; - F1FC70972139C778007AAFB3 /* GutenpackAttachmentRender_2x.png.dat in Resources */ = {isa = PBXBuildFile; fileRef = F1FC70952139C57D007AAFB3 /* GutenpackAttachmentRender_2x.png.dat */; }; F9982CFB218786E8001E606B /* WordPressPasteboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9982CFA218786E8001E606B /* WordPressPasteboardDelegate.swift */; }; F9982CFD21878789001E606B /* EmbedURLProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9982CFC21878788001E606B /* EmbedURLProcessor.swift */; }; F9982CFF2187879E001E606B /* EmbedURLProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9982CFE2187879E001E606B /* EmbedURLProcessorTests.swift */; }; @@ -100,6 +100,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 247D2C6F2AF5CB3200301B71 /* GutenpackAttachmentRender_2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GutenpackAttachmentRender_2x.png; sourceTree = ""; }; + 247D2C702AF5CB3200301B71 /* GutenpackAttachmentRender_3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GutenpackAttachmentRender_3x.png; sourceTree = ""; }; F105937220B2FEF1005A836E /* Gutenblock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gutenblock.swift; sourceTree = ""; }; F105937620B317D7005A836E /* CommentNode+Gutenberg.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CommentNode+Gutenberg.swift"; sourceTree = ""; }; F1065DBD20ADEECD008C72CC /* WordPressPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressPlugin.swift; sourceTree = ""; }; @@ -154,9 +156,6 @@ F1FC70772139952C007AAFB3 /* VideoAttachmentWordPressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAttachmentWordPressTests.swift; sourceTree = ""; }; F1FC707F21399A3B007AAFB3 /* GutenblockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenblockTests.swift; sourceTree = ""; }; F1FC70852139A998007AAFB3 /* GutenpackAttachmentRendererTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenpackAttachmentRendererTests.swift; sourceTree = ""; }; - F1FC70922139B70A007AAFB3 /* GutenpackAttachmentRender_3x.png.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = GutenpackAttachmentRender_3x.png.dat; sourceTree = ""; }; - F1FC70952139C57D007AAFB3 /* GutenpackAttachmentRender_2x.png.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = GutenpackAttachmentRender_2x.png.dat; sourceTree = ""; }; - F1FC70962139C58F007AAFB3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; F9982CFA218786E8001E606B /* WordPressPasteboardDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressPasteboardDelegate.swift; sourceTree = ""; }; F9982CFC21878788001E606B /* EmbedURLProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbedURLProcessor.swift; sourceTree = ""; }; F9982CFE2187879E001E606B /* EmbedURLProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbedURLProcessorTests.swift; sourceTree = ""; }; @@ -483,9 +482,8 @@ F1FC70892139B1B4007AAFB3 /* Resources */ = { isa = PBXGroup; children = ( - F1FC70962139C58F007AAFB3 /* README.md */, - F1FC70952139C57D007AAFB3 /* GutenpackAttachmentRender_2x.png.dat */, - F1FC70922139B70A007AAFB3 /* GutenpackAttachmentRender_3x.png.dat */, + 247D2C6F2AF5CB3200301B71 /* GutenpackAttachmentRender_2x.png */, + 247D2C702AF5CB3200301B71 /* GutenpackAttachmentRender_3x.png */, ); path = Resources; sourceTree = ""; @@ -622,8 +620,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - F1FC70972139C778007AAFB3 /* GutenpackAttachmentRender_2x.png.dat in Resources */, - F1FC70932139B70A007AAFB3 /* GutenpackAttachmentRender_3x.png.dat in Resources */, + 247D2C722AF5CB3200301B71 /* GutenpackAttachmentRender_3x.png in Resources */, + 247D2C712AF5CB3200301B71 /* GutenpackAttachmentRender_2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -809,6 +807,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; + COMPRESS_PNG_FILES = NO; DEVELOPMENT_TEAM = PZYM8XX95Q; INFOPLIST_FILE = WordPressEditorTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -818,6 +817,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.automattic.WordPressTests; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_PNG_TEXT = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1010,6 +1010,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; + COMPRESS_PNG_FILES = NO; DEVELOPMENT_TEAM = PZYM8XX95Q; INFOPLIST_FILE = WordPressEditorTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1019,6 +1020,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.automattic.WordPressTests; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_PNG_TEXT = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1029,6 +1031,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; + COMPRESS_PNG_FILES = NO; DEVELOPMENT_TEAM = PZYM8XX95Q; INFOPLIST_FILE = WordPressEditorTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1038,6 +1041,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.automattic.WordPressTests; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_PNG_TEXT = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png.dat b/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png similarity index 100% rename from WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png.dat rename to WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png diff --git a/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png.dat b/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png similarity index 100% rename from WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png.dat rename to WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_3x.png diff --git a/WordPressEditor/WordPressEditorTests/Resources/README.md b/WordPressEditor/WordPressEditorTests/Resources/README.md deleted file mode 100644 index 07a5a6f79..000000000 --- a/WordPressEditor/WordPressEditorTests/Resources/README.md +++ /dev/null @@ -1,8 +0,0 @@ -The reason why some PNG files in the folder have a .dat extension, is because it prevents -Xcode from compressing those images further when building the test target. - -This allows us to compare the data in these files with the images generated by the tests. - -To update any of these images simply execute in the console (with a well placed breakpoint): - - UIImagePNGRepresentation(image).write(to: URL(fileURLWithPath: "some/path")) diff --git a/WordPressEditor/WordPressEditorTests/WordPressPlugin/Gutenberg/GutenpackAttachmentRendererTests.swift b/WordPressEditor/WordPressEditorTests/WordPressPlugin/Gutenberg/GutenpackAttachmentRendererTests.swift index 65b1668a6..5217e0c73 100644 --- a/WordPressEditor/WordPressEditorTests/WordPressPlugin/Gutenberg/GutenpackAttachmentRendererTests.swift +++ b/WordPressEditor/WordPressEditorTests/WordPressPlugin/Gutenberg/GutenpackAttachmentRendererTests.swift @@ -58,9 +58,9 @@ class GutenpackAttachmentRendererTests: XCTestCase { let fileName: String = { if UIScreen.main.scale == 3 { - return "GutenpackAttachmentRender_3x.png" + return "GutenpackAttachmentRender_3x" } else if UIScreen.main.scale == 2 { - return "GutenpackAttachmentRender_2x.png" + return "GutenpackAttachmentRender_2x" } // We no longer support 1x @@ -68,7 +68,7 @@ class GutenpackAttachmentRendererTests: XCTestCase { }() let bundle = Bundle(for: type(of: self)) - guard let url = bundle.url(forResource: fileName, withExtension: "dat", subdirectory: nil), + guard let url = bundle.url(forResource: fileName, withExtension: "png", subdirectory: nil), let expectedPNGRepresentation = try? Data(contentsOf: url, options: []) else { XCTFail() return From 6f244bbe19142df4c1ab97041dab676d3ca75b1c Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:50:15 -0600 Subject: [PATCH 06/13] Fix snapshot tests --- .../GutenpackAttachmentRender_2x.png | Bin 2034 -> 2034 bytes .../GutenpackAttachmentRender_3x.png | Bin 3259 -> 3259 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png b/WordPressEditor/WordPressEditorTests/Resources/GutenpackAttachmentRender_2x.png index 057ef62e5b552a03dd14ea88023183325f3738da..7008d7badc765c60d2a8ad23ff201af036a8eb19 100644 GIT binary patch delta 109 zcmeyw|A~Ksy)7%F5Cby<0|O%vGB8Rpu!7kP3|@@VaCQ)*22>3b14DZz0}E75)WiZ& cMWzKzaDfvGnBnXQpdyC%+YX)GxXFPX0BQIR4*&oF delta 138 zcmeyw|A~KseLV}K6ay;*0|O%v+c8SR*=~#)49sA0CI*J~Oa>OH8b2Tn0uz83qz6JX qFJOeK28u0Uf~!8UfEmt?0BM_XVUY@u;w3b14DZz0}E75)WiZ& eMWzKzFabuL1OH8b2Tn0ylsdqz6JX sFJOeK28u0Uf~jWIS-=cqGqixT-Fv?C7?9#D@Q5sCU{JK!xbH7F0J8rPFaQ7m From 1539e7c2c221ef6fdef0b7b1876bdacbf3557cb1 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:53:21 -0600 Subject: [PATCH 07/13] Disable podspec validation --- .buildkite/pipeline.yml | 9 ++++++++- .buildkite/validate_podspec_annotation.md | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .buildkite/validate_podspec_annotation.md diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a65764ce5..acf3c0827 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -24,9 +24,16 @@ steps: - label: "🔬 Validate Podspecs" key: "validate" command: | - validate_podspec + # validate_podspec + echo '+++ ⚠️ validate_podspec was bypassed ⚠️' + # post a message in the logs + cat .buildkite/validate_podspec_annotation.md + # and also as an annotation + cat .buildkite/validate_podspec_annotation.md | buildkite-agent annotate --style 'warning' env: *common_env plugins: *common_plugins + agents: + queue: upload ################# # Lint diff --git a/.buildkite/validate_podspec_annotation.md b/.buildkite/validate_podspec_annotation.md new file mode 100644 index 000000000..df2ebebd7 --- /dev/null +++ b/.buildkite/validate_podspec_annotation.md @@ -0,0 +1,8 @@ +**`validate_podspec` was bypassed!** + +As of Xcode 14.3, libraries with deployment target below iOS 11 (iOS 12 in Xcode 15) fail to build out of the box. +The reason is a missing file, `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a`, more info [here](https://stackoverflow.com/questions/75574268/missing-file-libarclite-iphoneos-a-xcode-14-3). + +Client apps can work around this with a post install hook that updates the dependency deployment target, but libraries do not have this option. + +In the interest of using up to date CI (i.e. not waste time downloading old images) we bypass validation until CocoaPods fixes the root issue. From 5e4c974dc894d6a6ecbf8a4239fc84eafdfdfcc2 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:50:08 -0700 Subject: [PATCH 08/13] Fix off-by-one issue with string indexes --- Aztec/Classes/Extensions/String+RangeConversion.swift | 2 +- AztecTests/Extensions/StringRangeMultibyteConversionTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Aztec/Classes/Extensions/String+RangeConversion.swift b/Aztec/Classes/Extensions/String+RangeConversion.swift index 88734724d..a8ebd6fd4 100644 --- a/Aztec/Classes/Extensions/String+RangeConversion.swift +++ b/Aztec/Classes/Extensions/String+RangeConversion.swift @@ -255,6 +255,6 @@ public extension String { } func isValidIndex(_ index: String.UTF16View.Index) -> Bool { - (self.startIndex ... self.endIndex).contains(index) + (self.startIndex ..< self.endIndex).contains(index) } } diff --git a/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift b/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift index bd5a8ef39..c0ba68922 100644 --- a/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift +++ b/AztecTests/Extensions/StringRangeMultibyteConversionTests.swift @@ -73,6 +73,6 @@ class StringRangeMultibyteConversionTests: XCTestCase { func testThatEndIndexIsReportedAsValid() { let candidate = "This is a string 👍🏽" - XCTAssertTrue(candidate.isValidIndex(candidate.endIndex)) + XCTAssertFalse(candidate.isValidIndex(candidate.endIndex)) } } From 71a459e260a4c4846c6a8338046b9548b65bdb73 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:53:38 -0700 Subject: [PATCH 09/13] Fix linter issues --- Aztec/Classes/TextKit/TextView.swift | 274 +++++++++--------- .../TestingSupport/UIKit+Extensions.swift | 2 +- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index 58d40891e..7ea683c4b 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -186,21 +186,21 @@ open class TextView: UITextView { open var shouldRecalculateTypingAttributesOnDeleteBackward = true // MARK: - Customizable Input VC - + private var customInputViewController: UIInputViewController? - + open override var inputViewController: UIInputViewController? { get { return customInputViewController } - + set { customInputViewController = newValue } } - + // MARK: - Behavior configuration - + private static let singleLineParagraphFormatters: [AttributeFormatter] = [ HeaderFormatter(headerLevel: .h1), HeaderFormatter(headerLevel: .h2), @@ -211,17 +211,17 @@ open class TextView: UITextView { FigureFormatter(), FigcaptionFormatter(), ] - + /// At some point moving ahead, this could be dynamically generated from the full list of registered formatters /// private lazy var paragraphFormatters: [ParagraphAttributeFormatter] = [ BlockquoteFormatter(), - HeaderFormatter(headerLevel:.h1), - HeaderFormatter(headerLevel:.h2), - HeaderFormatter(headerLevel:.h3), - HeaderFormatter(headerLevel:.h4), - HeaderFormatter(headerLevel:.h5), - HeaderFormatter(headerLevel:.h6), + HeaderFormatter(headerLevel: .h1), + HeaderFormatter(headerLevel: .h2), + HeaderFormatter(headerLevel: .h3), + HeaderFormatter(headerLevel: .h4), + HeaderFormatter(headerLevel: .h5), + HeaderFormatter(headerLevel: .h6), PreFormatter(placeholderAttributes: self.defaultAttributes), TextListFormatter(style: .ordered), TextListFormatter(style: .unordered), @@ -230,9 +230,9 @@ open class TextView: UITextView { // MARK: - Properties: Text Lists var maximumListIndentationLevels = 7 - + // MARK: - Properties: Blockquotes - + /// The max levels of quote indentation allowed /// Default is 9 public var maximumBlockquoteIndentationLevels = 9 @@ -249,7 +249,7 @@ open class TextView: UITextView { public let defaultParagraphStyle: ParagraphStyle var defaultMissingImage: UIImage - + fileprivate var defaultAttributes: [NSAttributedString.Key: Any] { var attributes: [NSAttributedString.Key: Any] = [ .font: defaultFont, @@ -266,7 +266,7 @@ open class TextView: UITextView { return UIColor.label } else { return UIColor.darkText - } + } }() open override var textColor: UIColor? { @@ -281,13 +281,13 @@ open class TextView: UITextView { } // MARK: - Plugin Loading - + var pluginManager: PluginManager { get { return storage.pluginManager } } - + public func load(_ plugin: Plugin) { pluginManager.load(plugin, in: self) } @@ -305,7 +305,7 @@ open class TextView: UITextView { // MARK: - Apparance Properties - + /// Blockquote Blocks Border and Text Colors /// @objc dynamic public var blockquoteBorderColors: [UIColor] { @@ -316,7 +316,7 @@ open class TextView: UITextView { layout.blockquoteBorderColors = newValue } } - + /// Blockquote Blocks Background Color. /// @objc dynamic public var blockquoteBackgroundColor: UIColor? { @@ -359,17 +359,17 @@ open class TextView: UITextView { // MARK: - Overwritten Properties - + /// The reason why we need to use this, instead of `super.typingAttributes`, is that iOS seems to sometimes /// modify `super.typingAttributes` without us having any mechanism to intercept those changes. /// private var myTypingAttributes = [NSAttributedString.Key: Any]() - + override open var typingAttributes: [NSAttributedString.Key: Any] { get { return myTypingAttributes } - + set { // We're still setting the parent's typing attributes in case they're used directly // by any iOS feature. @@ -380,7 +380,7 @@ open class TextView: UITextView { override open var textAlignment: NSTextAlignment { didSet { - if (textAlignment != oldValue) { + if textAlignment != oldValue { recalculateTypingAttributes() } } @@ -395,13 +395,13 @@ open class TextView: UITextView { } let string = textStorage.string - + guard !string.isEndOfParagraph(before: string.endIndex) else { return defaultAttributes } - + let lastLocation = max(string.count - 1, 0) - + return textStorage.attributes(at: lastLocation, effectiveRange: nil) } @@ -418,7 +418,7 @@ open class TextView: UITextView { defaultMissingImage: UIImage) { self.defaultFont = UIFontMetrics.default.scaledFont(for: defaultFont) - + self.defaultParagraphStyle = defaultParagraphStyle self.defaultMissingImage = defaultMissingImage @@ -438,7 +438,7 @@ open class TextView: UITextView { defaultFont = FontProvider.shared.defaultFont defaultParagraphStyle = ParagraphStyle.default defaultMissingImage = Assets.imageIcon - + super.init(coder: aDecoder) commonInit() } @@ -528,7 +528,7 @@ open class TextView: UITextView { return } } - + // MARK: - Intercept Keystrokes public lazy var carriageReturnKeyCommand: UIKeyCommand = { @@ -583,7 +583,7 @@ open class TextView: UITextView { insertText(String(.tab)) } } - + /// Decreases the indentation of the selected range open func decreaseIndent() { let lists = TextListFormatter.lists(in: typingAttributes) @@ -597,7 +597,7 @@ open class TextView: UITextView { } // MARK: - Text List indent methods - + private func indent(list: TextList, increase: Bool = true) { let formatter = TextListFormatter(style: list.style, placeholderAttributes: nil, increaseDepth: true) let li = LiFormatter(placeholderAttributes: nil) @@ -616,25 +616,25 @@ open class TextView: UITextView { } } - + // MARK: Text List increase or decrease indentation private func increaseIndent(listFormatter: TextListFormatter, liFormatter: LiFormatter, targetRange: NSRange) -> NSRange { let finalRange = listFormatter.applyAttributes(to: storage, at: targetRange) liFormatter.applyAttributes(to: storage, at: targetRange) - + return finalRange } - + private func decreaseIndent(listFormatter: TextListFormatter, liFormatter: LiFormatter, targetRange: NSRange) -> NSRange { let finalRange = listFormatter.removeAttributes(from: storage, at: targetRange) liFormatter.removeAttributes(from: storage, at: targetRange) - + return finalRange } - - + + // MARK: - Blockquote indent methods - + private func indent(blockquote: Blockquote, increase: Bool = true) { let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes, increaseDepth: true) let targetRange = formatter.applicationRange(for: selectedRange, in: storage) @@ -657,18 +657,18 @@ open class TextView: UITextView { let finalRange = quoteFormatter.applyAttributes(to: storage, at: targetRange) return finalRange } - + private func decreaseIndent(quoteFormatter: BlockquoteFormatter, targetRange: NSRange) -> NSRange { let finalRange = quoteFormatter.removeAttributes(from: storage, at: targetRange) return finalRange } - + // MARK: - Pasteboard Helpers internal func storeInPasteboard(encoded data: Data) { if pasteboard.numberOfItems > 0 { - pasteboard.items[0][NSAttributedString.pastesboardUTI] = data; + pasteboard.items[0][NSAttributedString.pastesboardUTI] = data } else { pasteboard.addItems([[NSAttributedString.pastesboardUTI: data]]) } @@ -676,7 +676,7 @@ open class TextView: UITextView { internal func storeInPasteboard(html: String) { if pasteboard.numberOfItems > 0 { - pasteboard.items[0][kUTTypeHTML as String] = html; + pasteboard.items[0][kUTTypeHTML as String] = html } else { pasteboard.addItems([[kUTTypeHTML as String: html]]) } @@ -684,7 +684,7 @@ open class TextView: UITextView { internal func storeInPasteboard(plain: String) { if pasteboard.numberOfItems > 0 { - pasteboard.items[0][kUTTypePlainText as String] = plain; + pasteboard.items[0][kUTTypePlainText as String] = plain } else { pasteboard.addItems([[kUTTypePlainText as String: plain]]) } @@ -693,7 +693,7 @@ open class TextView: UITextView { // MARK: - Intercept keyboard operations open override func insertText(_ text: String) { - + // For some reason the text view is allowing the attachment style to be set in // typingAttributes. That's simply not acceptable. // @@ -746,7 +746,7 @@ open class TextView: UITextView { if storage.length > 0 { deletedString = storage.attributedSubstring(from: deletionRange) } - + ensureRemovalOfParagraphStylesBeforeRemovingCharacter(at: selectedRange) super.deleteBackward() @@ -768,22 +768,22 @@ open class TextView: UITextView { } // MARK: - UITextView Overrides - + open override func caretRect(for position: UITextPosition) -> CGRect { var caretRect = super.caretRect(for: position) let characterIndex = offset(from: beginningOfDocument, to: position) - + guard layoutManager.isValidGlyphIndex(characterIndex) else { return caretRect } - + let glyphIndex = layoutManager.glyphIndexForCharacter(at: characterIndex) let usedLineFragment = layoutManager.lineFragmentUsedRect(forGlyphAt: glyphIndex, effectiveRange: nil) - + guard !usedLineFragment.isEmpty else { return caretRect } - + caretRect.origin.y = usedLineFragment.origin.y + textContainerInset.top caretRect.size.height = usedLineFragment.size.height @@ -799,7 +799,7 @@ open class TextView: UITextView { public func getHTML(prettify: Bool = true) -> String { return storage.getHTML(prettify: prettify) } - + /// Loads the specified HTML into the editor, and records a new undo step, /// making sure the undo stack isn't reset /// @@ -823,25 +823,25 @@ open class TextView: UITextView { // https://github.com/wordpress-mobile/WordPress-Aztec-iOS/issues/58 // font = defaultFont - + storage.setHTML(html, defaultAttributes: defaultAttributes) recalculateTypingAttributes() notifyTextViewDidChange() formattingDelegate?.textViewCommandToggledAStyle() } - + public func replace(_ range: NSRange, withHTML html: String) { - + let string = storage.htmlConverter.attributedString(from: html, defaultAttributes: defaultAttributes) - + let originalString = storage.attributedSubstring(from: range) let finalRange = NSRange(location: range.location, length: string.length) - + undoManager?.registerUndo(withTarget: self, handler: { [weak self] target in self?.undoTextReplacement(of: originalString, finalRange: finalRange) }) - + storage.replaceCharacters(in: range, with: string) selectedRange = NSRange(location: finalRange.location + finalRange.length, length: 0) } @@ -976,7 +976,7 @@ open class TextView: UITextView { let applicationRange = formatter.applicationRange(for: range, in: textStorage) let originalString = storage.attributedSubstring(from: applicationRange) - + undoManager?.registerUndo(withTarget: self, handler: { [weak self] target in self?.undoTextReplacement(of: originalString, finalRange: applicationRange) }) @@ -989,7 +989,7 @@ open class TextView: UITextView { // NOTE: We are making sure that the selectedRange location is inside the string // The selected range can be out of the string when you are adding content to the end of the string. // In those cases we check the atributes of the previous caracter - let location = max(0,min(selectedRange.location, textStorage.length-1)) + let location = max(0, min(selectedRange.location, textStorage.length-1)) typingAttributes = textStorage.attributes(at: location, effectiveRange: nil) } notifyTextViewDidChange() @@ -1008,7 +1008,7 @@ open class TextView: UITextView { formatter.removeAttributes(from: storage, at: applicationRange) } else { formatter.applyAttributes(to: storage, at: applicationRange) - } + } if applicationRange.length == 0 { typingAttributes = formatter.toggle(in: typingAttributes) @@ -1016,7 +1016,7 @@ open class TextView: UITextView { // NOTE: We are making sure that the selectedRange location is inside the string // The selected range can be out of the string when you are adding content to the end of the string. // In those cases we check the atributes of the previous caracter - let location = max(0,min(selectedRange.location, textStorage.length-1)) + let location = max(0, min(selectedRange.location, textStorage.length-1)) typingAttributes = textStorage.attributes(at: location, effectiveRange: nil) } notifyTextViewDidChange() @@ -1096,14 +1096,14 @@ open class TextView: UITextView { ensureInsertionOfEndOfLineForEmptyParagraphAtEndOfFile(forApplicationRange: range) let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes) - + toggle(formatter: formatter, atRange: range) - + let citeFormatter = CiteFormatter() - + if citeFormatter.present(in: storage, at: selectedRange.location) { let applicationRange = citeFormatter.applicationRange(for: selectedRange, in: attributedText) - + performUndoable(at: applicationRange) { citeFormatter.removeAttributes(from: storage, at: applicationRange) } @@ -1222,7 +1222,7 @@ open class TextView: UITextView { /// B. We're at the end of the document /// C. There's a List (OR) Blockquote (OR) Pre active /// - /// We're doing this as a workaround, in order to force the LayoutManager render the Bullet (OR) + /// We're doing this as a workaround, in order to force the LayoutManager render the Bullet (OR) /// Blockquote's background. /// private func ensureInsertionOfEndOfLine(beforeInserting text: String) { @@ -1328,9 +1328,9 @@ open class TextView: UITextView { self.selectedRange = pristine } } - + // MARK: - UITextView workarounds - + /// When the selected text range is set, by default, any custom attributes are removed from typingAttributes. /// We override this property to fix that behavior. /// @@ -1338,7 +1338,7 @@ open class TextView: UITextView { get { return super.selectedTextRange } - + set { if let start = newValue?.start { // We need to calculate the new typing attributes before we change the selected text range. @@ -1351,15 +1351,15 @@ open class TextView: UITextView { super.selectedTextRange = newValue } } - + /// Convenience method to recalculate the typing attributes for the current `selectedRange`. /// private func recalculateTypingAttributes() { let location = selectedRange.location - + recalculateTypingAttributes(at: location) } - + /// Recalculate the typing attributes as if the caret was at the provided location. This method is useful /// when the caret is about to be moved to the specified location, but we want the `typingAttributes` to be /// recalculated before the text view's `textViewDidChangeSelection(_:)` is called. @@ -1374,17 +1374,17 @@ open class TextView: UITextView { /// - location: the new caret location. /// private func recalculateTypingAttributes(at location: Int) { - + guard storage.length > 0 else { typingAttributes = defaultAttributes return } - + if storage.string.isEmptyLineAtEndOfFile(at: location) { removeParagraphPropertiesFromTypingAttributes() } else { let location = min(location, storage.length - 1) - + typingAttributes = attributedText.attributes(at: location, effectiveRange: nil) } } @@ -1403,7 +1403,7 @@ open class TextView: UITextView { let originalText = attributedText.attributedSubstring(from: range) let attributedTitle = NSAttributedString(string: title) - let finalRange = NSRange(location: range.location, length: attributedTitle.length) + let finalRange = NSRange(location: range.location, length: attributedTitle.length) undoManager?.registerUndo(withTarget: self, handler: { [weak self] target in self?.undoTextReplacement(of: originalText, finalRange: finalRange) @@ -1474,7 +1474,7 @@ open class TextView: UITextView { open func replaceWithImage(at range: NSRange, sourceURL url: URL, placeHolderImage: UIImage?, identifier: String = UUID().uuidString) -> ImageAttachment { let attachment = ImageAttachment(identifier: identifier, url: url) attachment.delegate = storage - attachment.image = placeHolderImage + attachment.image = placeHolderImage replace(at: range, with: attachment) return attachment } @@ -1591,7 +1591,7 @@ open class TextView: UITextView { guard let locationAfter = textStorage.string.location(after: index) else { selectedRange = NSRange(location: index, length: 0) - return; + return } var newLocation = locationAfter if isPointInsideAttachmentMargin(point: point) { @@ -1633,15 +1633,15 @@ open class TextView: UITextView { return (linkFullRange, linkURL) } - + open func linkURL(for attachment: NSTextAttachment) -> URL? { guard let attachmentRange = textStorage.ranges(forAttachment: attachment).first else { return nil } - + return linkURL(forRange: attachmentRange) } - + /// Returns an NSURL if the specified range as attached a link attribute /// /// - Parameter range: The NSRange to inspect @@ -1662,7 +1662,7 @@ open class TextView: UITextView { } if let urlString = attr as? String { - return URL(string:urlString) + return URL(string: urlString) } return nil @@ -1678,7 +1678,7 @@ open class TextView: UITextView { let index = maxIndex(range.location) var effectiveRange = NSRange() guard index < storage.length, - let _ = storage.attribute(.link, at: index, longestEffectiveRange: &effectiveRange, in: storage.rangeOfEntireString), + let _ = storage.attribute(.link, at: index, longestEffectiveRange: &effectiveRange, in: storage.rangeOfEntireString), let representation = storage.attribute(.linkHtmlRepresentation, at: effectiveRange.location, effectiveRange: nil) as? HTMLRepresentation, case .element(let element) = representation.kind else { return nil @@ -1709,31 +1709,31 @@ open class TextView: UITextView { return effectiveRange } - + // MARK: - Captions open func caption(for attachment: NSTextAttachment) -> NSAttributedString? { return textStorage.caption(for: attachment) } - + open func removeCaption(for attachment: NSTextAttachment) { guard let attachmentRange = textStorage.ranges(forAttachment: attachment).first, let captionRange = textStorage.captionRange(for: attachment) else { return } - + let finalRange = NSRange(location: attachmentRange.location, length: attachmentRange.length + captionRange.length) - + textStorage.replaceCharacters(in: finalRange, with: NSAttributedString(attachment: attachment)) - + notifyTextViewDidChange() } - + open func replaceCaption(for attachment: NSTextAttachment, with newCaption: NSAttributedString) { guard let attachmentRange = textStorage.ranges(forAttachment: attachment).first else { return } - + guard let existingCaptionRange = textStorage.captionRange(for: attachment) else { let newAttachmentString = NSAttributedString(attachment: attachment, caption: newCaption, attributes: [:]) textStorage.replaceCharacters(in: attachmentRange, with: newAttachmentString) @@ -1746,20 +1746,20 @@ open class TextView: UITextView { let originalParagraphStyle = textStorage.attribute(.paragraphStyle, at: existingCaptionRange.location, effectiveRange: nil) as! ParagraphStyle var newAttributes = newCaption.attributes(at: 0, effectiveRange: nil) newAttributes[.paragraphStyle] = originalParagraphStyle - + // TODO: when the caption is not there, we must insert it (and format the attachment as a FIGURE()) - + let finalCaption = NSMutableAttributedString() - + finalCaption.append(newCaption) finalCaption.append(NSAttributedString(.paragraphSeparator, attributes: [:])) finalCaption.setAttributes(newAttributes, range: finalCaption.rangeOfEntireString) - + textStorage.replaceCharacters(in: existingCaptionRange, with: finalCaption) - + notifyTextViewDidChange() } - + // MARK: - Storage Indexes (WTF?) @@ -1825,7 +1825,7 @@ open class TextView: UITextView { /// - Returns: a copy of the original attachment for undoing purposes. /// @discardableResult - open func edit(_ attachment: T, block: (T) -> ()) -> T where T:NSTextAttachment { + open func edit(_ attachment: T, block: (T) -> ()) -> T where T: NSTextAttachment { precondition(storage.range(for: attachment) != nil) @@ -1840,13 +1840,13 @@ open class TextView: UITextView { performUndoable(at: range) { var originalAttributes = storage.attributes(at: range.location, effectiveRange: nil) originalAttributes[.attachment] = copy - + storage.setAttributes(originalAttributes, range: range) return range } - + notifyTextViewDidChange() - + return copy } @@ -1910,19 +1910,19 @@ private extension TextView { guard let previousParagraphRange = attributedText.paragraphRange(before: selectedRange) else { return false } - + let currentParagraphRange = attributedText.paragraphRange(for: selectedRange) - + return previousParagraphRange != currentParagraphRange && storage.string.isEmptyParagraph(at: previousParagraphRange.location) } // MARK: - Single-line attributes logic. - + private func evaluateRemovalOfSingleLineParagraphAttributesAfterSelectionChange() { guard storage.string.isEmptyParagraph(at: selectedRange.location) else { return } - + removeSingleLineParagraphAttributes() removeBlockquoteAndCite() } @@ -1932,43 +1932,43 @@ private extension TextView { private func removeSingleLineParagraphAttributes() { for formatter in type(of: self).singleLineParagraphFormatters { let range = formatter.applicationRange(for: selectedRange, in: textStorage) - + removeAttributes(managedBy: formatter, from: range) removeTypingAttributes(managedBy: formatter) } } - + /// Removes blockquote + cite after pressing ENTER in a line that has both styles. /// private func removeBlockquoteAndCite() { // Blockquote + cite removal let formatter = BlockquoteFormatter(placeholderAttributes: typingAttributes) let citeFormatter = CiteFormatter() - + if formatter.present(in: typingAttributes) && citeFormatter.present(in: typingAttributes) { - + let applicationRange = formatter.applicationRange(for: selectedRange, in: storage) - + typingAttributes = formatter.remove(from: typingAttributes) typingAttributes = citeFormatter.remove(from: typingAttributes) - + performUndoable(at: applicationRange) { formatter.removeAttributes(from: storage, at: applicationRange) citeFormatter.removeAttributes(from: storage, at: applicationRange) - + return applicationRange } } } - + // MARK: - Attributes - + private func removeAttributes(managedBy formatter: AttributeFormatter, from range: NSRange) { let applicationRange = formatter.applicationRange(for: selectedRange, in: textStorage) formatter.removeAttributes(from: textStorage, at: applicationRange) } - + private func removeTypingAttributes(managedBy formatter: AttributeFormatter) { typingAttributes = formatter.remove(from: typingAttributes) } @@ -1985,7 +1985,7 @@ private extension TextView { } removeParagraphFormatting() - + // If there's any paragraph property that's not removed by a formatter, we'll still // make sure it's forcefully removed by the following calls. removeParagraphPropertiesFromTypingAttributes() @@ -1993,7 +1993,7 @@ private extension TextView { return true } - + /// Removes all paragraph formatting from the typingAttributes and from the selected range. /// private func removeParagraphFormatting() { @@ -2014,7 +2014,7 @@ private extension TextView { && storage.string.isEmptyLine(at: selectedRange.location) && typingAttributesHaveRemovableParagraphStyles() } - + /// This method lets the caller know if the typing attributes have removable paragraph styles. /// These styles are simply anything besides

, which should be removed when pressing enter twice. /// @@ -2024,7 +2024,7 @@ private extension TextView { guard let paragraphStyle = typingAttributes[.paragraphStyle] as? ParagraphStyle else { return false } - + return paragraphStyle.hasProperty(where: { type(of: $0) != HTMLParagraph.self }) } @@ -2057,48 +2057,48 @@ private extension TextView { /// is beyond the storage's contents, the typingAttributes will be modified. /// private func removeParagraphProperties(from range: NSRange) { - + let paragraphRanges = storage.paragraphRanges(intersecting: range, includeParagraphSeparator: true) - + for paragraphRange in paragraphRanges { removeParagraphPropertiesFromParagraph(spanning: paragraphRange) } } - + private func removeParagraphPropertiesFromTypingAttributes() { guard let paragraphStyle = typingAttributes[.paragraphStyle] as? ParagraphStyle else { return } - + typingAttributes[.paragraphStyle] = paragraphStyleWithoutProperties(from: paragraphStyle) } - + private func removeParagraphPropertiesFromParagraph(spanning range: NSRange) { - + var attributes = storage.attributes(at: range.location, effectiveRange: nil) - + guard let paragraphStyle = attributes[.paragraphStyle] as? ParagraphStyle else { return } - + attributes[.paragraphStyle] = paragraphStyleWithoutProperties(from: paragraphStyle) - + storage.setAttributes(attributes, range: range) } - + private func paragraphStyleWithoutProperties(from paragraphStyle: ParagraphStyle) -> ParagraphStyle { let newParagraphStyle = ParagraphStyle(with: paragraphStyle) - + // If the topmost property is a paragraph, we can keep it. Otherwise just // create a new one. if let firstProperty = newParagraphStyle.properties.first, type(of: firstProperty) == HTMLParagraph.self { - + newParagraphStyle.properties = [firstProperty] } else { newParagraphStyle.properties = [HTMLParagraph()] } - + return newParagraphStyle } } @@ -2114,11 +2114,11 @@ extension TextView: TextStorageAttachmentsDelegate { imageFor url: URL, onSuccess success: @escaping (UIImage) -> (), onFailure failure: @escaping () -> ()) { - + guard let textAttachmentDelegate = textAttachmentDelegate else { fatalError("This class requires a text attachment delegate to be set.") } - + textAttachmentDelegate.textView(self, attachment: attachment, imageAt: url, onSuccess: success, onFailure: failure) } @@ -2129,12 +2129,12 @@ extension TextView: TextStorageAttachmentsDelegate { return textAttachmentDelegate.textView(self, placeholderFor: attachment) } - + func storage(_ storage: TextStorage, urlFor imageAttachment: ImageAttachment) -> URL? { guard let textAttachmentDelegate = textAttachmentDelegate else { fatalError("This class requires a text attachment delegate to be set.") } - + return textAttachmentDelegate.textView(self, urlFor: imageAttachment) } @@ -2248,7 +2248,7 @@ public extension TextView { /// Undoable Operation. Returns the Final Text Range, resulting from applying the undoable Operation /// Note that for Styling Operations, the Final Range will most likely match the Initial Range. - /// For text editing it will only match the initial range if the original string was replaced with a + /// For text editing it will only match the initial range if the original string was replaced with a /// string of the same length. /// typealias Undoable = () -> NSRange diff --git a/AztecTests/TestingSupport/UIKit+Extensions.swift b/AztecTests/TestingSupport/UIKit+Extensions.swift index 4ab489909..8d2993950 100644 --- a/AztecTests/TestingSupport/UIKit+Extensions.swift +++ b/AztecTests/TestingSupport/UIKit+Extensions.swift @@ -4,7 +4,7 @@ extension UIPasteboard { static let forTesting = UIPasteboard.withUniqueName() /// Remove all items from the pasteboard - /// + /// func reset() { self.items = [[:]] } From 443694626042c6a7004f6d3d664116acc2b243a6 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:56:59 -0700 Subject: [PATCH 10/13] =?UTF-8?q?Apply=20Tony=E2=80=99s=20CocoaPods=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .buildkite/pipeline.yml | 11 +++-------- .buildkite/validate_podspec_annotation.md | 8 -------- 2 files changed, 3 insertions(+), 16 deletions(-) delete mode 100644 .buildkite/validate_podspec_annotation.md diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index acf3c0827..84a2aab0a 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,7 +1,7 @@ # Nodes with values to reuse in the pipeline. common_params: plugins: &common_plugins - - automattic/a8c-ci-toolkit#2.13.0 + - automattic/a8c-ci-toolkit#3.0.1 # Common environment values to use with the `env` key. env: &common_env IMAGE_ID: xcode-15.0.1 @@ -24,16 +24,11 @@ steps: - label: "🔬 Validate Podspecs" key: "validate" command: | - # validate_podspec - echo '+++ ⚠️ validate_podspec was bypassed ⚠️' - # post a message in the logs - cat .buildkite/validate_podspec_annotation.md - # and also as an annotation - cat .buildkite/validate_podspec_annotation.md | buildkite-agent annotate --style 'warning' + validate_podspec --patch-cocoapods env: *common_env plugins: *common_plugins agents: - queue: upload + queue: mac ################# # Lint diff --git a/.buildkite/validate_podspec_annotation.md b/.buildkite/validate_podspec_annotation.md deleted file mode 100644 index df2ebebd7..000000000 --- a/.buildkite/validate_podspec_annotation.md +++ /dev/null @@ -1,8 +0,0 @@ -**`validate_podspec` was bypassed!** - -As of Xcode 14.3, libraries with deployment target below iOS 11 (iOS 12 in Xcode 15) fail to build out of the box. -The reason is a missing file, `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a`, more info [here](https://stackoverflow.com/questions/75574268/missing-file-libarclite-iphoneos-a-xcode-14-3). - -Client apps can work around this with a post install hook that updates the dependency deployment target, but libraries do not have this option. - -In the interest of using up to date CI (i.e. not waste time downloading old images) we bypass validation until CocoaPods fixes the root issue. From 1a00efbe059be2775b551b23b1433b3afad02f6d Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:16:45 -0700 Subject: [PATCH 11/13] Fix CocoaPods warnings --- Aztec/Classes/EditorView/EditorView.swift | 13 +------------ .../Extensions/NSAttributedString+Archive.swift | 10 ++++------ Aztec/Classes/Extensions/UIColor+Parsers.swift | 6 +++--- Aztec/Classes/Extensions/UIPasteboard+Helpers.swift | 2 +- Aztec/Classes/TextKit/TextView.swift | 4 ++-- 5 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Aztec/Classes/EditorView/EditorView.swift b/Aztec/Classes/EditorView/EditorView.swift index 2df843372..c819f428c 100644 --- a/Aztec/Classes/EditorView/EditorView.swift +++ b/Aztec/Classes/EditorView/EditorView.swift @@ -41,18 +41,7 @@ public class EditorView: UIView { richTextView.contentOffset = newValue } } - - public var scrollIndicatorInsets: UIEdgeInsets { - get { - return activeView.scrollIndicatorInsets - } - - set { - htmlTextView.scrollIndicatorInsets = newValue - richTextView.scrollIndicatorInsets = newValue - } - } - + // MARK: - Editing Mode public enum EditMode { diff --git a/Aztec/Classes/Extensions/NSAttributedString+Archive.swift b/Aztec/Classes/Extensions/NSAttributedString+Archive.swift index aafe5696c..cb0014ba9 100644 --- a/Aztec/Classes/Extensions/NSAttributedString+Archive.swift +++ b/Aztec/Classes/Extensions/NSAttributedString+Archive.swift @@ -6,14 +6,12 @@ extension NSAttributedString { static let pastesboardUTI = "com.wordpress.aztec.attributedString" - func archivedData() -> Data { - let data = NSKeyedArchiver.archivedData(withRootObject: self) - return data + func archivedData() throws -> Data { + return try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) } - static func unarchive(with data: Data) -> NSAttributedString? { - let attributedString = NSKeyedUnarchiver.unarchiveObject(with: data) as? NSAttributedString - return attributedString + static func unarchive(with data: Data) throws -> NSAttributedString? { + return try NSKeyedUnarchiver.unarchivedObject(ofClass: NSAttributedString.self, from: data) } } diff --git a/Aztec/Classes/Extensions/UIColor+Parsers.swift b/Aztec/Classes/Extensions/UIColor+Parsers.swift index 01ef6542c..8502090fa 100644 --- a/Aztec/Classes/Extensions/UIColor+Parsers.swift +++ b/Aztec/Classes/Extensions/UIColor+Parsers.swift @@ -8,11 +8,11 @@ public extension UIColor { convenience init?(hexString: String) { let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int = UInt32() - if !Scanner(string: hex).scanHexInt32(&int) { + var int = UInt64() + if !Scanner(string: hex).scanHexInt64(&int) { return nil } - let a, r, g, b: UInt32 + let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) diff --git a/Aztec/Classes/Extensions/UIPasteboard+Helpers.swift b/Aztec/Classes/Extensions/UIPasteboard+Helpers.swift index 0fdf6c073..46be4e0ad 100644 --- a/Aztec/Classes/Extensions/UIPasteboard+Helpers.swift +++ b/Aztec/Classes/Extensions/UIPasteboard+Helpers.swift @@ -51,7 +51,7 @@ private extension UIPasteboard { return nil } - return NSAttributedString.unarchive(with: data) + return try? NSAttributedString.unarchive(with: data) } /// Attempts to unarchive the Pasteboard's Plain Text contents into an Attributed String diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index 7ea683c4b..af6218217 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -495,7 +495,7 @@ open class TextView: UITextView { // MARK: - Intercept copy paste operations open override func cut(_ sender: Any?) { - let data = storage.attributedSubstring(from: selectedRange).archivedData() + let data = try! storage.attributedSubstring(from: selectedRange).archivedData() let html = storage.getHTML(range: selectedRange) super.cut(sender) @@ -504,7 +504,7 @@ open class TextView: UITextView { } open override func copy(_ sender: Any?) { - let data = storage.attributedSubstring(from: selectedRange).archivedData() + let data = try! storage.attributedSubstring(from: selectedRange).archivedData() let html = storage.getHTML(range: selectedRange) let plain = storage.getPlainText(range: selectedRange) From 59f04a1ca4299865e0fb98f5f8b73c7a92bc34c1 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:26:04 -0700 Subject: [PATCH 12/13] Bump iOS Min Version to 12.0 --- Aztec.xcodeproj/project.pbxproj | 16 ++++++++-------- Aztec/Classes/EditorView/EditorView.swift | 10 ++++++++++ AztecTests/EditorView/EditorViewTests.swift | 8 ++++---- WordPress-Aztec-iOS.podspec | 2 +- WordPress-Editor-iOS.podspec | 2 +- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Aztec.xcodeproj/project.pbxproj b/Aztec.xcodeproj/project.pbxproj index c2cf19977..872f83e6f 100644 --- a/Aztec.xcodeproj/project.pbxproj +++ b/Aztec.xcodeproj/project.pbxproj @@ -1826,7 +1826,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = /usr/include/libxml2; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lxml2"; @@ -1888,7 +1888,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = /usr/include/libxml2; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-lxml2"; SDKROOT = iphoneos; @@ -1916,7 +1916,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Aztec/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.Aztec; @@ -1943,7 +1943,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Aztec/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.Aztec; @@ -2032,7 +2032,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = /usr/include/libxml2; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lxml2"; @@ -2060,7 +2060,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Aztec/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.Aztec; @@ -2139,7 +2139,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = /usr/include/libxml2; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lxml2"; @@ -2168,7 +2168,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Aztec/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.Aztec; diff --git a/Aztec/Classes/EditorView/EditorView.swift b/Aztec/Classes/EditorView/EditorView.swift index c819f428c..959f9724f 100644 --- a/Aztec/Classes/EditorView/EditorView.swift +++ b/Aztec/Classes/EditorView/EditorView.swift @@ -42,6 +42,16 @@ public class EditorView: UIView { } } + public var horizontalScrollIndicatorInsets: UIEdgeInsets { + get { + return activeView.horizontalScrollIndicatorInsets + } + set { + htmlTextView.horizontalScrollIndicatorInsets = newValue + richTextView.horizontalScrollIndicatorInsets = newValue + } + } + // MARK: - Editing Mode public enum EditMode { diff --git a/AztecTests/EditorView/EditorViewTests.swift b/AztecTests/EditorView/EditorViewTests.swift index 57a1e87a2..63e9277c1 100644 --- a/AztecTests/EditorView/EditorViewTests.swift +++ b/AztecTests/EditorView/EditorViewTests.swift @@ -55,10 +55,10 @@ class EditorViewTests: XCTestCase { defaultMissingImage: UIImage()) let insets = UIEdgeInsets(top: 10, left: 20, bottom: 30, right: 40) - editorView.scrollIndicatorInsets = insets - - XCTAssertEqual(editorView.richTextView.scrollIndicatorInsets, insets) - XCTAssertEqual(editorView.htmlTextView.scrollIndicatorInsets, insets) + editorView.horizontalScrollIndicatorInsets = insets + + XCTAssertEqual(editorView.richTextView.horizontalScrollIndicatorInsets, insets) + XCTAssertEqual(editorView.htmlTextView.horizontalScrollIndicatorInsets, insets) } func testEditingModeAndActiveView() { diff --git a/WordPress-Aztec-iOS.podspec b/WordPress-Aztec-iOS.podspec index 568a52131..c740a0756 100644 --- a/WordPress-Aztec-iOS.podspec +++ b/WordPress-Aztec-iOS.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.license = { type: 'MPLv2', file: 'LICENSE.md' } s.author = { 'The WordPress Mobile Team' => 'mobile@wordpress.org' } - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.swift_version = '5.0' s.source = { git: 'https://github.com/wordpress-mobile/AztecEditor-iOS.git', tag: s.version.to_s } diff --git a/WordPress-Editor-iOS.podspec b/WordPress-Editor-iOS.podspec index da485d5f1..7fcdb25ee 100644 --- a/WordPress-Editor-iOS.podspec +++ b/WordPress-Editor-iOS.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.license = { type: 'MPLv2', file: 'LICENSE.md' } s.author = { 'The WordPress Mobile Team' => 'mobile@wordpress.org' } - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.swift_version = '5.0' s.source = { git: 'https://github.com/wordpress-mobile/AztecEditor-iOS.git', tag: s.version.to_s } From 1dd0099fc93e92336cdbe04affe95c00eea80946 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 19 Dec 2023 15:53:26 -0700 Subject: [PATCH 13/13] One more warning --- Aztec/Classes/Extensions/NSAttributedString+Attachments.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Aztec/Classes/Extensions/NSAttributedString+Attachments.swift b/Aztec/Classes/Extensions/NSAttributedString+Attachments.swift index 7a16287b9..4c1134292 100644 --- a/Aztec/Classes/Extensions/NSAttributedString+Attachments.swift +++ b/Aztec/Classes/Extensions/NSAttributedString+Attachments.swift @@ -99,8 +99,7 @@ extension NSAttributedString } // MARK: - Captions - - open func caption(for attachment: NSTextAttachment) -> NSAttributedString? { + public func caption(for attachment: NSTextAttachment) -> NSAttributedString? { guard let captionRange = self.captionRange(for: attachment) else { return nil }