field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraEditingProtectionTests.swift (4932B)


      1 import Foundation
      2 @testable import TeraApp
      3 import XCTest
      4 
      5 @MainActor
      6 final class TeraEditingProtectionTests: XCTestCase {
      7   func testLateDialogDismissalOrDiscardCannotActOnANewerFailedChoice() async throws {
      8     let protection = TeraEditingProtection()
      9     var applied = 0
     10     _ = await protection.replace(kind: .editing, save: { false }, apply: { applied += 1; return true })
     11     let old = try XCTUnwrap(protection.choiceToken)
     12     protection.cancel(token: old)
     13     _ = await protection.replace(kind: .editing, save: { false }, apply: { applied += 1; return true })
     14     let current = try XCTUnwrap(protection.choiceToken)
     15     XCTAssertNotEqual(old, current)
     16     protection.cancel(token: old)
     17     protection.discard(token: old)
     18     XCTAssertTrue(protection.failed)
     19     XCTAssertEqual(protection.choiceToken, current)
     20     XCTAssertEqual(applied, 0)
     21     protection.discard(token: current)
     22     await TeraScopeFixtures.eventually { !protection.isWorking }
     23     XCTAssertEqual(applied, 1)
     24   }
     25 
     26   func testOnePendingReplacementAwaitsSaveAndPublishesOnlyAppliedReopen() async {
     27     let protection = TeraEditingProtection()
     28     let pause = ResourceTestPause()
     29     var saves = 0
     30     var applied = 0
     31     let first = Task {
     32       await protection.replace(kind: .reopen, save: {
     33         saves += 1
     34         await pause.wait()
     35         return true
     36       }, apply: { applied += 1; return true })
     37     }
     38     await pause.entered.wait()
     39     for _ in 0 ..< 1000 {
     40       XCTAssertNil(protection.schedule(kind: .editing, save: { XCTFail("Duplicate save"); return true }, apply: { false }))
     41       protection.retry()
     42       protection.discard()
     43     }
     44     XCTAssertEqual(saves, 1)
     45     XCTAssertEqual(applied, 0)
     46     XCTAssertNil(protection.reopened)
     47     await pause.resume.open()
     48     let result = await first.value
     49     XCTAssertTrue(result)
     50     XCTAssertEqual(applied, 1)
     51     XCTAssertNotNil(protection.reopened)
     52     XCTAssertFalse(protection.failed)
     53     XCTAssertFalse(protection.isWorking)
     54   }
     55 
     56   func testFailedSaveRetainsChoiceAndRetryAppliesOnce() async {
     57     let protection = TeraEditingProtection()
     58     var saves = 0
     59     var applied = 0
     60     let result = await protection.replace(kind: .reopen, save: { saves += 1; return saves == 2 }, apply: { applied += 1; return true })
     61     XCTAssertFalse(result)
     62     XCTAssertTrue(protection.failed)
     63     XCTAssertNil(protection.reopened)
     64     XCTAssertEqual(applied, 0)
     65     protection.retry()
     66     await TeraScopeFixtures.eventually { !protection.isWorking }
     67     XCTAssertEqual(saves, 2)
     68     XCTAssertEqual(applied, 1)
     69     XCTAssertNotNil(protection.reopened)
     70     protection.retry()
     71     protection.discard()
     72     XCTAssertEqual(applied, 1)
     73   }
     74 
     75   func testDiscardIsExplicitAndCancellingTheChoiceKeepsEditing() async {
     76     let protection = TeraEditingProtection()
     77     var applied = 0
     78     var saves = 0
     79     let save = { saves += 1; return false }
     80     let apply = { applied += 1; return true }
     81     _ = await protection.replace(kind: .editing, save: save, apply: apply)
     82     protection.cancel()
     83     protection.discard()
     84     XCTAssertEqual(applied, 0)
     85     XCTAssertFalse(protection.failed)
     86     _ = await protection.replace(kind: .editing, save: save, apply: apply)
     87     XCTAssertTrue(protection.failed)
     88     protection.discard()
     89     await TeraScopeFixtures.eventually { !protection.isWorking }
     90     XCTAssertEqual(applied, 1)
     91     XCTAssertEqual(saves, 2)
     92     XCTAssertNil(protection.reopened)
     93   }
     94 
     95   func testEditingChangedWhileSavingRetainsWorkerUntilLateCompletionWithoutReplacing() async {
     96     let protection = TeraEditingProtection()
     97     let pause = ResourceTestPause()
     98     var applied = false
     99     let task = Task {
    100       await protection.replace(kind: .reopen, save: { await pause.wait(); return true }, apply: { applied = true; return true })
    101     }
    102     await pause.entered.wait()
    103     protection.editingChanged()
    104     XCTAssertNil(protection.schedule(kind: .editing, save: { true }, apply: { true }))
    105     await pause.resume.open()
    106     let result = await task.value
    107     XCTAssertFalse(result)
    108     XCTAssertFalse(applied)
    109     XCTAssertNil(protection.reopened)
    110     let next = await protection.replace(kind: .editing, save: { true }, apply: { true })
    111     XCTAssertTrue(next)
    112   }
    113 
    114   func testCancelledWaiterCannotReplaceAndFailedLoadCannotDismissSavedWork() async {
    115     let protection = TeraEditingProtection()
    116     let pause = ResourceTestPause()
    117     let task = Task {
    118       await protection.replace(kind: .reopen, save: { await pause.wait(); return true }, apply: { XCTFail("Cancelled replacement"); return true })
    119     }
    120     await pause.entered.wait()
    121     task.cancel()
    122     await TeraScopeFixtures.eventually { !protection.isWorking }
    123     await pause.resume.open()
    124     let result = await task.value
    125     XCTAssertFalse(result)
    126     let refused = await protection.replace(kind: .reopen, save: { true }, apply: { false })
    127     XCTAssertFalse(refused)
    128     XCTAssertNil(protection.reopened)
    129     XCTAssertFalse(protection.failed)
    130   }
    131 }