| 1 | import XCTest |
| 2 | |
| 3 | @MainActor final class NativeUITests: XCTestCase { |
| 4 | func testCreateAndEditFormula() { |
| 5 | continueAfterFailure = false |
| 6 | let app = XCUIApplication() |
| 7 | app.launch() |
| 8 | defer { app.terminate() } |
| 9 | let create = app.buttons["New spreadsheet"] |
| 10 | XCTAssertTrue(create.waitForExistence(timeout: 15)) |
| 11 | create.tap() |
| 12 | let formula = app.textFields["Cell contents or formula"] |
| 13 | XCTAssertTrue(formula.waitForExistence(timeout: 10), "Creating a sheet must navigate to its native editor") |
| 14 | formula.tap() |
| 15 | formula.typeText("=2+3") |
| 16 | app.buttons["Save"].tap() |
| 17 | XCTAssertTrue(app.buttons["A1, 5"].waitForExistence(timeout: 10), "The server-calculated value must appear") |
| 18 | let saved = NSPredicate(format: "exists == false") |
| 19 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: saved, object: app.buttons["Save"])], timeout: 5), .completed) |
| 20 | app.buttons["A1, 5"].tap() |
| 21 | let rawFormula = NSPredicate(format: "value == %@", "=2+3") |
| 22 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: rawFormula, object: formula)], timeout: 5), .completed, "Selecting a cell must expose its formula rather than its display value") |
| 23 | formula.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)).tap() |
| 24 | formula.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: 4)) |
| 25 | formula.typeText("=7*6") |
| 26 | app.buttons["Save"].tap() |
| 27 | XCTAssertTrue(app.buttons["A1, 42"].waitForExistence(timeout: 10)) |
| 28 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: saved, object: app.buttons["Save"])], timeout: 5), .completed) |
| 29 | app.buttons["A1, 42"].tap() |
| 30 | let finalFormula = NSPredicate(format: "value == %@", "=7*6") |
| 31 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: finalFormula, object: formula)], timeout: 5), .completed) |
| 32 | |
| 33 | // Exercise commands through the actual native menus, preserving A1 as the |
| 34 | // fixture's independent server-persistence sentinel. |
| 35 | menu(app, "Edit", command: "Fill down") |
| 36 | XCTAssertTrue(app.buttons["A2, 42"].waitForExistence(timeout: 10)) |
| 37 | select(app, "A1:A2") |
| 38 | menu(app, "Edit", command: "Copy") |
| 39 | select(app, "B1") |
| 40 | menu(app, "Edit", command: "Paste") |
| 41 | XCTAssertTrue(app.buttons["B1, 42"].waitForExistence(timeout: 10)) |
| 42 | XCTAssertTrue(app.buttons["B2, 42"].waitForExistence(timeout: 10)) |
| 43 | select(app, "B1:B2") |
| 44 | openMenu(app, "Format") |
| 45 | app.buttons["Number format"].tap() |
| 46 | app.buttons["Number"].tap() |
| 47 | XCTAssertTrue(app.buttons["B1, 42.00"].waitForExistence(timeout: 10)) |
| 48 | XCTAssertTrue(app.buttons["B2, 42.00"].waitForExistence(timeout: 10)) |
| 49 | menu(app, "Edit", command: "Undo my last value edit") |
| 50 | XCTAssertTrue(app.buttons["B2, Empty"].waitForExistence(timeout: 10)) |
| 51 | select(app, "B2") |
| 52 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: NSPredicate(format: "value == %@ OR value == %@", "", "Cell contents or formula"), object: formula)], timeout: 10), .completed) |
| 53 | menu(app, "File", command: "Version history") |
| 54 | let restore = app.buttons["Restore before this edit"].firstMatch |
| 55 | XCTAssertTrue(restore.waitForExistence(timeout: 10)) |
| 56 | restore.tap() |
| 57 | app.buttons["Done"].tap() |
| 58 | // Canonical value undo clears the sparse cell row, including its format; |
| 59 | // restoring the input therefore returns the default number display. |
| 60 | XCTAssertTrue(app.buttons["B2, 42"].waitForExistence(timeout: 10), "History Restore must restore the pasted formula") |
| 61 | select(app, "A1") |
| 62 | let screenshot = XCTAttachment(screenshot: app.screenshot()) |
| 63 | screenshot.name = "Sheets native formula editing" |
| 64 | screenshot.lifetime = .keepAlways |
| 65 | add(screenshot) |
| 66 | } |
| 67 | |
| 68 | private func select(_ app: XCUIApplication, _ reference: String) { |
| 69 | let field = app.textFields["Cell or range, for example A1:B5"] |
| 70 | XCTAssertTrue(field.waitForExistence(timeout: 5)) |
| 71 | field.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)).tap() |
| 72 | field.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: (field.value as? String ?? "").count)) |
| 73 | field.typeText(reference + "\n") |
| 74 | } |
| 75 | |
| 76 | func testFailedCellEditCanBeRetriedWithoutLosingDraft() { |
| 77 | continueAfterFailure = false |
| 78 | let app = XCUIApplication() |
| 79 | app.launch() |
| 80 | defer { app.terminate() } |
| 81 | XCTAssertTrue(app.buttons["New spreadsheet"].waitForExistence(timeout: 15)) |
| 82 | app.buttons["New spreadsheet"].tap() |
| 83 | let formula = app.textFields["Cell contents or formula"] |
| 84 | XCTAssertTrue(formula.waitForExistence(timeout: 10)) |
| 85 | formula.tap() |
| 86 | // Only this exact synthetic input triggers one refusal in the fixture. |
| 87 | formula.typeText("=6*7") |
| 88 | app.buttons["Save"].tap() |
| 89 | let retry = app.buttons["Retry pending edits"] |
| 90 | XCTAssertTrue(retry.waitForExistence(timeout: 10)) |
| 91 | XCTAssertEqual(formula.value as? String, "=6*7") |
| 92 | XCTAssertFalse(formula.isEnabled) |
| 93 | app.buttons["A2, Empty"].tap() |
| 94 | XCTAssertEqual(app.textFields["Cell or range, for example A1:B5"].value as? String, "A1") |
| 95 | retry.tap() |
| 96 | XCTAssertTrue(app.buttons["A1, 42"].waitForExistence(timeout: 10)) |
| 97 | XCTAssertEqual(XCTWaiter.wait(for: [XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: retry)], timeout: 10), .completed) |
| 98 | let screenshot = XCTAttachment(screenshot: app.screenshot()) |
| 99 | screenshot.name = "Sheets native retried edit"; screenshot.lifetime = .keepAlways; add(screenshot) |
| 100 | } |
| 101 | |
| 102 | func testExportWorkbookOpensNativeDestinationAndCancels() { |
| 103 | continueAfterFailure = false |
| 104 | let app = XCUIApplication() |
| 105 | app.launch() |
| 106 | defer { app.terminate() } |
| 107 | let workbook = app.buttons["Native review workbook"] |
| 108 | XCTAssertTrue(workbook.waitForExistence(timeout: 15)) |
| 109 | workbook.tap() |
| 110 | XCTAssertTrue(app.textFields["Cell contents or formula"].waitForExistence(timeout: 10)) |
| 111 | menu(app, "File", command: "Export workbook") |
| 112 | let export = app.buttons["Save workbook…"] |
| 113 | XCTAssertTrue(export.waitForExistence(timeout: 10)) |
| 114 | export.tap() |
| 115 | let cancel = app.buttons["Cancel"] |
| 116 | XCTAssertTrue(cancel.waitForExistence(timeout: 15), "A native file destination picker must open after download") |
| 117 | let screenshot = XCTAttachment(screenshot: app.screenshot()) |
| 118 | screenshot.name = "Sheets native export destination" |
| 119 | screenshot.lifetime = .keepAlways |
| 120 | add(screenshot) |
| 121 | cancel.tap() |
| 122 | XCTAssertTrue(export.waitForExistence(timeout: 10)) |
| 123 | app.buttons["Done"].tap() |
| 124 | } |
| 125 | |
| 126 | private func openMenu(_ app: XCUIApplication, _ name: String) { |
| 127 | if app.buttons["Sheet actions"].exists { app.buttons["Sheet actions"].tap() } |
| 128 | let menu = app.buttons[name] |
| 129 | XCTAssertTrue(menu.waitForExistence(timeout: 5), "Missing native menu: \(name)") |
| 130 | menu.tap() |
| 131 | } |
| 132 | |
| 133 | private func menu(_ app: XCUIApplication, _ name: String, command: String) { |
| 134 | openMenu(app, name) |
| 135 | let action = app.buttons[command] |
| 136 | XCTAssertTrue(action.waitForExistence(timeout: 5), "Missing native command: \(command)") |
| 137 | action.tap() |
| 138 | } |
| 139 | } |
| 140 | |