Revision control

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
@testable import Client
6
import Shared
7
import Storage
8
import UIKit
9
import WebKit
10
11
import XCTest
12
13
open class TabManagerMockProfile: MockProfile {
14
var numberOfTabsStored = 0
15
override public func storeTabs(_ tabs: [RemoteTab]) -> Deferred<Maybe<Int>> {
16
numberOfTabsStored = tabs.count
17
return deferMaybe(tabs.count)
18
}
19
}
20
21
struct MethodSpy {
22
let functionName: String
23
let method: ((_ tabs: [Tab?]) -> Void)?
24
25
init(functionName: String) {
26
self.functionName = functionName
27
self.method = nil
28
}
29
30
init(functionName: String, method: ((_ tabs: [Tab?]) -> Void)?) {
31
self.functionName = functionName
32
self.method = method
33
}
34
}
35
36
fileprivate let spyDidSelectedTabChange = "tabManager(_:didSelectedTabChange:previous:isRestoring:)"
37
38
open class MockTabManagerDelegate: TabManagerDelegate {
39
//this array represents the order in which delegate methods should be called.
40
//each delegate method will pop the first struct from the array. If the method name doesn't match the struct then the order is incorrect
41
//Then it evaluates the method closure which will return true/false depending on if the tabs are correct
42
var methodCatchers: [MethodSpy] = []
43
44
func expect(_ methods: [MethodSpy]) {
45
self.methodCatchers = methods
46
}
47
48
func verify(_ message: String) {
49
XCTAssertTrue(methodCatchers.isEmpty, message)
50
}
51
52
func testDelegateMethodWithName(_ name: String, tabs: [Tab?]) {
53
guard let spy = self.methodCatchers.first else {
54
XCTAssert(false, "No method was availible in the queue. For the delegate method \(name) to use")
55
return
56
}
57
XCTAssertEqual(spy.functionName, name)
58
if let methodCheck = spy.method {
59
methodCheck(tabs)
60
}
61
methodCatchers.removeFirst()
62
}
63
64
public func tabManager(_ tabManager: TabManager, didSelectedTabChange selected: Tab?, previous: Tab?, isRestoring: Bool) {
65
testDelegateMethodWithName(#function, tabs: [selected, previous])
66
}
67
68
public func tabManager(_ tabManager: TabManager, didAddTab tab: Tab, isRestoring: Bool) {
69
testDelegateMethodWithName(#function, tabs: [tab])
70
}
71
72
public func tabManager(_ tabManager: TabManager, didRemoveTab tab: Tab, isRestoring: Bool) {
73
testDelegateMethodWithName(#function, tabs: [tab])
74
}
75
76
public func tabManagerDidRestoreTabs(_ tabManager: TabManager) {
77
testDelegateMethodWithName(#function, tabs: [])
78
}
79
80
public func tabManagerDidAddTabs(_ tabManager: TabManager) {
81
testDelegateMethodWithName(#function, tabs: [])
82
}
83
84
public func tabManagerDidRemoveAllTabs(_ tabManager: TabManager, toast: ButtonToast?) {
85
testDelegateMethodWithName(#function, tabs: [])
86
}
87
}
88
89
class TabManagerTests: XCTestCase {
90
91
let didRemove = MethodSpy(functionName: "tabManager(_:didRemoveTab:isRestoring:)")
92
let didAdd = MethodSpy(functionName: "tabManager(_:didAddTab:isRestoring:)")
93
94
var profile: TabManagerMockProfile!
95
var manager: TabManager!
96
var delegate: MockTabManagerDelegate!
97
98
override func setUp() {
99
super.setUp()
100
101
profile = TabManagerMockProfile()
102
manager = TabManager(profile: profile, imageStore: nil)
103
delegate = MockTabManagerDelegate()
104
}
105
106
override func tearDown() {
107
profile._shutdown()
108
manager.removeDelegate(delegate)
109
manager.removeAll()
110
111
super.tearDown()
112
}
113
114
func testAddTabShouldAddOneNormalTab() {
115
manager.addDelegate(delegate)
116
delegate.expect([didAdd])
117
manager.addTab()
118
delegate.verify("Not all delegate methods were called")
119
XCTAssertEqual(manager.normalTabs.count, 1, "There should be one normal tab")
120
}
121
122
func testAddTabShouldAddOnePrivateTab() {
123
manager.addDelegate(delegate)
124
delegate.expect([didAdd])
125
manager.addTab(isPrivate: true)
126
delegate.verify("Not all delegate methods were called")
127
XCTAssertEqual(manager.privateTabs.count, 1, "There should be one private tab")
128
}
129
130
func testAddTabAndSelect() {
131
manager.selectTab(manager.addTab())
132
XCTAssertEqual(manager.selectedIndex, 0, "There should be selected first tab")
133
}
134
135
func testMoveTabFromLastToFirstPosition() {
136
// add two tabs, last one will be selected
137
manager.selectTab(manager.addTab())
138
manager.moveTab(isPrivate: false, fromIndex: 1, toIndex: 0)
139
XCTAssertEqual(manager.selectedIndex, 0, "There should be selected second tab")
140
}
141
142
func testDidDeleteLastTab() {
143
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
144
XCTAssertNotNil(tabs[0])
145
XCTAssertNotNil(tabs[1])
146
}
147
148
// create the tab before adding the mock delegate. So we don't have to check delegate calls we dont care about
149
let tab = manager.addTab()
150
manager.selectTab(tab)
151
manager.addDelegate(delegate)
152
// it wont call didSelect because addTabAndSelect did not pass last removed tab
153
delegate.expect([didRemove, didAdd, didSelect])
154
manager.removeTabAndUpdateSelectedIndex(tab)
155
delegate.verify("Not all delegate methods were called")
156
}
157
158
func testDidDeleteLastPrivateTab() {
159
//create the tab before adding the mock delegate. So we don't have to check delegate calls we dont care about
160
let tab = manager.addTab()
161
manager.selectTab(tab)
162
let privateTab = manager.addTab(isPrivate: true)
163
manager.selectTab(privateTab)
164
manager.addDelegate(delegate)
165
166
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
167
let next = tabs[0]!
168
let previous = tabs[1]!
169
XCTAssertTrue(previous != next)
170
XCTAssertTrue(previous == privateTab)
171
XCTAssertTrue(next == tab)
172
XCTAssertTrue(previous.isPrivate)
173
XCTAssertTrue(self.manager.selectedTab == next)
174
}
175
delegate.expect([didRemove, didSelect])
176
manager.removeTabAndUpdateSelectedIndex(privateTab)
177
delegate.verify("Not all delegate methods were called")
178
}
179
180
func testDidCreateNormalTabWhenDeletingAll() {
181
let removeAllTabs = MethodSpy(functionName: "tabManagerDidRemoveAllTabs(_:toast:)")
182
183
//create the tab before adding the mock delegate. So we don't have to check delegate calls we dont care about
184
let tab = manager.addTab()
185
manager.selectTab(tab)
186
let privateTab = manager.addTab(isPrivate: true)
187
manager.selectTab(privateTab)
188
manager.addDelegate(delegate)
189
190
191
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { _ in
192
// test fails if this not called
193
}
194
195
// This test makes sure that a normal tab is always added even when a normal tab is not selected when calling removeAll
196
delegate.expect([didRemove, didAdd, didSelect, removeAllTabs])
197
198
manager.removeTabsWithUndoToast(manager.normalTabs)
199
delegate.verify("Not all delegate methods were called")
200
}
201
202
func testDeletePrivateTabsOnExit() {
203
profile.prefs.setBool(true, forKey: "settings.closePrivateTabs")
204
205
// create one private and one normal tab
206
let tab = manager.addTab()
207
manager.selectTab(tab)
208
manager.selectTab(manager.addTab(isPrivate: true))
209
210
XCTAssertEqual(manager.selectedTab?.isPrivate, true, "The selected tab should be the private tab")
211
XCTAssertEqual(manager.privateTabs.count, 1, "There should only be one private tab")
212
213
manager.selectTab(tab)
214
XCTAssertEqual(manager.privateTabs.count, 0, "If the normal tab is selected the private tab should have been deleted")
215
XCTAssertEqual(manager.normalTabs.count, 1, "The regular tab should stil be around")
216
217
manager.selectTab(manager.addTab(isPrivate: true))
218
XCTAssertEqual(manager.privateTabs.count, 1, "There should be one new private tab")
219
manager.willSwitchTabMode(leavingPBM: true)
220
XCTAssertEqual(manager.privateTabs.count, 0, "After willSwitchTabMode there should be no more private tabs")
221
222
manager.selectTab(manager.addTab(isPrivate: true))
223
manager.selectTab(manager.addTab(isPrivate: true))
224
XCTAssertEqual(manager.privateTabs.count, 2, "Private tabs should not be deleted when another one is added")
225
manager.selectTab(manager.addTab())
226
XCTAssertEqual(manager.privateTabs.count, 0, "But once we add a normal tab we've switched out of private mode. Private tabs should be deleted")
227
XCTAssertEqual(manager.normalTabs.count, 2, "The original normal tab and the new one should both still exist")
228
229
profile.prefs.setBool(false, forKey: "settings.closePrivateTabs")
230
manager.selectTab(manager.addTab(isPrivate: true))
231
manager.selectTab(tab)
232
XCTAssertEqual(manager.selectedTab?.isPrivate, false, "The selected tab should not be private")
233
XCTAssertEqual(manager.privateTabs.count, 1, "If the flag is false then private tabs should still exist")
234
}
235
236
func testTogglePBMDelete() {
237
profile.prefs.setBool(true, forKey: "settings.closePrivateTabs")
238
239
let tab = manager.addTab()
240
manager.selectTab(tab)
241
manager.selectTab(manager.addTab())
242
manager.selectTab(manager.addTab(isPrivate: true))
243
244
manager.willSwitchTabMode(leavingPBM: false)
245
XCTAssertEqual(manager.privateTabs.count, 1, "There should be 1 private tab")
246
manager.willSwitchTabMode(leavingPBM: true)
247
XCTAssertEqual(manager.privateTabs.count, 0, "There should be 0 private tab")
248
manager.removeTabAndUpdateSelectedIndex(tab)
249
XCTAssertEqual(manager.normalTabs.count, 1, "There should be 1 normal tab")
250
}
251
252
func testRemoveNonSelectedTab() {
253
254
let tab = manager.addTab()
255
manager.selectTab(tab)
256
manager.addTab()
257
let deleteTab = manager.addTab()
258
259
manager.removeTabAndUpdateSelectedIndex(deleteTab)
260
XCTAssertEqual(tab, manager.selectedTab)
261
XCTAssertFalse(manager.tabs.contains(deleteTab))
262
}
263
264
func testDeleteSelectedTab() {
265
266
func addTab(_ visit: Bool) -> Tab {
267
let tab = manager.addTab()
268
if visit {
269
tab.lastExecutedTime = Date.now()
270
}
271
return tab
272
}
273
274
let tab0 = addTab(false) // not visited
275
let tab1 = addTab(true)
276
let tab2 = addTab(true)
277
let tab3 = addTab(true)
278
let tab4 = addTab(false) // not visited
279
280
// starting at tab1, we should be selecting
281
// [ tab3, tab4, tab2, tab0 ]
282
283
manager.selectTab(tab1)
284
tab1.parent = tab3
285
manager.removeTabAndUpdateSelectedIndex(manager.selectedTab!)
286
// Rule: parent tab if it was the most recently visited
287
XCTAssertEqual(manager.selectedTab, tab3)
288
289
manager.removeTabAndUpdateSelectedIndex(manager.selectedTab!)
290
// Rule: next to the right.
291
XCTAssertEqual(manager.selectedTab, tab4)
292
293
manager.removeTabAndUpdateSelectedIndex(manager.selectedTab!)
294
// Rule: next to the left, when none to the right
295
XCTAssertEqual(manager.selectedTab, tab2)
296
297
manager.removeTabAndUpdateSelectedIndex(manager.selectedTab!)
298
// Rule: last one left.
299
XCTAssertEqual(manager.selectedTab, tab0)
300
}
301
302
func testDeleteLastTab() {
303
304
//create the tab before adding the mock delegate. So we don't have to check delegate calls we dont care about
305
(0..<10).forEach {_ in manager.addTab() }
306
manager.selectTab(manager.tabs.last)
307
let deleteTab = manager.tabs.last
308
let newSelectedTab = manager.tabs[8]
309
manager.addDelegate(delegate)
310
311
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
312
let next = tabs[0]!
313
let previous = tabs[1]!
314
XCTAssertEqual(deleteTab, previous)
315
XCTAssertEqual(next, newSelectedTab)
316
}
317
delegate.expect([didRemove, didSelect])
318
manager.removeTabAndUpdateSelectedIndex(manager.tabs.last!)
319
320
delegate.verify("Not all delegate methods were called")
321
}
322
323
func testDelegatesCalledWhenRemovingPrivateTabs() {
324
//setup
325
profile.prefs.setBool(true, forKey: "settings.closePrivateTabs")
326
327
// create one private and one normal tab
328
let tab = manager.addTab()
329
let newTab = manager.addTab()
330
manager.selectTab(tab)
331
manager.selectTab(manager.addTab(isPrivate: true))
332
manager.addDelegate(delegate)
333
334
// Double check a few things
335
XCTAssertEqual(manager.selectedTab?.isPrivate, true, "The selected tab should be the private tab")
336
XCTAssertEqual(manager.privateTabs.count, 1, "There should only be one private tab")
337
338
// switch to normal mode. Which should delete the private tabs
339
manager.willSwitchTabMode(leavingPBM: true)
340
341
//make sure tabs are cleared properly and indexes are reset
342
XCTAssertEqual(manager.privateTabs.count, 0, "Private tab should have been deleted")
343
XCTAssertEqual(manager.selectedIndex, -1, "The selected index should have been reset")
344
345
// didSelect should still be called when switching between a nil tab
346
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
347
XCTAssertNil(tabs[1], "there should be no previous tab")
348
let next = tabs[0]!
349
XCTAssertFalse(next.isPrivate)
350
}
351
352
// make sure delegate method is actually called
353
delegate.expect([didSelect])
354
355
// select the new tab to trigger the delegate methods
356
manager.selectTab(newTab)
357
358
// check
359
delegate.verify("Not all delegate methods were called")
360
}
361
362
func testDeleteFirstTab() {
363
364
//create the tab before adding the mock delegate. So we don't have to check delegate calls we dont care about
365
(0..<10).forEach {_ in manager.addTab() }
366
manager.selectTab(manager.tabs.first)
367
let deleteTab = manager.tabs.first
368
let newSelectedTab = manager.tabs[1]
369
manager.addDelegate(delegate)
370
371
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
372
let next = tabs[0]!
373
let previous = tabs[1]!
374
XCTAssertEqual(deleteTab, previous)
375
XCTAssertEqual(next, newSelectedTab)
376
}
377
delegate.expect([didRemove, didSelect])
378
manager.removeTabAndUpdateSelectedIndex(manager.tabs.first!)
379
delegate.verify("Not all delegate methods were called")
380
}
381
382
func testRemoveTabSelectedTabShouldChangeIndex() {
383
384
let tab1 = manager.addTab()
385
manager.addTab()
386
let tab3 = manager.addTab()
387
388
manager.selectTab(tab3)
389
let beforeRemoveTabIndex = manager.selectedIndex
390
manager.removeTabAndUpdateSelectedIndex(tab1)
391
392
XCTAssertNotEqual(manager.selectedIndex, beforeRemoveTabIndex)
393
XCTAssertEqual(manager.selectedTab, tab3)
394
XCTAssertEqual(manager.tabs[manager.selectedIndex], tab3)
395
}
396
397
func testRemoveTabRemovingLastNormalTabShouldNotSwitchToPrivateTab() {
398
399
let tab0 = manager.addTab()
400
let tab1 = manager.addTab(isPrivate: true)
401
402
manager.selectTab(tab0)
403
// select private tab, so we are in privateMode
404
manager.selectTab(tab1, previous: tab0)
405
// if we are able to remove normal tab this means we are no longer in private mode
406
manager.removeTabAndUpdateSelectedIndex(tab0)
407
408
// manager should creat new tab and select it
409
XCTAssertNotEqual(manager.selectedTab, tab1)
410
XCTAssertNotEqual(manager.selectedIndex, manager.tabs.firstIndex(of: tab1))
411
}
412
413
func testRemoveAllShouldRemoveAllTabs() {
414
415
let tab0 = manager.addTab()
416
let tab1 = manager.addTab()
417
418
manager.removeAll()
419
XCTAssert(nil == manager.tabs.firstIndex(of: tab0))
420
XCTAssert(nil == manager.tabs.firstIndex(of: tab1))
421
}
422
423
// Private tabs and regular tabs are in the same tabs array.
424
// Make sure that when a private tab is added inbetween regular tabs it isnt accidently selected when removing a regular tab
425
func testTabsIndex() {
426
// We add 2 tabs. Then a private one before adding another normal tab and selecting it.
427
// Make sure that when the last one is deleted we dont switch to the private tab
428
let (_, _, privateOne, last) = (manager.addTab(), manager.addTab(), manager.addTab(isPrivate: true), manager.addTab())
429
manager.selectTab(last)
430
manager.addDelegate(delegate)
431
432
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
433
let next = tabs[0]!
434
let previous = tabs[1]!
435
XCTAssertEqual(last, previous)
436
XCTAssert(next != privateOne && !next.isPrivate)
437
}
438
delegate.expect([didRemove, didSelect])
439
manager.removeTabAndUpdateSelectedIndex(last)
440
441
delegate.verify("Not all delegate methods were called")
442
}
443
444
func testRemoveTabAndUpdateSelectedIndexIsSelectedParentTabAfterRemoval() {
445
446
func addTab(_ visit: Bool) -> Tab {
447
let tab = manager.addTab()
448
if visit {
449
tab.lastExecutedTime = Date.now()
450
}
451
return tab
452
}
453
let _ = addTab(false) // not visited
454
let tab1 = addTab(true)
455
let _ = addTab(true)
456
let tab3 = addTab(true)
457
let _ = addTab(false) // not visited
458
459
manager.selectTab(tab1)
460
tab1.parent = tab3
461
manager.removeTabAndUpdateSelectedIndex(tab1)
462
463
XCTAssertEqual(manager.selectedTab, tab3)
464
}
465
466
func testTabsIndexClosingFirst() {
467
468
// We add 2 tabs. Then a private one before adding another normal tab and selecting the first.
469
// Make sure that when the last one is deleted we dont switch to the private tab
470
let deleted = manager.addTab()
471
let newSelected = manager.addTab()
472
manager.addTab(isPrivate: true)
473
manager.addTab()
474
manager.selectTab(manager.tabs.first)
475
manager.addDelegate(delegate)
476
477
let didSelect = MethodSpy(functionName: spyDidSelectedTabChange) { tabs in
478
let next = tabs[0]!
479
let previous = tabs[1]!
480
XCTAssertEqual(deleted, previous)
481
XCTAssertEqual(next, newSelected)
482
}
483
delegate.expect([didRemove, didSelect])
484
manager.removeTabAndUpdateSelectedIndex(manager.tabs.first!)
485
delegate.verify("Not all delegate methods were called")
486
}
487
488
func testUndoCloseTabsRemovesAutomaticallyCreatedNonPrivateTab() {
489
490
let tab = manager.addTab()
491
let tabToSave = Tab(bvc: BrowserViewController.foregroundBVC(), configuration: WKWebViewConfiguration())
492
tabToSave.sessionData = SessionData(currentPage: 0, urls: [URL(string: "url")!], lastUsedTime: Date.now())
493
guard let savedTab = SavedTab(tab: tabToSave, isSelected: true) else {
494
XCTFail("Failed to serialize tab")
495
return
496
}
497
manager.recentlyClosedForUndo = [savedTab]
498
manager.undoCloseTabs()
499
XCTAssertNotEqual(manager.tabs.first, tab)
500
}
501
}