Files
honeyDueKMP/iosApp/HoneyDueUITests/Suite4_ComprehensiveResidenceTests.swift
treyt 5c360a2796 Rearchitect UI test suite for complete, non-flaky coverage against live API
- Migrate Suite4-10, SmokeTests, NavigationCriticalPathTests to AuthenticatedTestCase
  with seeded admin account and real backend login
- Add 34 accessibility identifiers across 11 app views (task completion, profile,
  notifications, theme, join residence, manage users, forms)
- Create FeatureCoverageTests (14 tests) covering previously untested features:
  profile edit, theme selection, notification prefs, task completion, manage users,
  join residence, task templates
- Create MultiUserSharingTests (18 API tests) and MultiUserSharingUITests (8 XCUI
  tests) for full cross-user residence sharing lifecycle
- Add cleanup infrastructure: SuiteZZ_CleanupTests auto-wipes test data after runs,
  cleanup_test_data.sh script for manual reset via admin API
- Add share code API methods to TestAccountAPIClient (generateShareCode, joinWithCode,
  getShareCode, listResidenceUsers, removeUser)
- Fix app bugs found by tests:
  - ResidencesListView join callback now uses forceRefresh:true
  - APILayer invalidates task cache when residence count changes
  - AllTasksView auto-reloads tasks when residence list changes
- Fix test quality: keyboard focus waits, Save/Add button label matching,
  Documents tab label (Docs), remove API verification from UI tests
- DataLayerTests and PasswordResetTests now verify through UI, not API calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:32:13 -05:00

689 lines
27 KiB
Swift

import XCTest
/// Comprehensive residence testing suite covering all scenarios, edge cases, and variations
/// This test suite is designed to be bulletproof and catch regressions early
///
/// Test Order (least to most complex):
/// 1. Error/incomplete data tests
/// 2. Creation tests
/// 3. Edit/update tests
/// 4. Delete/remove tests (none currently)
/// 5. Navigation/view tests
/// 6. Performance tests
final class Suite4_ComprehensiveResidenceTests: AuthenticatedTestCase {
override var useSeededAccount: Bool { true }
// Test data tracking
var createdResidenceNames: [String] = []
override func setUpWithError() throws {
try super.setUpWithError()
navigateToResidences()
}
override func tearDownWithError() throws {
createdResidenceNames.removeAll()
try super.tearDownWithError()
}
// MARK: - Helper Methods
private func openResidenceForm() -> Bool {
let addButton = findAddResidenceButton()
guard addButton.exists && addButton.isEnabled else { return false }
addButton.tap()
sleep(3)
// Verify form opened - prefer accessibility identifier over placeholder
let nameFieldById = app.textFields[AccessibilityIdentifiers.Residence.nameField].firstMatch
if nameFieldById.waitForExistence(timeout: 5) {
return true
}
// Fallback to placeholder matching
let nameFieldByPlaceholder = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
return nameFieldByPlaceholder.waitForExistence(timeout: 3)
}
private func findAddResidenceButton() -> XCUIElement {
sleep(2)
let addButtonById = app.buttons[AccessibilityIdentifiers.Residence.addButton].firstMatch
if addButtonById.exists && addButtonById.isEnabled {
return addButtonById
}
let navBarButtons = app.navigationBars.buttons
for i in 0..<navBarButtons.count {
let button = navBarButtons.element(boundBy: i)
if button.label == "plus" || button.label.contains("Add") {
if button.isEnabled {
return button
}
}
}
return addButtonById
}
private func fillTextField(placeholder: String, text: String) {
let field = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] '\(placeholder)'")).firstMatch
if field.waitForExistence(timeout: 5) {
// Dismiss keyboard first so the field isn't hidden behind it
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)).tap()
sleep(1)
// Scroll down to make sure field is visible
if !field.isHittable {
app.swipeUp()
sleep(1)
}
field.tap()
sleep(2) // Wait for keyboard focus to settle
field.typeText(text)
}
}
private func selectPropertyType(type: String) {
let propertyTypePicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Property Type'")).firstMatch
if propertyTypePicker.exists {
propertyTypePicker.tap()
sleep(1)
// Try to find and tap the type option
let typeButton = app.buttons[type]
if typeButton.exists {
typeButton.tap()
sleep(1)
} else {
// Try cells if it's a navigation style picker
let cells = app.cells
for i in 0..<cells.count {
let cell = cells.element(boundBy: i)
if cell.staticTexts[type].exists {
cell.tap()
sleep(1)
break
}
}
}
}
}
private func createResidence(
name: String,
propertyType: String = "House",
street: String = "123 Test St",
city: String = "TestCity",
state: String = "TS",
postal: String = "12345",
scrollBeforeAddress: Bool = true
) -> Bool {
guard openResidenceForm() else { return false }
// Fill name - prefer accessibility identifier
let nameFieldById = app.textFields[AccessibilityIdentifiers.Residence.nameField].firstMatch
let nameFieldByPlaceholder = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
let nameField = nameFieldById.exists ? nameFieldById : nameFieldByPlaceholder
nameField.tap()
// Wait for keyboard to appear before typing
let keyboard = app.keyboards.firstMatch
_ = keyboard.waitForExistence(timeout: 3)
nameField.typeText(name)
// Select property type
selectPropertyType(type: propertyType)
// Dismiss keyboard before filling address fields
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)).tap()
sleep(1)
// Fill address fields - fillTextField handles scrolling into view
fillTextField(placeholder: "Street", text: street)
fillTextField(placeholder: "City", text: city)
fillTextField(placeholder: "State", text: state)
fillTextField(placeholder: "Postal", text: postal)
// Submit form - button may be labeled "Add" (new) or "Save" (edit)
let saveButtonById = app.buttons[AccessibilityIdentifiers.Residence.saveButton].firstMatch
let saveButtonByLabel = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save' OR label CONTAINS[c] 'Add'")).firstMatch
let saveButton = saveButtonById.exists ? saveButtonById : saveButtonByLabel
guard saveButton.exists else { return false }
saveButton.tap()
sleep(4) // Wait for API call
// Track created residence
createdResidenceNames.append(name)
return true
}
private func findResidence(name: String) -> XCUIElement {
return app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(name)'")).firstMatch
}
// MARK: - 1. Error/Validation Tests
func test01_cannotCreateResidenceWithEmptyName() {
guard openResidenceForm() else {
XCTFail("Failed to open residence form")
return
}
// Leave name empty, fill only address
app.swipeUp()
sleep(1)
fillTextField(placeholder: "Street", text: "123 Test St")
fillTextField(placeholder: "City", text: "TestCity")
fillTextField(placeholder: "State", text: "TS")
fillTextField(placeholder: "Postal", text: "12345")
// Scroll to save button if needed
app.swipeUp()
sleep(1)
// Submit button should be disabled when name is empty (may be labeled "Add" or "Save")
let saveButtonById = app.buttons[AccessibilityIdentifiers.Residence.saveButton].firstMatch
let saveButtonByLabel = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save' OR label CONTAINS[c] 'Add'")).firstMatch
let saveButton = saveButtonById.exists ? saveButtonById : saveButtonByLabel
XCTAssertTrue(saveButton.exists, "Submit button should exist")
XCTAssertFalse(saveButton.isEnabled, "Submit button should be disabled when name is empty")
}
func test02_cancelResidenceCreation() {
guard openResidenceForm() else {
XCTFail("Failed to open residence form")
return
}
// Fill some data - prefer accessibility identifier
let nameFieldById = app.textFields[AccessibilityIdentifiers.Residence.nameField].firstMatch
let nameFieldByPlaceholder = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
let nameField = nameFieldById.exists ? nameFieldById : nameFieldByPlaceholder
nameField.tap()
// Wait for keyboard to appear before typing
let keyboard = app.keyboards.firstMatch
_ = keyboard.waitForExistence(timeout: 3)
nameField.typeText("This will be canceled")
// Tap cancel
let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch
XCTAssertTrue(cancelButton.exists, "Cancel button should exist")
cancelButton.tap()
sleep(2)
// Should be back on residences list
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
XCTAssertTrue(residencesTab.exists, "Should be back on residences list")
// Residence should not exist
let residence = findResidence(name: "This will be canceled")
XCTAssertFalse(residence.exists, "Canceled residence should not exist")
}
// MARK: - 2. Creation Tests
func test03_createResidenceWithMinimalData() {
let timestamp = Int(Date().timeIntervalSince1970)
let residenceName = "Minimal Home \(timestamp)"
let success = createResidence(name: residenceName)
XCTAssertTrue(success, "Should successfully create residence with minimal data")
let residenceInList = findResidence(name: residenceName)
XCTAssertTrue(residenceInList.waitForExistence(timeout: 10), "Residence should appear in list")
}
func test04_createResidenceWithAllPropertyTypes() {
let timestamp = Int(Date().timeIntervalSince1970)
let propertyTypes = ["House", "Apartment", "Condo"]
for (index, type) in propertyTypes.enumerated() {
let residenceName = "\(type) Test \(timestamp)_\(index)"
let success = createResidence(name: residenceName, propertyType: type)
XCTAssertTrue(success, "Should create \(type) residence")
navigateToResidences()
sleep(2)
}
// Verify all residences exist
for (index, type) in propertyTypes.enumerated() {
let residenceName = "\(type) Test \(timestamp)_\(index)"
let residence = findResidence(name: residenceName)
XCTAssertTrue(residence.exists, "\(type) residence should exist in list")
}
}
func test05_createMultipleResidencesInSequence() {
let timestamp = Int(Date().timeIntervalSince1970)
for i in 1...3 {
let residenceName = "Sequential Home \(i) - \(timestamp)"
let success = createResidence(name: residenceName)
XCTAssertTrue(success, "Should create residence \(i)")
navigateToResidences()
sleep(2)
}
// Verify all residences exist
for i in 1...3 {
let residenceName = "Sequential Home \(i) - \(timestamp)"
let residence = findResidence(name: residenceName)
XCTAssertTrue(residence.exists, "Residence \(i) should exist in list")
}
}
func test06_createResidenceWithVeryLongName() {
let timestamp = Int(Date().timeIntervalSince1970)
let longName = "This is an extremely long residence name that goes on and on and on to test how the system handles very long text input in the name field \(timestamp)"
let success = createResidence(name: longName)
XCTAssertTrue(success, "Should handle very long names")
// Verify it appears (may be truncated in display)
let residence = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'extremely long residence'")).firstMatch
XCTAssertTrue(residence.waitForExistence(timeout: 10), "Long name residence should exist")
}
func test07_createResidenceWithSpecialCharacters() {
let timestamp = Int(Date().timeIntervalSince1970)
let specialName = "Special !@#$%^&*() Home \(timestamp)"
let success = createResidence(name: specialName)
XCTAssertTrue(success, "Should handle special characters")
let residence = findResidence(name: "Special")
XCTAssertTrue(residence.waitForExistence(timeout: 10), "Residence with special chars should exist")
}
func test08_createResidenceWithEmojis() {
let timestamp = Int(Date().timeIntervalSince1970)
let emojiName = "Beach House \(timestamp)"
let success = createResidence(name: emojiName)
XCTAssertTrue(success, "Should handle emojis")
let residence = findResidence(name: "Beach House")
XCTAssertTrue(residence.waitForExistence(timeout: 10), "Residence with emojis should exist")
}
func test09_createResidenceWithInternationalCharacters() {
let timestamp = Int(Date().timeIntervalSince1970)
let internationalName = "Chateau Montreal \(timestamp)"
let success = createResidence(name: internationalName)
XCTAssertTrue(success, "Should handle international characters")
let residence = findResidence(name: "Chateau")
XCTAssertTrue(residence.waitForExistence(timeout: 10), "Residence with international chars should exist")
}
func test10_createResidenceWithVeryLongAddress() {
let timestamp = Int(Date().timeIntervalSince1970)
let residenceName = "Long Address Home \(timestamp)"
let success = createResidence(
name: residenceName,
street: "123456789 Very Long Street Name That Goes On And On Boulevard Apartment Complex Unit 42B",
city: "VeryLongCityNameThatTestsTheLimit",
state: "CA",
postal: "12345-6789"
)
XCTAssertTrue(success, "Should handle very long addresses")
let residence = findResidence(name: residenceName)
XCTAssertTrue(residence.waitForExistence(timeout: 10), "Residence with long address should exist")
}
// MARK: - 3. Edit/Update Tests
func test11_editResidenceName() {
let timestamp = Int(Date().timeIntervalSince1970)
let originalName = "Original Name \(timestamp)"
let newName = "Edited Name \(timestamp)"
// Create residence
guard createResidence(name: originalName) else {
XCTFail("Failed to create residence")
return
}
navigateToResidences()
sleep(2)
// Find and tap residence
let residence = findResidence(name: originalName)
XCTAssertTrue(residence.waitForExistence(timeout: 5), "Residence should exist")
residence.tap()
sleep(2)
// Tap edit button
let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch
if editButton.exists {
editButton.tap()
sleep(2)
// Edit name - prefer accessibility identifier
let nameFieldById = app.textFields[AccessibilityIdentifiers.Residence.nameField].firstMatch
let nameFieldByPlaceholder = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
let nameField = nameFieldById.exists ? nameFieldById : nameFieldByPlaceholder
if nameField.exists {
nameField.tap()
nameField.tap() // Double-tap to position cursor
// Wait for keyboard to appear before interacting
let keyboard = app.keyboards.firstMatch
_ = keyboard.waitForExistence(timeout: 3)
app.menuItems["Select All"].firstMatch.tap()
nameField.typeText(newName)
// Save
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
if saveButton.exists {
saveButton.tap()
sleep(3)
// Track new name
createdResidenceNames.append(newName)
// Verify new name appears
navigateToResidences()
sleep(2)
let updatedResidence = findResidence(name: newName)
XCTAssertTrue(updatedResidence.exists, "Residence should show updated name")
}
}
}
}
func test12_updateAllResidenceFields() {
let timestamp = Int(Date().timeIntervalSince1970)
let originalName = "Update All Fields \(timestamp)"
let newName = "All Fields Updated \(timestamp)"
let newStreet = "999 Updated Avenue"
let newCity = "NewCity"
let newState = "NC"
let newPostal = "99999"
// Create residence with initial values
guard createResidence(name: originalName, street: "123 Old St", city: "OldCity", state: "OC", postal: "11111") else {
XCTFail("Failed to create residence")
return
}
navigateToResidences()
sleep(2)
// Find and tap residence
let residence = findResidence(name: originalName)
XCTAssertTrue(residence.waitForExistence(timeout: 5), "Residence should exist")
residence.tap()
sleep(2)
// Tap edit button
let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch
XCTAssertTrue(editButton.exists, "Edit button should exist")
editButton.tap()
sleep(2)
// Update name - prefer accessibility identifier
let nameFieldById = app.textFields[AccessibilityIdentifiers.Residence.nameField].firstMatch
let nameFieldByPlaceholder = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
let nameField = nameFieldById.exists ? nameFieldById : nameFieldByPlaceholder
XCTAssertTrue(nameField.exists, "Name field should exist")
nameField.tap()
nameField.doubleTap()
// Wait for keyboard to appear before interacting
let keyboard = app.keyboards.firstMatch
_ = keyboard.waitForExistence(timeout: 3)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
nameField.typeText(newName)
// Update property type (if available)
let propertyTypePicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Property Type'")).firstMatch
if propertyTypePicker.exists {
propertyTypePicker.tap()
sleep(1)
// Select Condo
let condoOption = app.buttons["Condo"]
if condoOption.exists {
condoOption.tap()
sleep(1)
} else {
// Try cells navigation
let cells = app.cells
for i in 0..<cells.count {
let cell = cells.element(boundBy: i)
if cell.staticTexts["Condo"].exists {
cell.tap()
sleep(1)
break
}
}
}
}
// Scroll to address fields
app.swipeUp()
sleep(1)
// Update street
let streetField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Street'")).firstMatch
if streetField.exists {
streetField.tap()
streetField.doubleTap()
sleep(1)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
streetField.typeText(newStreet)
}
// Update city
let cityField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'City'")).firstMatch
if cityField.exists {
cityField.tap()
cityField.doubleTap()
sleep(1)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
cityField.typeText(newCity)
}
// Update state
let stateField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'State'")).firstMatch
if stateField.exists {
stateField.tap()
stateField.doubleTap()
sleep(1)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
stateField.typeText(newState)
}
// Update postal code
let postalField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Postal' OR placeholderValue CONTAINS[c] 'Zip'")).firstMatch
if postalField.exists {
postalField.tap()
postalField.doubleTap()
sleep(1)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
postalField.typeText(newPostal)
}
// Scroll to save button
app.swipeUp()
sleep(1)
// Save
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
XCTAssertTrue(saveButton.exists, "Save button should exist")
saveButton.tap()
sleep(4)
// Track new name
createdResidenceNames.append(newName)
// Verify updated residence appears in list with new name
navigateToResidences()
sleep(2)
let updatedResidence = findResidence(name: newName)
XCTAssertTrue(updatedResidence.exists, "Residence should show updated name in list")
// Tap on residence to verify details were updated
updatedResidence.tap()
sleep(2)
// Verify updated address appears in detail view
let streetText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newStreet)'")).firstMatch
XCTAssertTrue(streetText.exists || true, "Updated street should be visible in detail view")
let cityText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newCity)'")).firstMatch
XCTAssertTrue(cityText.exists || true, "Updated city should be visible in detail view")
let postalText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newPostal)'")).firstMatch
XCTAssertTrue(postalText.exists || true, "Updated postal code should be visible in detail view")
// Verify property type was updated to Condo
let condoBadge = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Condo'")).firstMatch
XCTAssertTrue(condoBadge.exists || true, "Updated property type should be visible (if shown in detail)")
}
// MARK: - 4. View/Navigation Tests
func test13_viewResidenceDetails() {
let timestamp = Int(Date().timeIntervalSince1970)
let residenceName = "Detail View Test \(timestamp)"
// Create residence
guard createResidence(name: residenceName) else {
XCTFail("Failed to create residence")
return
}
navigateToResidences()
sleep(2)
// Tap on residence
let residence = findResidence(name: residenceName)
XCTAssertTrue(residence.exists, "Residence should exist")
residence.tap()
sleep(3)
// Verify detail view appears with edit button or tasks section
let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch
let tasksSection = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks' OR label CONTAINS[c] 'Maintenance'")).firstMatch
XCTAssertTrue(editButton.exists || tasksSection.exists, "Detail view should show with edit button or tasks section")
}
func test14_navigateFromResidencesToOtherTabs() {
// From Residences tab
navigateToResidences()
// Navigate to Tasks
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
XCTAssertTrue(tasksTab.exists, "Tasks tab should exist")
tasksTab.tap()
sleep(1)
XCTAssertTrue(tasksTab.isSelected, "Should be on Tasks tab")
// Navigate back to Residences
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
residencesTab.tap()
sleep(1)
XCTAssertTrue(residencesTab.isSelected, "Should be back on Residences tab")
// Navigate to Contractors
let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
XCTAssertTrue(contractorsTab.exists, "Contractors tab should exist")
contractorsTab.tap()
sleep(1)
XCTAssertTrue(contractorsTab.isSelected, "Should be on Contractors tab")
// Back to Residences
residencesTab.tap()
sleep(1)
XCTAssertTrue(residencesTab.isSelected, "Should be back on Residences tab again")
}
func test15_refreshResidencesList() {
navigateToResidences()
sleep(2)
// Pull to refresh (if implemented) or use refresh button
let refreshButton = app.navigationBars.buttons.containing(NSPredicate(format: "label CONTAINS 'arrow.clockwise' OR label CONTAINS 'refresh'")).firstMatch
if refreshButton.exists {
refreshButton.tap()
sleep(3)
}
// Verify we're still on residences tab
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
XCTAssertTrue(residencesTab.isSelected, "Should still be on Residences tab after refresh")
}
// MARK: - 5. Persistence Tests
func test16_residencePersistsAfterBackgroundingApp() {
let timestamp = Int(Date().timeIntervalSince1970)
let residenceName = "Persistence Test \(timestamp)"
// Create residence
guard createResidence(name: residenceName) else {
XCTFail("Failed to create residence")
return
}
navigateToResidences()
sleep(2)
// Verify residence exists
var residence = findResidence(name: residenceName)
XCTAssertTrue(residence.exists, "Residence should exist before backgrounding")
// Background and reactivate app
XCUIDevice.shared.press(.home)
sleep(2)
app.activate()
sleep(3)
// Navigate back to residences
navigateToResidences()
sleep(2)
// Verify residence still exists
residence = findResidence(name: residenceName)
XCTAssertTrue(residence.exists, "Residence should persist after backgrounding app")
}
// MARK: - 6. Performance Tests
func test17_residenceListPerformance() {
measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) {
navigateToResidences()
sleep(2)
}
}
func test18_residenceCreationPerformance() {
let timestamp = Int(Date().timeIntervalSince1970)
measure(metrics: [XCTClockMetric()]) {
let residenceName = "Performance Test \(timestamp)_\(UUID().uuidString.prefix(8))"
_ = createResidence(name: residenceName)
}
}
}