From 495bb04ff16739348ae9699c6d7ab7380c756c99 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 21 Jul 2026 22:57:04 -0600 Subject: [PATCH 1/4] Update `SwiftSoup` from `2.7.5` to `2.13.6` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the `'swiftsoup': found 4 file(s) which are unhandled` warning emitted during `swift test`. Upstream dropped the four stray `Info*.plist` files from the package's `Sources/` directory in 2.8.2, so any release past our pin resolves it — this takes us to the latest, 2.13.6. No breaking API changes across 2.8–2.13.6 (Swift 5.9+ floor, satisfied). Our usage — `parse`, `parseBodyFragment`, `clean`, `Element`/`Elements`/`Comment`/ `Entities` — is unchanged. --- Modules/Package.resolved | 6 +++--- Modules/Package.swift | 2 +- Package.resolved | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 1d28546591b6..47ebf3c10ed2 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5a94a8cef753a6b67c6281c7661bb654a9e97e34a4cee0ea42071d0e1a72f474", + "originHash" : "dda5c0004d2c337ca5d411fa9c103eb10933935ce58df86451e2786492abcd51", "pins" : [ { "identity" : "alamofire", @@ -309,8 +309,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup.git", "state" : { - "revision" : "3c2c7e1e72b8abd96eafbae80323c5c1e5317437", - "version" : "2.7.5" + "revision" : "ead56133a693d0184d8c2db1a6d6394410cacfd6", + "version" : "2.13.6" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 7c5caa5d05fe..082cc0214d53 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -46,7 +46,7 @@ let package = Package( .package(url: "https://github.com/erikdoe/ocmock", revision: "2c0bfd373289f4a7716db5d6db471640f91a6507"), .package(url: "https://github.com/johnxnguyen/Down", branch: "master"), .package(url: "https://github.com/kaishin/Gifu", from: "3.4.1"), - .package(url: "https://github.com/scinfu/SwiftSoup", exact: "2.7.5"), + .package(url: "https://github.com/scinfu/SwiftSoup", exact: "2.13.6"), .package(url: "https://github.com/squarefrog/UIDeviceIdentifier", from: "2.3.0"), // We can remove the SVProgressHUD fork once this PR is merged: https://github.com/SVProgressHUD/SVProgressHUD/pull/1131 .package(url: "https://github.com/automattic/SVProgressHUD", branch: "master"), diff --git a/Package.resolved b/Package.resolved index a614aca3a293..e4f561d43c78 100644 --- a/Package.resolved +++ b/Package.resolved @@ -309,8 +309,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup", "state" : { - "revision" : "3c2c7e1e72b8abd96eafbae80323c5c1e5317437", - "version" : "2.7.5" + "revision" : "ead56133a693d0184d8c2db1a6d6394410cacfd6", + "version" : "2.13.6" } }, { From 028cef8880169166a4cecc0f9eef170cf8ef8c6a Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:09:46 -0600 Subject: [PATCH 2/4] Fix Gutenberg upload processors under `SwiftSoup` 2.13.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SwiftSoup` 2.12+ serializes unchanged nodes from a cached slice of the original source, so the `attr()` mutations the media upload processors make on nested elements weren't reflected in the output — only the mutated node is marked dirty, not its ancestors, so `body.html()` re-emitted stale bytes and uploaded media kept their local `file://` URLs and temp IDs. `GutenbergContentParser.html()` now replaces each top-level element with a copy to force a fresh render of the mutated subtrees, while emitting the surrounding comment/text nodes (Gutenberg block delimiters) from their original bytes. Output is byte-for-byte identical to the previous behavior. All Gutenberg upload processor tests pass on 2.13.6. --- .../Processors/GutenbergContentParser.swift | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift index ac4b85c44b87..55e7ec9cd7d2 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift @@ -10,9 +10,9 @@ public class GutenbergParsedBlock { public var attributes: [String: Any] { get { - guard let data = self.attributesData.data(using: .utf8 ), - let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), - let attributes = jsonObject as? [String: Any] + guard let data = self.attributesData.data(using: .utf8), + let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), + let attributes = jsonObject as? [String: Any] else { return [:] } @@ -21,7 +21,8 @@ public class GutenbergParsedBlock { set(newValue) { guard let data = try? JSONSerialization.data(withJSONObject: newValue, options: .sortedKeys), - let attributes = String(data: data, encoding: .utf8) else { + let attributes = String(data: data, encoding: .utf8) + else { return } self.attributesData = attributes @@ -44,8 +45,7 @@ public class GutenbergParsedBlock { if let separatorRange = data.range(of: " ") { self.name = String(data[data.startIndex.. String { - return (try? self.htmlDocument?.body()?.html()) ?? "" + guard let body = self.htmlDocument?.body() else { + return "" + } + // SwiftSoup 2.12+ serializes unchanged nodes from a cached copy of the + // original source, so the attribute mutations the processors make on + // nested elements aren't reflected in the output. Replacing each + // top-level element with a copy marks its subtree dirty and forces a + // re-render, while the surrounding comment and text nodes (the Gutenberg + // block delimiters) are emitted from their original bytes. + for element in body.children().array() { + guard let clone = try? element.copy() as? Element else { + continue + } + try? element.replaceWith(clone) + } + return (try? body.html()) ?? "" } private func traverseChildNodes(element: Element, parentBlock: GutenbergParsedBlock? = nil) { var currentBlock: GutenbergParsedBlock? - element.getChildNodes().forEach { node in - switch node { - // Convert comment tag into block - case let comment as SwiftSoup.Comment: - guard let block = GutenbergParsedBlock(comment: comment, parentBlock: parentBlock) else { - return - } + element.getChildNodes() + .forEach { node in + switch node { + // Convert comment tag into block + case let comment as SwiftSoup.Comment: + guard let block = GutenbergParsedBlock(comment: comment, parentBlock: parentBlock) else { + return + } - // Identify close tag - if let currrentBlock = currentBlock, block.name == "/\(currrentBlock.name)" { - currentBlock = nil - return - } + // Identify close tag + if let currrentBlock = currentBlock, block.name == "/\(currrentBlock.name)" { + currentBlock = nil + return + } - self.blocks.append(block) - currentBlock = block - // Insert HTML elements into block being processed - case let element as SwiftSoup.Element: - if let currentBlock { - currentBlock.elements.add(element) + self.blocks.append(block) + currentBlock = block + // Insert HTML elements into block being processed + case let element as SwiftSoup.Element: + if let currentBlock { + currentBlock.elements.add(element) + } + if element.childNodeSize() > 0 { + traverseChildNodes(element: element, parentBlock: currentBlock ?? parentBlock) + } + default: break } - if element.childNodeSize() > 0 { - traverseChildNodes(element: element, parentBlock: currentBlock ?? parentBlock) - } - default: break } - } } } From cf81fb26e32c2963b0cd75cebaf9647f768a90ba Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:41:25 -0600 Subject: [PATCH 3/4] Add `GutenbergContentParser` tests for its serialization contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the existing suite with the coverage the SwiftSoup 2.13.6 fix relies on but that was missing. The prior element-mutation test only mutates a top-level node, whose change survives serialization even with the 2.12+ regression; the new tests mutate nested and deeply-nested elements, which are the paths that actually broke. Also pins the serialization contract that shifts across SwiftSoup versions — void-element normalization, entity / raw-text / preformatted / unicode fidelity, `html()` idempotence — and the malformed-JSON and sorted-key attribute handling. --- .../Gutenberg/GutenbergContentParser.swift | 307 +++++++++++++----- 1 file changed, 233 insertions(+), 74 deletions(-) diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift b/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift index b5ba010d1203..f2633f40ec2b 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift +++ b/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift @@ -3,42 +3,42 @@ import XCTest class GutenbergContentParserTests: XCTestCase { let singleBlock = """ - -

Hello world!

- - """ + +

Hello world!

+ + """ let nestedBlock = """ - -
-
-

Title

+ +
+
+

Title

- -

This is a nested block.

- + +

This is a nested block.

+ -
+
-

Subtitle

+

Subtitle

- -

This is another nested block.

- + +

This is another nested block.

+ -

Footer

+

Footer

+
-
- - """ + + """ func testParserSingleBlock() { let parser = GutenbergContentParser(for: singleBlock) let blocks = parser.blocks let expectedBlockContent = """ -

Hello world!

- """ +

Hello world!

+ """ XCTAssertEqual(blocks.count, 1, "Should return one block") @@ -59,32 +59,32 @@ class GutenbergContentParserTests: XCTestCase { let blocks = parser.blocks let expectedParentBlockContent = """ -
-
-

Title

+
+
+

Title

- -

This is a nested block.

- + +

This is a nested block.

+ -
+
-

Subtitle

+

Subtitle

- -

This is another nested block.

- + +

This is another nested block.

+ -

Footer

+

Footer

+
-
- """ + """ let expectedNestedBlock1Content = """ -

This is a nested block.

- """ +

This is a nested block.

+ """ let expectedNestedBlock2Content = """ -

This is another nested block.

- """ +

This is another nested block.

+ """ let parentBlock = blocks[0] let nestedBlock1 = parentBlock.blocks[0] @@ -106,7 +106,11 @@ class GutenbergContentParserTests: XCTestCase { XCTAssertEqual(nestedBlock1.attributes["id"] as? Int, 1, "Id attribute matches block's attribute") XCTAssertEqual(nestedBlock1.attributes["name"] as? String, "block1", "Name attribute matches block's attribute") XCTAssertEqual(nestedBlock1.blocks.count, 0, "Shouldn't contain nested blocks") - XCTAssertEqual(nestedBlock1.parentBlock?.content, parentBlock.content, "Should have a parent block and matches parent's content") + XCTAssertEqual( + nestedBlock1.parentBlock?.content, + parentBlock.content, + "Should have a parent block and matches parent's content" + ) XCTAssertEqual(nestedBlock2.name, "wp:nested-block", "Name should match block's name") XCTAssertEqual(nestedBlock2.content, expectedNestedBlock2Content, "Content should match block's content") @@ -114,7 +118,11 @@ class GutenbergContentParserTests: XCTestCase { XCTAssertEqual(nestedBlock2.attributes["id"] as? Int, 2, "Id attribute matches block's attribute") XCTAssertEqual(nestedBlock2.attributes["name"] as? String, "block2", "Name attribute matches block's attribute") XCTAssertEqual(nestedBlock2.blocks.count, 0, "Shouldn't contain nested blocks") - XCTAssertEqual(nestedBlock2.parentBlock?.content, parentBlock.content, "Should have a parent block and matches parent's content") + XCTAssertEqual( + nestedBlock2.parentBlock?.content, + parentBlock.content, + "Should have a parent block and matches parent's content" + ) } func testParserNestedBlockToHTML() { @@ -130,28 +138,28 @@ class GutenbergContentParserTests: XCTestCase { parentBlock.attributes["newId"] = 1001 let expectedResult = """ - -
-
-

Title

+ +
+
+

Title

- -

This is a nested block.

- + +

This is a nested block.

+ -
+
-

Subtitle

+

Subtitle

- -

This is another nested block.

- + +

This is another nested block.

+ -

Footer

+

Footer

+
-
- - """ + + """ XCTAssertEqual(parser.html(), expectedResult, "Parsed content should contain the modifications") } @@ -163,29 +171,180 @@ class GutenbergContentParserTests: XCTestCase { try! parentBlock.elements.select("div").first()?.addClass("new-class") let expectedResult = """ - -
-
-

Title

+ +
+
+

Title

- -

This is a nested block.

- + +

This is a nested block.

+ -
+
-

Subtitle

+

Subtitle

- -

This is another nested block.

- + +

This is another nested block.

+ -

Footer

+

Footer

+
-
- - """ + + """ XCTAssertEqual(parser.html(), expectedResult, "Parsed content should contain the modifications") } + + // MARK: - Serialization contract + + func testVoidElementsAreSelfClosed() { + let input = """ + +

+ + """ + let expected = """ + +

+ + """ + XCTAssertEqual(GutenbergContentParser(for: input).html(), expected) + } + + func testEntitiesArePreserved() { + let content = """ + + Fish & chips + + """ + XCTAssertEqual(GutenbergContentParser(for: content).html(), content) + } + + func testRawTextElementsAreNotEscaped() { + // Regressing here would corrupt Custom HTML / embedded scripts. + let content = """ + + + + """ + XCTAssertTrue(GutenbergContentParser(for: content).html().contains("1 < 2 && 3 > 2")) + } + + func testPreformattedWhitespaceIsPreserved() { + let content = """ + +
line one
+              indented	tabbed
+ + """ + XCTAssertEqual(GutenbergContentParser(for: content).html(), content) + } + + func testUnicodeIsPreserved() { + let content = """ + +

café ☕ 日本語 — Alşksdf

+ + """ + XCTAssertEqual(GutenbergContentParser(for: content).html(), content) + } + + func testContentWithoutBlockCommentsIsPassedThrough() { + let content = "

hello world

" + XCTAssertEqual(GutenbergContentParser(for: content).html(), content) + } + + func testEmptyContentProducesEmptyOutput() { + XCTAssertTrue(GutenbergContentParser(for: "").html().isEmpty) + } + + func testMultipleSiblingBlocksArePreserved() { + let content = """ + +

one

+ + +

two

+ + """ + XCTAssertEqual(GutenbergContentParser(for: content).html(), content) + } + + func testHTMLIsIdempotent() { + let parser = GutenbergContentParser(for: singleBlock) + XCTAssertEqual(parser.html(), parser.html()) + } + + // MARK: - Mutation propagation (SwiftSoup 2.12+ serialization-cache regression) + + func testModifyNestedElementAttribute() throws { + // The mutated is nested inside
; unlike a top-level element, + // its change is dropped by SwiftSoup 2.12+ unless html() re-renders it. + let parser = GutenbergContentParser( + for: """ + +
+ + """ + ) + let image = try XCTUnwrap(parser.blocks.first?.elements.select("img").first()) + try image.attr("src", "https://example.com/new.jpg") + + let output = parser.html() + XCTAssertTrue(output.contains("src=\"https://example.com/new.jpg\"")) + XCTAssertFalse(output.contains("local://old.jpg")) + } + + func testModifyDeeplyNestedElement() throws { + let parser = GutenbergContentParser( + for: """ + +
+ + """ + ) + let image = try XCTUnwrap(parser.blocks.first?.elements.select("img").first()) + try image.attr("src", "https://example.com/deep.jpg") + + XCTAssertTrue(parser.html().contains("https://example.com/deep.jpg")) + } + + // MARK: - Attribute parsing + + func testMissingAttributesParseToEmptyDictionary() throws { + let parser = GutenbergContentParser( + for: """ + +
+ + """ + ) + XCTAssertTrue(try XCTUnwrap(parser.blocks.first).attributes.isEmpty) + } + + func testMalformedAttributesParseToEmptyDictionary() throws { + let parser = GutenbergContentParser( + for: """ + +
+ + """ + ) + XCTAssertTrue(try XCTUnwrap(parser.blocks.first).attributes.isEmpty) + } + + func testWrittenAttributesEscapeSlashesAndSortKeys() throws { + let parser = GutenbergContentParser( + for: """ + +
+ + """ + ) + try XCTUnwrap(parser.blocks.first).attributes = ["id": 100, "href": "https://example.com/f.pdf"] + // JSONSerialization `.sortedKeys` orders "href" before "id" and escapes slashes. + XCTAssertTrue(parser.html().contains(#"{"href":"https:\/\/example.com\/f.pdf","id":100}"#)) + } } From 3bd8aa34005d415ad28b0b0d4443fd243f3f6035 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:29:40 -0600 Subject: [PATCH 4/4] Extract Gutenberg content processors into a `GutenbergProcessors` module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the SwiftSoup-based Gutenberg code — `GutenbergContentParser`, the `GutenbergProcessor` protocol, and the image/gallery/file upload processors — out of the app target into a new `GutenbergProcessors` module so its tests run under `swift test` on macOS instead of a full iOS app build. The Aztec-based processors stay in the app; they depend on UIKit and can't run cross-platform. Wiring is entirely in Package.swift, no project changes: the module is added to `keystoneDependencies` (how the app links it), a cross-platform test target is added to the root package, and the target is registered in the unit-test plan. `PostCoordinator` is the only consumer. Runs via `swift test --filter GutenbergProcessorsTests`. --- Modules/Package.swift | 15 +++++++++++++++ .../GutenbergContentParser.swift | 0 .../GutenbergFileUploadProcessor.swift | 6 +++--- .../GutenbergGalleryUploadProcessor.swift | 6 +++--- .../GutenbergImgUploadProcessor.swift | 6 +++--- .../GutenbergProcessors}/GutenbergProcessor.swift | 0 .../GutenbergContentParserTests.swift | 3 ++- .../GutenbergFileUploadProcessorTests.swift | 2 +- .../GutenbergGalleryUploadProcessorTests.swift | 2 +- .../GutenbergImgUploadProcessorTests.swift | 2 +- ...ergRefactoredGalleryUploadProcessorTests.swift | 2 +- Package.swift | 12 +++++++++++- Tests/KeystoneTests/WordPressUnitTests.xctestplan | 7 +++++++ WordPress/Classes/Services/PostCoordinator.swift | 1 + 14 files changed, 49 insertions(+), 15 deletions(-) rename {WordPress/Classes/ViewRelated/Gutenberg/Processors => Modules/Sources/GutenbergProcessors}/GutenbergContentParser.swift (100%) rename {WordPress/Classes/ViewRelated/Gutenberg/Processors => Modules/Sources/GutenbergProcessors}/GutenbergFileUploadProcessor.swift (84%) rename {WordPress/Classes/ViewRelated/Gutenberg/Processors => Modules/Sources/GutenbergProcessors}/GutenbergGalleryUploadProcessor.swift (95%) rename {WordPress/Classes/ViewRelated/Gutenberg/Processors => Modules/Sources/GutenbergProcessors}/GutenbergImgUploadProcessor.swift (91%) rename {WordPress/Classes/ViewRelated/Gutenberg/Processors => Modules/Sources/GutenbergProcessors}/GutenbergProcessor.swift (100%) rename Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift => Modules/Tests/GutenbergProcessorsTests/GutenbergContentParserTests.swift (99%) rename {Tests/KeystoneTests/Tests/Features/Gutenberg => Modules/Tests/GutenbergProcessorsTests}/GutenbergFileUploadProcessorTests.swift (97%) rename {Tests/KeystoneTests/Tests/Features/Gutenberg => Modules/Tests/GutenbergProcessorsTests}/GutenbergGalleryUploadProcessorTests.swift (99%) rename {Tests/KeystoneTests/Tests/Features/Gutenberg => Modules/Tests/GutenbergProcessorsTests}/GutenbergImgUploadProcessorTests.swift (98%) rename {Tests/KeystoneTests/Tests/Features/Gutenberg => Modules/Tests/GutenbergProcessorsTests}/GutenbergRefactoredGalleryUploadProcessorTests.swift (99%) diff --git a/Modules/Package.swift b/Modules/Package.swift index 082cc0214d53..9d07d6b80bb7 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -12,6 +12,7 @@ let package = Package( .library(name: "AsyncImageKit", targets: ["AsyncImageKit"]), .library(name: "DesignSystem", targets: ["DesignSystem"]), .library(name: "FormattableContentKit", targets: ["FormattableContentKit"]), + .library(name: "GutenbergProcessors", targets: ["GutenbergProcessors"]), .library(name: "JetpackStats", targets: ["JetpackStats"]), .library(name: "JetpackSocial", targets: ["JetpackSocial"]), .library(name: "JetpackStatsWidgetsCore", targets: ["JetpackStatsWidgetsCore"]), @@ -334,6 +335,11 @@ let package = Package( .enableUpcomingFeature("BareSlashRegexLiterals") ] ), + .target( + name: "GutenbergProcessors", + dependencies: [.product(name: "SwiftSoup", package: "SwiftSoup")], + swiftSettings: [.swiftLanguageMode(.v5)] + ), .target( name: "WordPressReader", dependencies: [ @@ -400,6 +406,14 @@ let package = Package( ), .testTarget(name: "WordPressCoreTests", dependencies: [.target(name: "WordPressCore")]), .testTarget(name: "WordPressIntelligenceTests", dependencies: [.target(name: "WordPressIntelligence")]), + .testTarget( + name: "GutenbergProcessorsTests", + dependencies: [ + .target(name: "GutenbergProcessors"), + .product(name: "SwiftSoup", package: "SwiftSoup") + ], + swiftSettings: [.swiftLanguageMode(.v5)] + ), .testTarget(name: "WordPressReaderTests", dependencies: [.target(name: "WordPressReader")]), .testTarget( name: "JetpackSocialTests", @@ -484,6 +498,7 @@ enum XcodeSupport { "DesignSystem", "BuildSettingsKit", "FormattableContentKit", + "GutenbergProcessors", "JetpackSocial", "JetpackStats", "JetpackStatsWidgetsCore", diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift b/Modules/Sources/GutenbergProcessors/GutenbergContentParser.swift similarity index 100% rename from WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergContentParser.swift rename to Modules/Sources/GutenbergProcessors/GutenbergContentParser.swift diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergFileUploadProcessor.swift b/Modules/Sources/GutenbergProcessors/GutenbergFileUploadProcessor.swift similarity index 84% rename from WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergFileUploadProcessor.swift rename to Modules/Sources/GutenbergProcessors/GutenbergFileUploadProcessor.swift index 7a16f8ac56aa..4417ec007908 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergFileUploadProcessor.swift +++ b/Modules/Sources/GutenbergProcessors/GutenbergFileUploadProcessor.swift @@ -1,6 +1,6 @@ import Foundation -class GutenbergFileUploadProcessor: GutenbergProcessor { +public class GutenbergFileUploadProcessor: GutenbergProcessor { private struct FileBlockKeys { static var name = "wp:file" static var id = "id" @@ -11,7 +11,7 @@ class GutenbergFileUploadProcessor: GutenbergProcessor { let remoteURLString: String let serverMediaID: Int - init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String) { + public init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String) { self.mediaUploadID = mediaUploadID self.serverMediaID = serverMediaID self.remoteURLString = remoteURLString @@ -34,7 +34,7 @@ class GutenbergFileUploadProcessor: GutenbergProcessor { } } - func process(_ blocks: [GutenbergParsedBlock]) { + public func process(_ blocks: [GutenbergParsedBlock]) { processFileBlocks(blocks) } } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergGalleryUploadProcessor.swift b/Modules/Sources/GutenbergProcessors/GutenbergGalleryUploadProcessor.swift similarity index 95% rename from WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergGalleryUploadProcessor.swift rename to Modules/Sources/GutenbergProcessors/GutenbergGalleryUploadProcessor.swift index 3661750eecb9..2847ad9687bc 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergGalleryUploadProcessor.swift +++ b/Modules/Sources/GutenbergProcessors/GutenbergGalleryUploadProcessor.swift @@ -1,7 +1,7 @@ import Foundation import SwiftSoup -class GutenbergGalleryUploadProcessor: GutenbergProcessor { +public class GutenbergGalleryUploadProcessor: GutenbergProcessor { let mediaUploadID: Int32 let remoteURLString: String @@ -12,7 +12,7 @@ class GutenbergGalleryUploadProcessor: GutenbergProcessor { static let imgClassIDPrefixAttribute = "wp-image-" - init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String, mediaLink: String) { + public init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String, mediaLink: String) { self.mediaUploadID = mediaUploadID self.serverMediaID = serverMediaID self.remoteURLString = remoteURLString @@ -139,7 +139,7 @@ class GutenbergGalleryUploadProcessor: GutenbergProcessor { } } - func process(_ blocks: [GutenbergParsedBlock]) { + public func process(_ blocks: [GutenbergParsedBlock]) { processGalleryBlocks(blocks) } } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergImgUploadProcessor.swift b/Modules/Sources/GutenbergProcessors/GutenbergImgUploadProcessor.swift similarity index 91% rename from WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergImgUploadProcessor.swift rename to Modules/Sources/GutenbergProcessors/GutenbergImgUploadProcessor.swift index 88b5e84e3054..593c9a8b5392 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergImgUploadProcessor.swift +++ b/Modules/Sources/GutenbergProcessors/GutenbergImgUploadProcessor.swift @@ -1,13 +1,13 @@ import Foundation -class GutenbergImgUploadProcessor: GutenbergProcessor { +public class GutenbergImgUploadProcessor: GutenbergProcessor { let mediaUploadID: Int32 let remoteURLString: String let serverMediaID: Int static let imgClassIDPrefixAttribute = "wp-image-" - init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String) { + public init(mediaUploadID: Int32, serverMediaID: Int, remoteURLString: String) { self.mediaUploadID = mediaUploadID self.serverMediaID = serverMediaID self.remoteURLString = remoteURLString @@ -63,7 +63,7 @@ class GutenbergImgUploadProcessor: GutenbergProcessor { } } - func process(_ blocks: [GutenbergParsedBlock]) { + public func process(_ blocks: [GutenbergParsedBlock]) { processImageBlocks(blocks) processMediaTextBlocks(blocks) } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergProcessor.swift b/Modules/Sources/GutenbergProcessors/GutenbergProcessor.swift similarity index 100% rename from WordPress/Classes/ViewRelated/Gutenberg/Processors/GutenbergProcessor.swift rename to Modules/Sources/GutenbergProcessors/GutenbergProcessor.swift diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift b/Modules/Tests/GutenbergProcessorsTests/GutenbergContentParserTests.swift similarity index 99% rename from Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift rename to Modules/Tests/GutenbergProcessorsTests/GutenbergContentParserTests.swift index f2633f40ec2b..53c8390387d4 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergContentParser.swift +++ b/Modules/Tests/GutenbergProcessorsTests/GutenbergContentParserTests.swift @@ -1,5 +1,6 @@ import XCTest -@testable import WordPress +@testable import GutenbergProcessors +import SwiftSoup class GutenbergContentParserTests: XCTestCase { let singleBlock = """ diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergFileUploadProcessorTests.swift b/Modules/Tests/GutenbergProcessorsTests/GutenbergFileUploadProcessorTests.swift similarity index 97% rename from Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergFileUploadProcessorTests.swift rename to Modules/Tests/GutenbergProcessorsTests/GutenbergFileUploadProcessorTests.swift index e1c06c3503ff..fe7ea472cc21 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergFileUploadProcessorTests.swift +++ b/Modules/Tests/GutenbergProcessorsTests/GutenbergFileUploadProcessorTests.swift @@ -1,5 +1,5 @@ import XCTest -@testable import WordPress +@testable import GutenbergProcessors class GutenbergFileUploadProcessorTests: XCTestCase { diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergGalleryUploadProcessorTests.swift b/Modules/Tests/GutenbergProcessorsTests/GutenbergGalleryUploadProcessorTests.swift similarity index 99% rename from Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergGalleryUploadProcessorTests.swift rename to Modules/Tests/GutenbergProcessorsTests/GutenbergGalleryUploadProcessorTests.swift index 6a856b43792a..4ebda9f71df6 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergGalleryUploadProcessorTests.swift +++ b/Modules/Tests/GutenbergProcessorsTests/GutenbergGalleryUploadProcessorTests.swift @@ -1,5 +1,5 @@ import XCTest -@testable import WordPress +@testable import GutenbergProcessors class GutenbergGalleryUploadProcessorTests: XCTestCase { diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergImgUploadProcessorTests.swift b/Modules/Tests/GutenbergProcessorsTests/GutenbergImgUploadProcessorTests.swift similarity index 98% rename from Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergImgUploadProcessorTests.swift rename to Modules/Tests/GutenbergProcessorsTests/GutenbergImgUploadProcessorTests.swift index bdd1f21ac9be..9474aba34642 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergImgUploadProcessorTests.swift +++ b/Modules/Tests/GutenbergProcessorsTests/GutenbergImgUploadProcessorTests.swift @@ -1,5 +1,5 @@ import XCTest -@testable import WordPress +@testable import GutenbergProcessors class GutenbergImgUploadProcessorTests: XCTestCase { diff --git a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergRefactoredGalleryUploadProcessorTests.swift b/Modules/Tests/GutenbergProcessorsTests/GutenbergRefactoredGalleryUploadProcessorTests.swift similarity index 99% rename from Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergRefactoredGalleryUploadProcessorTests.swift rename to Modules/Tests/GutenbergProcessorsTests/GutenbergRefactoredGalleryUploadProcessorTests.swift index bf0b71a75857..1d2548b468cb 100644 --- a/Tests/KeystoneTests/Tests/Features/Gutenberg/GutenbergRefactoredGalleryUploadProcessorTests.swift +++ b/Modules/Tests/GutenbergProcessorsTests/GutenbergRefactoredGalleryUploadProcessorTests.swift @@ -1,5 +1,5 @@ import XCTest -@testable import WordPress +@testable import GutenbergProcessors class GutenbergRefactoredGalleryUploadProcessorTests: XCTestCase { struct ImageUploadJob { diff --git a/Package.swift b/Package.swift index f9ff5b633880..6a9397e7670e 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,8 @@ let package = Package( .iOS(.v17) ], dependencies: [ - .package(path: "Modules") + .package(path: "Modules"), + .package(url: "https://github.com/scinfu/SwiftSoup", exact: "2.13.6") ], targets: [ .testTarget( @@ -46,6 +47,15 @@ let package = Package( "WPUserAgentTests.swift" ], swiftSettings: [.swiftLanguageMode(.v5)] + ), + .testTarget( + name: "GutenbergProcessorsTests", + dependencies: [ + .product(name: "GutenbergProcessors", package: "Modules"), + .product(name: "SwiftSoup", package: "SwiftSoup") + ], + path: "Modules/Tests/GutenbergProcessorsTests", + swiftSettings: [.swiftLanguageMode(.v5)] ) ] ) diff --git a/Tests/KeystoneTests/WordPressUnitTests.xctestplan b/Tests/KeystoneTests/WordPressUnitTests.xctestplan index f172c2102e2f..68e2e6e2e24f 100644 --- a/Tests/KeystoneTests/WordPressUnitTests.xctestplan +++ b/Tests/KeystoneTests/WordPressUnitTests.xctestplan @@ -41,6 +41,13 @@ "name" : "DesignSystemTests" } }, + { + "target" : { + "containerPath" : "container:..\/Modules", + "identifier" : "GutenbergProcessorsTests", + "name" : "GutenbergProcessorsTests" + } + }, { "target" : { "containerPath" : "container:..\/Modules", diff --git a/WordPress/Classes/Services/PostCoordinator.swift b/WordPress/Classes/Services/PostCoordinator.swift index b464bec59047..924a3ff936f2 100644 --- a/WordPress/Classes/Services/PostCoordinator.swift +++ b/WordPress/Classes/Services/PostCoordinator.swift @@ -3,6 +3,7 @@ import Aztec import AztecExtensions import Combine import Foundation +import GutenbergProcessors import WordPressData import WordPressKit import WordPressFlux