Files
honeyDueKMP/iosApp/CaseraUITests/Tests/DocumentIntegrationTests.swift
treyt fc0e0688eb Add comprehensive iOS unit and UI test suites for greenfield test plan
- Create unit tests: DataLayerTests (27 tests for DATA-001–007), DataManagerExtendedTests
  (20 tests for TASK-005, TASK-012, TCOMP-003, THEME-001, QA-002), plus ValidationHelpers,
  TaskMetrics, StringExtensions, DoubleExtensions, DateUtils, DocumentHelpers, ErrorMessageParser
- Create UI tests: AuthenticationTests, PasswordResetTests, OnboardingTests, TaskIntegration,
  ContractorIntegration, ResidenceIntegration, DocumentIntegration, DataLayer, Stability
- Add UI test framework: AuthenticatedTestCase, ScreenObjects, TestFlows, TestAccountManager,
  TestAccountAPIClient, TestDataCleaner, TestDataSeeder
- Add accessibility identifiers to password reset views for UI test support
- Add greenfield test plan CSVs and update automated column for 27 test IDs
- All 297 unit tests pass across 60 suites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:37:56 -06:00

185 lines
7.7 KiB
Swift

import XCTest
/// Integration tests for document CRUD against the real local backend.
///
/// Test Plan IDs: DOC-002, DOC-004, DOC-005
/// Data is seeded via API and cleaned up in tearDown.
final class DocumentIntegrationTests: AuthenticatedTestCase {
override var useSeededAccount: Bool { true }
// MARK: - DOC-002: Create Document
func testDOC002_CreateDocumentWithRequiredFields() {
// Seed a residence so the document form has a valid residence picker
cleaner.seedResidence()
navigateToDocuments()
let addButton = app.buttons[AccessibilityIdentifiers.Document.addButton]
let emptyState = app.otherElements[AccessibilityIdentifiers.Document.emptyStateView]
let documentList = app.otherElements[AccessibilityIdentifiers.Document.documentsList]
let loaded = addButton.waitForExistence(timeout: defaultTimeout)
|| emptyState.waitForExistence(timeout: 3)
|| documentList.waitForExistence(timeout: 3)
XCTAssertTrue(loaded, "Documents screen should load")
if addButton.exists && addButton.isHittable {
addButton.forceTap()
} else {
let emptyAddButton = app.buttons.containing(
NSPredicate(format: "label CONTAINS[c] 'Add' OR label CONTAINS[c] 'Create'")
).firstMatch
emptyAddButton.waitForExistenceOrFail(timeout: defaultTimeout)
emptyAddButton.forceTap()
}
let titleField = app.textFields[AccessibilityIdentifiers.Document.titleField]
titleField.waitForExistenceOrFail(timeout: defaultTimeout)
let uniqueTitle = "IntTest Doc \(Int(Date().timeIntervalSince1970))"
titleField.forceTap()
titleField.typeText(uniqueTitle)
let saveButton = app.buttons[AccessibilityIdentifiers.Document.saveButton]
saveButton.scrollIntoView(in: app.scrollViews.firstMatch)
saveButton.forceTap()
let newDoc = app.staticTexts[uniqueTitle]
XCTAssertTrue(
newDoc.waitForExistence(timeout: longTimeout),
"Newly created document should appear in list"
)
}
// MARK: - DOC-004: Edit Document
func testDOC004_EditDocument() {
// Seed a residence and document via API
let residence = cleaner.seedResidence()
let doc = cleaner.seedDocument(residenceId: residence.id, title: "Edit Target Doc \(Int(Date().timeIntervalSince1970))")
navigateToDocuments()
// Find and tap the seeded document
let card = app.staticTexts[doc.title]
card.waitForExistenceOrFail(timeout: longTimeout)
card.forceTap()
// Tap edit
let editButton = app.buttons[AccessibilityIdentifiers.Document.editButton]
editButton.waitForExistenceOrFail(timeout: defaultTimeout)
editButton.forceTap()
// Update title
let titleField = app.textFields[AccessibilityIdentifiers.Document.titleField]
titleField.waitForExistenceOrFail(timeout: defaultTimeout)
titleField.forceTap()
titleField.press(forDuration: 1.0)
let selectAll = app.menuItems["Select All"]
if selectAll.waitForExistence(timeout: 2) {
selectAll.tap()
}
let updatedTitle = "Updated Doc \(Int(Date().timeIntervalSince1970))"
titleField.typeText(updatedTitle)
let saveButton = app.buttons[AccessibilityIdentifiers.Document.saveButton]
saveButton.scrollIntoView(in: app.scrollViews.firstMatch)
saveButton.forceTap()
let updatedText = app.staticTexts[updatedTitle]
XCTAssertTrue(
updatedText.waitForExistence(timeout: longTimeout),
"Updated document title should appear after edit"
)
}
// MARK: - DOC-007: Document Image Section Exists
// NOTE: Full image-deletion testing (the original DOC-007 scenario) requires a
// document with at least one uploaded image. Image upload cannot be triggered
// via API alone it requires user interaction with the photo picker inside the
// app (or a multipart upload endpoint). This stub seeds a document, opens its
// detail view, and verifies the images section is present so that a human tester
// or future automation (with photo injection) can extend it.
func test22_documentImageSectionExists() throws {
// Seed a residence and a document via API
let residence = cleaner.seedResidence()
let document = cleaner.seedDocument(
residenceId: residence.id,
title: "Image Section Doc \(Int(Date().timeIntervalSince1970))"
)
navigateToDocuments()
// Open the seeded document's detail
let docText = app.staticTexts[document.title]
docText.waitForExistenceOrFail(timeout: longTimeout)
docText.forceTap()
// Verify the detail view loaded
let detailView = app.otherElements[AccessibilityIdentifiers.Document.detailView]
let detailLoaded = detailView.waitForExistence(timeout: defaultTimeout)
|| app.navigationBars.staticTexts[document.title].waitForExistence(timeout: defaultTimeout)
XCTAssertTrue(detailLoaded, "Document detail view should load after tapping the document")
// Look for an images / photos section header or add-image button.
// The exact identifier or label will depend on the document detail implementation.
let imagesSection = app.staticTexts.containing(
NSPredicate(format: "label CONTAINS[c] 'Image' OR label CONTAINS[c] 'Photo' OR label CONTAINS[c] 'Attachment'")
).firstMatch
let addImageButton = app.buttons.containing(
NSPredicate(format: "label CONTAINS[c] 'Image' OR label CONTAINS[c] 'Photo' OR label CONTAINS[c] 'Add'")
).firstMatch
let sectionVisible = imagesSection.waitForExistence(timeout: defaultTimeout)
|| addImageButton.waitForExistence(timeout: 3)
// This assertion will fail gracefully if the images section is not yet implemented.
// When it does fail, it surfaces the missing UI element for the developer.
XCTAssertTrue(
sectionVisible,
"Document detail should show an images/photos section or an add-image button. " +
"Full deletion of a specific image requires manual upload first — see DOC-007 in test plan."
)
}
// MARK: - DOC-005: Delete Document
func testDOC005_DeleteDocument() {
// Seed a document via API don't track since we'll delete through UI
let residence = cleaner.seedResidence()
let deleteTitle = "Delete Doc \(Int(Date().timeIntervalSince1970))"
TestDataSeeder.createDocument(token: session.token, residenceId: residence.id, title: deleteTitle)
navigateToDocuments()
let target = app.staticTexts[deleteTitle]
target.waitForExistenceOrFail(timeout: longTimeout)
target.forceTap()
let deleteButton = app.buttons[AccessibilityIdentifiers.Document.deleteButton]
deleteButton.waitForExistenceOrFail(timeout: defaultTimeout)
deleteButton.forceTap()
let confirmButton = app.buttons[AccessibilityIdentifiers.Alert.confirmButton]
let alertDelete = app.alerts.buttons.containing(
NSPredicate(format: "label CONTAINS[c] 'Delete' OR label CONTAINS[c] 'Confirm'")
).firstMatch
if confirmButton.waitForExistence(timeout: shortTimeout) {
confirmButton.tap()
} else if alertDelete.waitForExistence(timeout: shortTimeout) {
alertDelete.tap()
}
let deletedDoc = app.staticTexts[deleteTitle]
XCTAssertTrue(
deletedDoc.waitForNonExistence(timeout: longTimeout),
"Deleted document should no longer appear"
)
}
}