1 //  Copyright (c) 2023 650 Industries, Inc. All rights reserved.
2 
3 // swiftlint:disable closure_body_length
4 
5 import ExpoModulesTestCore
6 
7 @testable import EXUpdates
8 
9 import EXManifests
10 
11 class TestStateChangeDelegate: UpdatesStateChangeDelegate {
12   var lastEventType: EXUpdates.UpdatesStateEventType?
13   var lastEventBody: [String: Any?]?
sendUpdateStateChangeEventToBridgenull14   func sendUpdateStateChangeEventToBridge(_ eventType: EXUpdates.UpdatesStateEventType, body: [String: Any?]) {
15     lastEventType = eventType
16     lastEventBody = body
17   }
18 }
19 
20 class UpdatesStateMachineSpec: ExpoSpec {
specnull21   override func spec() {
22     describe("default state") {
23       it("instantiates") {
24         let testStateChangeDelegate = TestStateChangeDelegate()
25         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
26         expect(machine.state) == .idle
27       }
28 
29       it("should handle check and checkCompleteAvailable") {
30         let testStateChangeDelegate = TestStateChangeDelegate()
31         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
32 
33         machine.processEvent(UpdatesStateEventCheck())
34         expect(machine.state) == .checking
35         expect(testStateChangeDelegate.lastEventType) == .check
36 
37         machine.processEvent(UpdatesStateEventCheckCompleteWithUpdate(manifest: [
38           "updateId": "0000-xxxx"
39         ]))
40         expect(machine.state) == .idle
41         expect(machine.context.isChecking) == false
42         expect(machine.context.checkError).to(beNil())
43         expect(machine.context.latestManifest?["updateId"] as? String ?? "") == "0000-xxxx"
44         expect(machine.context.isUpdateAvailable) == true
45         expect(machine.context.isUpdatePending) == false
46         expect(testStateChangeDelegate.lastEventType) == .checkCompleteAvailable
47         let values = testStateChangeDelegate.lastEventBody?["context"] as? [String: Any] ?? [:]
48         expect(values["isUpdateAvailable"] as? Bool ?? false) == true
49       }
50 
51       it("should handle check and checkCompleteUnavailable") {
52         let testStateChangeDelegate = TestStateChangeDelegate()
53         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
54 
55         machine.processEvent(UpdatesStateEventCheck())
56         expect(machine.state) == .checking
57 
58         machine.processEvent(UpdatesStateEventCheckComplete())
59         expect(machine.state) == .idle
60         expect(machine.context.isChecking) == false
61         expect(machine.context.checkError).to(beNil())
62         expect(machine.context.latestManifest).to(beNil())
63         expect(machine.context.isUpdateAvailable) == false
64         expect(machine.context.isUpdatePending) == false
65       }
66 
67       it("should handle download and downloadComplete") {
68         let testStateChangeDelegate = TestStateChangeDelegate()
69         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
70 
71         machine.processEvent(UpdatesStateEventDownload())
72         expect(machine.state) == .downloading
73 
74         machine.processEvent(UpdatesStateEventDownloadCompleteWithUpdate(manifest: [
75           "updateId": "0000-xxxx"
76         ]))
77         expect(machine.state) == .idle
78         expect(machine.context.isChecking) == false
79         expect(machine.context.downloadError).to(beNil())
80         expect(machine.context.latestManifest?["updateId"] as? String ?? "") == "0000-xxxx"
81         expect(machine.context.downloadedManifest?["updateId"] as? String ?? "") == "0000-xxxx"
82         expect(machine.context.isUpdateAvailable) == true
83         expect(machine.context.isUpdatePending) == true
84         expect(machine.context.isRollback) == false
85       }
86 
87       it("should handle rollback") {
88         let testStateChangeDelegate = TestStateChangeDelegate()
89         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
90         let commitTime = Date()
91         machine.processEvent(UpdatesStateEventCheck())
92         expect(machine.state) == .checking
93 
94         machine.processEvent(UpdatesStateEventCheckCompleteWithRollback(rollbackCommitTime: commitTime))
95         expect(machine.state) == .idle
96         expect(machine.context.isChecking) == false
97         expect(machine.context.checkError).to(beNil())
98         expect(machine.context.latestManifest).to(beNil())
99         expect(machine.context.isUpdateAvailable) == true
100         expect(machine.context.isUpdatePending) == false
101         expect(machine.context.rollback?.commitTime) == commitTime
102       }
103 
104       it("invalid transitions are handled as expected") {
105         let testStateChangeDelegate = TestStateChangeDelegate()
106         let machine = UpdatesStateMachine(changeEventDelegate: testStateChangeDelegate)
107 
108         machine.processEvent(UpdatesStateEventCheck())
109         expect(machine.state) == .checking
110         // Reset the test delegate
111         testStateChangeDelegate.lastEventBody = nil
112         testStateChangeDelegate.lastEventType = nil
113 
114         // In .checking state, download events should be ignored,
115         // state should not change, context should not change,
116         // no events should be sent to JS
117         machine.processEvent(UpdatesStateEventDownload())
118 
119         expect(machine.state) == .checking
120         expect(testStateChangeDelegate.lastEventType).to(beNil())
121         expect(testStateChangeDelegate.lastEventBody).to(beNil())
122 
123         machine.processEvent(UpdatesStateEventDownloadCompleteWithUpdate(manifest: [
124           "updateId": "0000-xxxx"
125         ]))
126 
127         expect(machine.state) == .checking
128         expect(machine.context.downloadedManifest).to(beNil())
129 
130         machine.reset() // go back to .idle
131 
132         machine.processEvent(UpdatesStateEventRestart())
133         expect(machine.state) == .restarting
134 
135         // If restarting, all events should be ignored
136         machine.processEvent(UpdatesStateEventCheck())
137         expect(machine.state) == .restarting
138 
139         machine.processEvent(UpdatesStateEventDownload())
140         expect(machine.state) == .restarting
141 
142         machine.processEvent(UpdatesStateEventDownloadComplete())
143         expect(machine.state) == .restarting
144       }
145     }
146   }
147 }
148 
149 // swiftlint:enable closure_body_length
150