Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 02bcec0

Browse files
authored
Merge pull request #166 from wordpress-mobile/issue/post-editor-settings
Implement POST editor settings
2 parents 64a002f + 34948bd commit 02bcec0

File tree

5 files changed

+304
-1
lines changed

5 files changed

+304
-1
lines changed

WordPressKit.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "WordPressKit"
3-
s.version = "4.3.0-beta.4"
3+
s.version = "4.3.0-beta.5"
44
s.summary = "WordPressKit offers a clean and simple WordPress.com and WordPress.org API."
55

66
s.description = <<-DESC

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,13 @@
277277
74FC6F411F191C1D00112505 /* notifications-last-seen.json in Resources */ = {isa = PBXBuildFile; fileRef = 74FC6F3D1F191C1D00112505 /* notifications-last-seen.json */; };
278278
74FC6F421F191C1D00112505 /* notifications-load-hash.json in Resources */ = {isa = PBXBuildFile; fileRef = 74FC6F3E1F191C1D00112505 /* notifications-load-hash.json */; };
279279
74FC6F431F191C1D00112505 /* notifications-load-all.json in Resources */ = {isa = PBXBuildFile; fileRef = 74FC6F3F1F191C1D00112505 /* notifications-load-all.json */; };
280+
7E0D64FF22D855700092AD10 /* EditorServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E0D64FE22D855700092AD10 /* EditorServiceRemote.swift */; };
280281
7E3E7A4520E443060075D159 /* NSAttributedString+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3E7A4420E443050075D159 /* NSAttributedString+extensions.swift */; };
281282
7E3E7A4820E443370075D159 /* NSMutableAttributedString+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3E7A4720E443370075D159 /* NSMutableAttributedString+extensions.swift */; };
282283
7E3E7A4A20E443890075D159 /* Scanner+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3E7A4920E443890075D159 /* Scanner+extensions.swift */; };
283284
7E3E7A4C20E443AA0075D159 /* NSMutableParagraphStyle+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3E7A4B20E443AA0075D159 /* NSMutableParagraphStyle+extensions.swift */; };
285+
7EC60EBE22DC4F9000FB0336 /* EditorServiceRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC60EBD22DC4F9000FB0336 /* EditorServiceRemoteTests.swift */; };
286+
7EC60EC022DC5D7C00FB0336 /* EditorSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC60EBF22DC5D7C00FB0336 /* EditorSettings.swift */; };
284287
8236EB4D2024B9F8007C7CF9 /* RemoteBlogJetpackModulesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8236EB4C2024B9F8007C7CF9 /* RemoteBlogJetpackModulesSettings.swift */; };
285288
826016F11F9FA13A00533B6C /* ActivityServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826016F01F9FA13A00533B6C /* ActivityServiceRemote.swift */; };
286289
826016F31F9FA17B00533B6C /* Activity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826016F21F9FA17B00533B6C /* Activity.swift */; };
@@ -792,10 +795,13 @@
792795
74FC6F3D1F191C1D00112505 /* notifications-last-seen.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-last-seen.json"; sourceTree = "<group>"; };
793796
74FC6F3E1F191C1D00112505 /* notifications-load-hash.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-load-hash.json"; sourceTree = "<group>"; };
794797
74FC6F3F1F191C1D00112505 /* notifications-load-all.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "notifications-load-all.json"; sourceTree = "<group>"; };
798+
7E0D64FE22D855700092AD10 /* EditorServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorServiceRemote.swift; sourceTree = "<group>"; };
795799
7E3E7A4420E443050075D159 /* NSAttributedString+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+extensions.swift"; sourceTree = "<group>"; };
796800
7E3E7A4720E443370075D159 /* NSMutableAttributedString+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+extensions.swift"; sourceTree = "<group>"; };
797801
7E3E7A4920E443890075D159 /* Scanner+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Scanner+extensions.swift"; sourceTree = "<group>"; };
798802
7E3E7A4B20E443AA0075D159 /* NSMutableParagraphStyle+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableParagraphStyle+extensions.swift"; sourceTree = "<group>"; };
803+
7EC60EBD22DC4F9000FB0336 /* EditorServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorServiceRemoteTests.swift; sourceTree = "<group>"; };
804+
7EC60EBF22DC5D7C00FB0336 /* EditorSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorSettings.swift; sourceTree = "<group>"; };
799805
8236EB4C2024B9F8007C7CF9 /* RemoteBlogJetpackModulesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteBlogJetpackModulesSettings.swift; sourceTree = "<group>"; };
800806
826016F01F9FA13A00533B6C /* ActivityServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityServiceRemote.swift; sourceTree = "<group>"; };
801807
826016F21F9FA17B00533B6C /* Activity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Activity.swift; sourceTree = "<group>"; };
@@ -1239,6 +1245,14 @@
12391245
name = Extensions;
12401246
sourceTree = "<group>";
12411247
};
1248+
7EC60EBC22DC4F5A00FB0336 /* Editor */ = {
1249+
isa = PBXGroup;
1250+
children = (
1251+
7EC60EBD22DC4F9000FB0336 /* EditorServiceRemoteTests.swift */,
1252+
);
1253+
name = Editor;
1254+
sourceTree = "<group>";
1255+
};
12421256
826016FE1F9FD59400533B6C /* Activity */ = {
12431257
isa = PBXGroup;
12441258
children = (
@@ -1293,6 +1307,7 @@
12931307
826016FE1F9FD59400533B6C /* Activity */,
12941308
74B5F0DF1EF82AAB00B411E7 /* Blog */,
12951309
74585B911F0D520700E7E667 /* Domains */,
1310+
7EC60EBC22DC4F5A00FB0336 /* Editor */,
12961311
9A2D0B29225E0DF7009E585F /* Jetpack */,
12971312
74FA25F81F1FDA240044BC54 /* Media */,
12981313
74D97CB91F1CF6E200AC49B7 /* Menu */,
@@ -1418,6 +1433,7 @@
14181433
74A44DC81F13C533006CD8F4 /* NotificationSettingsServiceRemote.swift */,
14191434
74A44DC91F13C533006CD8F4 /* NotificationSyncServiceRemote.swift */,
14201435
74D67F051F1528470010C5ED /* PeopleServiceRemote.swift */,
1436+
7E0D64FE22D855700092AD10 /* EditorServiceRemote.swift */,
14211437
7433BBFF1EFC4505002D9E92 /* PlanServiceRemote.swift */,
14221438
439A44D72107C85E00795ED7 /* PlanServiceRemote_ApiVersion1_3.swift */,
14231439
439A44D52107C66A00795ED7 /* JSONDecoderExtension.swift */,
@@ -1484,6 +1500,7 @@
14841500
9AF4F2FD2183345D00570E4B /* Revisions */,
14851501
930F52B91ECF8A44002F921B /* Stats */,
14861502
7403A3011EF0726E00DED7DC /* AccountSettings.swift */,
1503+
7EC60EBF22DC5D7C00FB0336 /* EditorSettings.swift */,
14871504
74E229591F1E77290085F7F2 /* KeyringConnection.swift */,
14881505
74E2295A1F1E77290085F7F2 /* KeyringConnectionExternalUser.swift */,
14891506
40247DFB2120E69600AE1C3C /* AutomatedTransferStatus.swift */,
@@ -2432,6 +2449,7 @@
24322449
93F50A381F226B9300B5BEBA /* WordPressComServiceRemote.m in Sources */,
24332450
9F4E52002088E38200424676 /* ObjectValidation.swift in Sources */,
24342451
7430C9B81F1927C50051B8E6 /* RemoteReaderTopic.m in Sources */,
2452+
7EC60EC022DC5D7C00FB0336 /* EditorSettings.swift in Sources */,
24352453
7403A3021EF0726E00DED7DC /* AccountSettings.swift in Sources */,
24362454
40E7FEA9220FA4060032834E /* StatsEmailFollowersInsight.swift in Sources */,
24372455
404057DA221C9D560060250C /* StatsTopReferrersTimeIntervalData.swift in Sources */,
@@ -2472,6 +2490,7 @@
24722490
436D56352118D85800CEAA33 /* Country.swift in Sources */,
24732491
74A44DCB1F13C533006CD8F4 /* NotificationSettingsServiceRemote.swift in Sources */,
24742492
404057CE221C38130060250C /* StatsTopVideosTimeIntervalData.swift in Sources */,
2493+
7E0D64FF22D855700092AD10 /* EditorServiceRemote.swift in Sources */,
24752494
E182BF6A1FD961810001D850 /* Endpoint.swift in Sources */,
24762495
9AF4F2FF2183346B00570E4B /* RemoteRevision.swift in Sources */,
24772496
74BA04F41F06DC0A00ED5CD8 /* CommentServiceRemoteREST.m in Sources */,
@@ -2581,6 +2600,7 @@
25812600
74C473AF1EF2F7D1009918F2 /* SiteManagementServiceRemoteTests.swift in Sources */,
25822601
74A44DD51F13C6D8006CD8F4 /* PushAuthenticationServiceRemoteTests.swift in Sources */,
25832602
731BA83621DECD61000FDFCD /* SiteCreationRequestEncodingTests.swift in Sources */,
2603+
7EC60EBE22DC4F9000FB0336 /* EditorServiceRemoteTests.swift in Sources */,
25842604
931924241F1662FA0069CBCC /* JSONLoader.swift in Sources */,
25852605
93AC8EE21ED32FD000900F5A /* WPStatsServiceRemoteTests.m in Sources */,
25862606
E1787DB2200E5690004CB3AF /* TimeZoneServiceRemoteTests.swift in Sources */,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
import WordPressShared
3+
4+
public class EditorServiceRemote: ServiceRemoteWordPressComREST {
5+
public func postDesignateMobileEditor(_ siteID: Int, editor: EditorSettings.Mobile, success: @escaping (EditorSettings) -> Void, failure: @escaping (Error) -> Void) {
6+
let endpoint = "sites/\(siteID)/gutenberg?platform=mobile&editor=\(editor.rawValue)"
7+
let path = self.path(forEndpoint: endpoint, withVersion: ._2_0)
8+
9+
wordPressComRestApi.POST(path, parameters: nil, success: { (responseObject, httpResponse) in
10+
do {
11+
let settings = try EditorSettings(with: responseObject)
12+
success(settings)
13+
} catch {
14+
failure(error)
15+
}
16+
}) { (error, httpError) in
17+
failure(error)
18+
}
19+
}
20+
21+
public func getEditorSettings(_ siteID: Int, success: @escaping (EditorSettings) -> Void, failure: @escaping (Error) -> Void) {
22+
let endpoint = "sites/\(siteID)/gutenberg"
23+
let path = self.path(forEndpoint: endpoint, withVersion: ._2_0)
24+
25+
wordPressComRestApi.GET(path, parameters: nil, success: { (responseObject, httpResponse) in
26+
do {
27+
let settings = try EditorSettings(with: responseObject)
28+
success(settings)
29+
} catch {
30+
failure(error)
31+
}
32+
}) { (error, httpError) in
33+
failure(error)
34+
}
35+
}
36+
}

WordPressKit/EditorSettings.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
private struct RemoteEditorSettings: Codable {
2+
let editorMobile: String
3+
let editorWeb: String
4+
}
5+
6+
public struct EditorSettings {
7+
public enum Error: Swift.Error {
8+
case decodingFailed
9+
}
10+
11+
/// Editor choosen by the user to be used on Mobile
12+
///
13+
/// - gutenberg: The block editor
14+
/// - aztec: The mobile "classic" editor
15+
/// - notSet: The user has never saved they preference on remote
16+
public enum Mobile: String {
17+
case gutenberg
18+
case aztec
19+
case notSet = ""
20+
}
21+
22+
23+
/// Editor choosen by the user to be used on Web
24+
///
25+
/// - classic: The classic editor
26+
/// - gutenberg: The block editor
27+
public enum Web: String {
28+
case classic
29+
case gutenberg
30+
}
31+
32+
public let mobile: Mobile
33+
public let web: Web
34+
}
35+
36+
extension EditorSettings {
37+
init(with response: AnyObject) throws {
38+
guard let response = response as? [String: AnyObject] else {
39+
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorBadServerResponse, userInfo: nil)
40+
}
41+
42+
let data = try JSONSerialization.data(withJSONObject: response, options: .prettyPrinted)
43+
let editorPreferenesRemote = try JSONDecoder.apiDecoder.decode(RemoteEditorSettings.self, from: data)
44+
try self.init(with: editorPreferenesRemote)
45+
}
46+
47+
private init(with remote: RemoteEditorSettings) throws {
48+
guard
49+
let mobile = Mobile(rawValue: remote.editorMobile),
50+
let web = Web(rawValue: remote.editorWeb)
51+
else {
52+
throw Error.decodingFailed
53+
}
54+
self = EditorSettings(mobile: mobile, web: web)
55+
}
56+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import XCTest
2+
@testable import WordPressKit
3+
4+
class EditorServiceRemoteTests: XCTestCase {
5+
6+
let mockRemoteApi = MockWordPressComRestApi()
7+
var editorServiceRemote: EditorServiceRemote!
8+
let siteID = 99999
9+
10+
override func setUp() {
11+
super.setUp()
12+
editorServiceRemote = EditorServiceRemote(wordPressComRestApi: mockRemoteApi)
13+
}
14+
15+
// MARK: - POST tests
16+
17+
func testPostDesignateMobileEditorPostMethodIsCalled() {
18+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .gutenberg, success: { _ in }, failure: { _ in })
19+
XCTAssertTrue(mockRemoteApi.postMethodCalled)
20+
}
21+
22+
func testPostDesignateMobileEditorSuccessSettingGutenberg() {
23+
let expec = expectation(description: "success")
24+
let response = mockResponse(forMobile: .gutenberg, andWeb: .gutenberg)
25+
26+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .gutenberg, success: { editor in
27+
XCTAssertEqual(editor.mobile, .gutenberg)
28+
XCTAssertEqual(editor.web, .gutenberg)
29+
expec.fulfill()
30+
}) { (error) in
31+
XCTFail("This call should succeed. Error: \(error)")
32+
expec.fulfill()
33+
}
34+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
35+
36+
wait(for: [expec], timeout: 0.1)
37+
}
38+
39+
func testPostDesignateMobileEditorSuccessSettingAztec() {
40+
let expec = expectation(description: "success")
41+
let response = mockResponse(forMobile: .aztec, andWeb: .classic)
42+
43+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .aztec, success: { editor in
44+
XCTAssertEqual(editor.mobile, .aztec)
45+
XCTAssertEqual(editor.web, .classic)
46+
expec.fulfill()
47+
}) { (error) in
48+
XCTFail("This call should succeed. Error: \(error)")
49+
expec.fulfill()
50+
}
51+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
52+
53+
wait(for: [expec], timeout: 0.1)
54+
}
55+
56+
func testPostDesignateMobileEditorDoesNotCrashWithBadKeyResponse() {
57+
let expec = expectation(description: "success")
58+
let response: [String: String] = [
59+
"editor_mobile_bad": "gutenberg",
60+
"editor_web": "gutenberg",
61+
]
62+
63+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .gutenberg, success: { editor in
64+
XCTFail("This should fail")
65+
expec.fulfill()
66+
}) { (error) in
67+
let nsError = error as NSError
68+
XCTAssertEqual(nsError.code, NSCoderValueNotFoundError)
69+
expec.fulfill()
70+
}
71+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
72+
73+
wait(for: [expec], timeout: 0.1)
74+
}
75+
76+
func testPostDesignateMobileEditorThrowsErrorWithBadValueResponse() {
77+
let expec = expectation(description: "success")
78+
let response: [String: String] = [
79+
"editor_mobile": "guten_BORG",
80+
"editor_web": "guten_WRONG",
81+
]
82+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .gutenberg, success: { editor in
83+
XCTFail("This should throw an error")
84+
expec.fulfill()
85+
}) { (error) in
86+
XCTAssertEqual(error as NSError, EditorSettings.Error.decodingFailed as NSError)
87+
expec.fulfill()
88+
}
89+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
90+
91+
wait(for: [expec], timeout: 0.1)
92+
}
93+
94+
func testPostDesignateMobileEditorError() {
95+
let expec = expectation(description: "success")
96+
let errorExpec = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)
97+
editorServiceRemote.postDesignateMobileEditor(siteID, editor: .gutenberg, success: { _ in
98+
XCTFail("This call should error")
99+
expec.fulfill()
100+
}) { (error) in
101+
XCTAssertEqual(error as NSError, errorExpec)
102+
expec.fulfill()
103+
}
104+
mockRemoteApi.failureBlockPassedIn?(errorExpec, nil)
105+
XCTAssertTrue(mockRemoteApi.postMethodCalled)
106+
107+
wait(for: [expec], timeout: 0.1)
108+
}
109+
110+
// MARK: - GET tests
111+
112+
func testGetEditorSettingsGutenberg() {
113+
let expec = expectation(description: "success")
114+
let response = mockResponse(forMobile: .gutenberg, andWeb: .gutenberg)
115+
116+
editorServiceRemote.getEditorSettings(siteID, success: { (editor) in
117+
XCTAssertEqual(editor.mobile, .gutenberg)
118+
XCTAssertEqual(editor.web, .gutenberg)
119+
expec.fulfill()
120+
}) { (error) in
121+
XCTFail("This call should succeed. Error: \(error)")
122+
expec.fulfill()
123+
}
124+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
125+
XCTAssertTrue(mockRemoteApi.getMethodCalled)
126+
127+
wait(for: [expec], timeout: 0.1)
128+
}
129+
130+
func testGetEditorSettingsNotSetForMobile() {
131+
let expec = expectation(description: "success")
132+
let response = mockResponse(forMobile: .notSet, andWeb: .gutenberg)
133+
134+
editorServiceRemote.getEditorSettings(siteID, success: { (editor) in
135+
XCTAssertEqual(editor.mobile, .notSet)
136+
XCTAssertEqual(editor.web, .gutenberg)
137+
expec.fulfill()
138+
}) { (error) in
139+
XCTFail("This call should succeed. Error: \(error)")
140+
expec.fulfill()
141+
}
142+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
143+
XCTAssertTrue(mockRemoteApi.getMethodCalled)
144+
145+
wait(for: [expec], timeout: 0.1)
146+
}
147+
148+
func testGetEditorSettingsClassic() {
149+
let expec = expectation(description: "success")
150+
let response = mockResponse(forMobile: .aztec, andWeb: .classic)
151+
152+
editorServiceRemote.getEditorSettings(siteID, success: { (editor) in
153+
XCTAssertEqual(editor.mobile, .aztec)
154+
XCTAssertEqual(editor.web, .classic)
155+
expec.fulfill()
156+
}) { (error) in
157+
XCTFail("This call should succeed. Error: \(error)")
158+
expec.fulfill()
159+
}
160+
mockRemoteApi.successBlockPassedIn?(response as AnyObject, HTTPURLResponse())
161+
XCTAssertTrue(mockRemoteApi.getMethodCalled)
162+
163+
wait(for: [expec], timeout: 0.1)
164+
}
165+
166+
func testGetEditorSettingsFailure() {
167+
let expec = expectation(description: "success")
168+
let errorExpec = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)
169+
170+
editorServiceRemote.getEditorSettings(siteID, success: { (editor) in
171+
XCTFail("This call should error")
172+
expec.fulfill()
173+
}) { (error) in
174+
XCTAssertEqual(error as NSError, errorExpec)
175+
expec.fulfill()
176+
}
177+
mockRemoteApi.failureBlockPassedIn?(errorExpec, nil)
178+
XCTAssertTrue(mockRemoteApi.getMethodCalled)
179+
180+
wait(for: [expec], timeout: 0.1)
181+
}
182+
}
183+
184+
extension EditorServiceRemoteTests {
185+
func mockResponse(forMobile mobile: EditorSettings.Mobile, andWeb web: EditorSettings.Web) -> [String: String] {
186+
return [
187+
"editor_mobile": mobile.rawValue,
188+
"editor_web": web.rawValue,
189+
]
190+
}
191+
}

0 commit comments

Comments
 (0)