oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import XCTest
2import UIKit
3final class NativeUITests: XCTestCase {
4 @MainActor func testInputRuleReversalAndMultilinePersistence() async throws {
5 continueAfterFailure = false
6 let app = XCUIApplication()
7 app.launch()
8 defer { app.terminate() }
9 let create = app.buttons["docs.create"]
10 XCTAssertTrue(create.waitForExistence(timeout: 15))
11 create.tap()
12 let title = app.textFields["Title"]
13 XCTAssertTrue(title.waitForExistence(timeout: 5))
14 title.tap(); title.typeText("Native input rule verification")
15 app.buttons["docs.create.submit"].tap()
16 let editor = app.textViews["docs.editor"]
17 XCTAssertTrue(editor.waitForExistence(timeout: 15))
18 editor.tap(); editor.typeText("- ")
19 let transformed = editor.value as? String ?? ""
20 XCTAssertFalse(transformed.contains("-"), "The native input rule must remove its typed prefix")
21 editor.typeText(XCUIKeyboardKey.delete.rawValue)
22 let reverted = editor.value as? String ?? ""
23 print("Synthetic rule transformed: \(String(reflecting: transformed)); reverted: \(String(reflecting: reverted))")
24 let reversal = XCTAttachment(string: "Synthetic transformed value: \(String(reflecting: transformed))\nSynthetic reverted value: \(String(reflecting: reverted))")
25 reversal.name = "Native input rule reversal"; reversal.lifetime = .keepAlways; add(reversal)
26 XCTAssertTrue(reverted.contains("- "), "Backspace must restore the complete input-rule prefix")
27 editor.typeText("Alpha\nBeta")
28 let displayed = editor.value as? String ?? ""
29 XCTAssertTrue(displayed.contains("Alpha"))
30 XCTAssertTrue(displayed.contains("Beta"))
31 struct Config: Decodable { struct Credential: Decodable { let name: String; let value: String }; let origin: String; let credential: Credential }
32 let config = try JSONDecoder().decode(Config.self, from: Data(contentsOf: URL.documentsDirectory.appendingPathComponent("review.json")))
33 func fetch(_ path: String) async throws -> Data {
34 var request = URLRequest(url: URL(string: config.origin + path)!)
35 request.setValue("\(config.credential.name)=\(config.credential.value)", forHTTPHeaderField: "Cookie")
36 request.setValue("application/json", forHTTPHeaderField: "Accept")
37 let (data, response) = try await URLSession.shared.data(for: request)
38 XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200)
39 return data
40 }
41 struct Listed: Decodable { struct Item: Decodable { let sqid: String; let title: String }; let documents: [Item] }
42 let list = try JSONDecoder().decode(Listed.self, from: await fetch("/"))
43 let item = try XCTUnwrap(list.documents.first { $0.title == "Native input rule verification" })
44 var saved = ""
45 for _ in 0..<50 {
46 saved = String(decoding: try await fetch("/d/\(item.sqid)/export.md"), as: UTF8.self)
47 if saved.contains("Alpha") && saved.contains("Beta") { break }
48 try await Task.sleep(for: .milliseconds(100))
49 }
50 let attachment = XCTAttachment(string: "Synthetic Markdown: \(String(reflecting: saved))\nSynthetic editor value: \(String(reflecting: displayed))")
51 attachment.name = "Native multiline persistence"; attachment.lifetime = .keepAlways; add(attachment)
52 let prefix = "# Native input rule verification\n\n"
53 XCTAssertTrue(saved.hasPrefix(prefix))
54 let body = String(saved.dropFirst(prefix.count)).replacingOccurrences(of: "\\-", with: "-")
55 print("Synthetic rule persisted Markdown: \(String(reflecting: saved)); displayed: \(String(reflecting: displayed))")
56 XCTAssertEqual(body.split(whereSeparator: { $0.isWhitespace }).joined(separator: " "), "- Alpha Beta", "Reversal and multiline typing must preserve every character in the real CRDT store")
57 let screenshot = XCTAttachment(screenshot: app.screenshot())
58 screenshot.name = "Docs native input rule and multiline editor"; screenshot.lifetime = .keepAlways; add(screenshot)
59 }
60
61 @MainActor func testKeyboardJumpSearchOpensDocument() throws {
62 // Apple's XCUIElement overview supports keyboard injection on iPadOS/macOS,
63 // while iPhone automation supports gestures. On iPhone this inserts literal K.
64 try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
65 "XCTest hardware keyboard injection requires iPadOS; use the jump-button case on iPhone")
66 try exerciseJump(keyboard: true)
67 }
68 @MainActor func testJumpButtonSearchOpensDocument() throws { try exerciseJump(keyboard: false) }
69 @MainActor func testJumpButtonAndHardwareReturnOpensDocument() throws {
70 try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
71 "XCTest hardware keyboard injection requires iPadOS")
72 try exerciseJump(keyboard: false, hardwareReturn: true)
73 }
74 @MainActor func testRegisteredCommandAndSoftwareSubmitOpenDocument() throws {
75 try exerciseJump(keyboard: false, responderProbe: true)
76 }
77 @MainActor private func exerciseJump(keyboard: Bool, hardwareReturn: Bool = false, responderProbe: Bool = false) throws {
78 continueAfterFailure = false
79 let app = XCUIApplication()
80 if responderProbe { app.launchEnvironment["DOCS_REVIEW_COMMAND_PROBE"] = "1" }
81 app.launch()
82 defer { app.terminate() }
83 let first = app.buttons["Native review document"].firstMatch
84 XCTAssertTrue(first.waitForExistence(timeout: 15))
85 first.tap()
86 let editor = app.textViews["docs.editor"]
87 XCTAssertTrue(editor.waitForExistence(timeout: 15))
88 editor.tap()
89 if responderProbe { /* Review target dispatches the registered action after focus. */ }
90 else if keyboard { editor.typeKey("k", modifierFlags: .command) }
91 else { app.buttons["docs.jump"].firstMatch.tap() }
92 print("Synthetic jump shortcut hierarchy: \(app.debugDescription)")
93 try app.screenshot().pngRepresentation.write(to: URL.documentsDirectory.appendingPathComponent("jump-shortcut.review.png"), options: .atomic)
94 let query = app.textFields["docs.jump.query"]
95 XCTAssertTrue(query.waitForExistence(timeout: 5), "Command-K must open native document search from the editor")
96 query.typeText("Second review")
97 let result = app.buttons.containing(.staticText, identifier: "Second review document").firstMatch
98 XCTAssertTrue(result.waitForExistence(timeout: 10), "Native search must show the existing server-ranked result")
99 if responderProbe { query.typeText("\n") }
100 else if keyboard || hardwareReturn { query.typeKey(XCUIKeyboardKey.return, modifierFlags: []) }
101 else { result.tap() }
102 print("Synthetic jump return hierarchy: \(app.debugDescription)")
103 try app.screenshot().pngRepresentation.write(to: URL.documentsDirectory.appendingPathComponent("jump-return.review.png"), options: .atomic)
104 let opened = app.navigationBars["Second review document"].waitForExistence(timeout: 10)
105 if !opened {
106 print("Synthetic jump result hierarchy: \(app.debugDescription)")
107 let diagnostic = XCTAttachment(screenshot: app.screenshot())
108 diagnostic.name = "Docs native jump failure"; diagnostic.lifetime = .keepAlways; add(diagnostic)
109 }
110 XCTAssertTrue(opened, "Enter must open the selected native search result")
111 XCTAssertTrue(editor.waitForExistence(timeout: 10))
112 let screenshot = XCTAttachment(screenshot: app.screenshot())
113 screenshot.name = "Docs native keyboard jump result"; screenshot.lifetime = .keepAlways; add(screenshot)
114 }
115
116 @MainActor func testExportPDFAndMarkdownDestinationsAndCancellation() throws {
117 continueAfterFailure = false
118 let app = XCUIApplication()
119 app.launch()
120 defer { app.terminate() }
121 let document = app.buttons["Native review document"].firstMatch
122 XCTAssertTrue(document.waitForExistence(timeout: 15))
123 document.tap()
124 XCTAssertTrue(app.textViews["docs.editor"].waitForExistence(timeout: 15))
125 let export = app.buttons["Export and print"]
126 for format in ["PDF", "Markdown"] {
127 XCTAssertTrue(export.waitForExistence(timeout: 5))
128 export.tap()
129 app.buttons["Download as " + format].tap()
130 let cancel = app.buttons["Cancel"]
131 XCTAssertTrue(cancel.waitForExistence(timeout: 20), "The native destination picker must open for " + format)
132 let screenshot = XCTAttachment(screenshot: app.screenshot())
133 screenshot.name = "Docs native " + format + " export destination"; screenshot.lifetime = .keepAlways; add(screenshot)
134 let hierarchy = XCTAttachment(string: app.debugDescription)
135 hierarchy.name = "Synthetic " + format + " export picker hierarchy"; hierarchy.lifetime = .keepAlways; add(hierarchy)
136 cancel.tap()
137 XCTAssertTrue(export.waitForExistence(timeout: 10))
138 }
139 }
140
141 @MainActor func testCreateTypeFormatAndPersistDocument() async throws {
142 continueAfterFailure = false
143 let app = XCUIApplication()
144 app.launch()
145 defer { app.terminate() }
146 XCTAssertFalse(app.staticTexts["fixture.missing"].exists)
147 let create = app.buttons["docs.create"]
148 guard create.waitForExistence(timeout: 10) else { XCTFail("Fixture document list did not load"); throw NSError(domain: "NativeReview", code: 1) }
149 create.tap()
150 let title = app.textFields["Title"]
151 XCTAssertTrue(title.waitForExistence(timeout: 3))
152 title.tap(); title.typeText("Native editing verification")
153 app.buttons["docs.create.submit"].tap()
154 let editor = app.textViews["docs.editor"]
155 guard editor.waitForExistence(timeout: 10) else { XCTFail("Native editor did not open"); throw NSError(domain: "NativeReview", code: 2) }
156 editor.tap(); editor.typeText("Native text")
157 XCTAssertTrue((editor.value as? String)?.contains("Native text") == true)
158 let bold = app.buttons["Bold"].firstMatch
159 XCTAssertTrue(bold.exists)
160 bold.tap()
161 // The toolbar retains the editor's first responder. Tapping the centre
162 // of a whole-page text view here would deliberately reposition the caret.
163 editor.typeText(" bold")
164 let displayed = editor.value as? String ?? "<missing editor value>"
165 if !displayed.contains("bold") {
166 let diagnostic = XCTAttachment(string: "Synthetic editor value: \(String(reflecting: displayed))\n\(app.debugDescription)")
167 diagnostic.name = "Synthetic native input failure"; diagnostic.lifetime = .keepAlways; add(diagnostic)
168 let failedScreenshot = XCTAttachment(screenshot: app.screenshot())
169 failedScreenshot.name = "Docs native input failure"; failedScreenshot.lifetime = .keepAlways; add(failedScreenshot)
170 }
171 XCTAssertTrue(displayed.contains("bold"), "Formatted input must remain visible: \(String(reflecting: displayed))")
172 struct Config: Decodable { struct Credential: Decodable { let name: String; let value: String }; let origin: String; let credential: Credential }
173 let config = try JSONDecoder().decode(Config.self, from: Data(contentsOf: URL.documentsDirectory.appendingPathComponent("review.json")))
174 func fetch(_ path: String) async throws -> Data {
175 var request = URLRequest(url: URL(string: config.origin + path)!)
176 request.setValue("\(config.credential.name)=\(config.credential.value)", forHTTPHeaderField: "Cookie")
177 request.setValue("application/json", forHTTPHeaderField: "Accept")
178 let (data, response) = try await URLSession.shared.data(for: request)
179 XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200)
180 return data
181 }
182 struct Listed: Decodable { struct Item: Decodable { let sqid: String; let title: String }; let documents: [Item] }
183 let list = try JSONDecoder().decode(Listed.self, from: await fetch("/"))
184 let item = try XCTUnwrap(list.documents.first { $0.title == "Native editing verification" })
185 var saved = ""
186 for _ in 0..<40 {
187 saved = String(decoding: try await fetch("/d/\(item.sqid)/export.md"), as: UTF8.self)
188 if saved.contains("Native text") && saved.contains("**") && saved.contains("bold") { break }
189 try await Task.sleep(for: .milliseconds(100))
190 }
191 let persistedDiagnostic = XCTAttachment(string: "Synthetic persisted Markdown: \(String(reflecting: saved))\nSynthetic displayed value: \(String(reflecting: editor.value as? String ?? "<missing>"))")
192 persistedDiagnostic.name = "Synthetic native persistence"; persistedDiagnostic.lifetime = .keepAlways; add(persistedDiagnostic)
193 XCTAssertTrue(saved.contains("Native text"), "Native edits must reach the real CRDT store")
194 XCTAssertTrue(saved.contains("**"), "Bold must persist as Markdown formatting, not only visible text")
195 XCTAssertTrue(saved.contains("bold"), "Formatted typing must reach the real CRDT store")
196 // Canonical Markdown export includes the document title as its heading.
197 let titlePrefix = "# Native editing verification\n\n"
198 XCTAssertTrue(saved.hasPrefix(titlePrefix))
199 let body = String(saved.dropFirst(titlePrefix.count))
200 XCTAssertEqual(body.replacingOccurrences(of: "**", with: "").trimmingCharacters(in: .whitespacesAndNewlines), "Native text bold", "Formatting the insertion point must preserve every character and append at the retained caret")
201 let screenshot = XCTAttachment(screenshot: app.screenshot())
202 screenshot.name = "Docs native editor"; screenshot.lifetime = .keepAlways; add(screenshot)
203 }
204}
205