Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
|
|
@ -0,0 +1,32 @@
|
|||
class MockRecentlyDeletedCategoriesManager: NSObject, RecentlyDeletedCategoriesManager, BookmarksObservable {
|
||||
|
||||
var categories = [RecentlyDeletedCategory]()
|
||||
|
||||
func recentlyDeletedCategoriesCount() -> UInt64 {
|
||||
UInt64(categories.count)
|
||||
}
|
||||
|
||||
func getRecentlyDeletedCategories() -> [RecentlyDeletedCategory] {
|
||||
categories
|
||||
}
|
||||
|
||||
func deleteFile(at urls: [URL]) {
|
||||
categories.removeAll { urls.contains($0.fileURL) }
|
||||
}
|
||||
|
||||
func deleteAllRecentlyDeletedCategories() {
|
||||
categories.removeAll()
|
||||
}
|
||||
|
||||
func recoverRecentlyDeletedCategories(at urls: [URL]) {
|
||||
categories.removeAll { urls.contains($0.fileURL) }
|
||||
}
|
||||
|
||||
func deleteRecentlyDeletedCategory(at urls: [URL]) {
|
||||
categories.removeAll { urls.contains($0.fileURL) }
|
||||
}
|
||||
|
||||
func add(_ observer: any BookmarksObserver) {}
|
||||
|
||||
func remove(_ observer: any BookmarksObserver) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class RecentlyDeletedCategoriesViewModelTests: XCTestCase {
|
||||
var viewModel: RecentlyDeletedCategoriesViewModel!
|
||||
var bookmarksManagerMock: MockRecentlyDeletedCategoriesManager!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
bookmarksManagerMock = MockRecentlyDeletedCategoriesManager()
|
||||
setupBookmarksManagerStubs()
|
||||
|
||||
viewModel = RecentlyDeletedCategoriesViewModel(bookmarksManager: bookmarksManagerMock)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
viewModel = nil
|
||||
bookmarksManagerMock = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
private func setupBookmarksManagerStubs() {
|
||||
bookmarksManagerMock.categories = [
|
||||
RecentlyDeletedCategory(title: "test1", fileURL: URL(string: "test1")!, deletionDate: Date()),
|
||||
RecentlyDeletedCategory(title: "test2", fileURL: URL(string: "test2")!, deletionDate: Date()),
|
||||
RecentlyDeletedCategory(title: "lol", fileURL: URL(string: "lol")!, deletionDate: Date()),
|
||||
RecentlyDeletedCategory(title: "te1", fileURL: URL(string: "te1")!, deletionDate: Date()),
|
||||
]
|
||||
}
|
||||
|
||||
func testInitializationFetchesCategories() {
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, Int(bookmarksManagerMock.recentlyDeletedCategoriesCount()))
|
||||
}
|
||||
|
||||
// MARK: - Selection Tests
|
||||
func testMultipleSelectionAndDeselection() {
|
||||
viewModel.selectAllCategories()
|
||||
let initialSelectedCount = viewModel.selectedIndexPaths.count
|
||||
XCTAssertEqual(initialSelectedCount, viewModel.filteredDataSource.flatMap { $0.content }.count)
|
||||
|
||||
viewModel.deselectAllCategories()
|
||||
XCTAssertTrue(viewModel.selectedIndexPaths.isEmpty)
|
||||
}
|
||||
|
||||
func testSelectAndDeselectSpecificCategory() {
|
||||
let specificIndexPath = IndexPath(row: 0, section: 0)
|
||||
viewModel.selectCategory(at: specificIndexPath)
|
||||
XCTAssertTrue(viewModel.selectedIndexPaths.contains(specificIndexPath))
|
||||
|
||||
viewModel.deselectCategory(at: specificIndexPath)
|
||||
XCTAssertFalse(viewModel.selectedIndexPaths.contains(specificIndexPath))
|
||||
}
|
||||
|
||||
func testSelectAndDeselectSpecificCategories() {
|
||||
let indexPath1 = IndexPath(row: 0, section: 0)
|
||||
let indexPath2 = IndexPath(row: 1, section: 0)
|
||||
let indexPath3 = IndexPath(row: 2, section: 0)
|
||||
viewModel.selectCategory(at: indexPath1)
|
||||
viewModel.selectCategory(at: indexPath2)
|
||||
viewModel.selectCategory(at: indexPath3)
|
||||
XCTAssertTrue(viewModel.selectedIndexPaths.contains(indexPath1))
|
||||
XCTAssertTrue(viewModel.selectedIndexPaths.contains(indexPath2))
|
||||
XCTAssertTrue(viewModel.selectedIndexPaths.contains(indexPath3))
|
||||
|
||||
viewModel.deselectCategory(at: indexPath1)
|
||||
XCTAssertFalse(viewModel.selectedIndexPaths.contains(indexPath1))
|
||||
XCTAssertEqual(viewModel.state, .someSelected)
|
||||
|
||||
viewModel.deselectCategory(at: indexPath2)
|
||||
viewModel.deselectCategory(at: indexPath3)
|
||||
XCTAssertEqual(viewModel.selectedIndexPaths.count, .zero)
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
}
|
||||
|
||||
func testStateChangesOnSelection() {
|
||||
let indexPath = IndexPath(row: 1, section: 0)
|
||||
viewModel.selectCategory(at: indexPath)
|
||||
XCTAssertEqual(viewModel.state, .someSelected)
|
||||
|
||||
viewModel.deselectCategory(at: indexPath)
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
}
|
||||
|
||||
func testStateChangesOnDone() {
|
||||
let indexPath = IndexPath(row: 1, section: 0)
|
||||
viewModel.selectCategory(at: indexPath)
|
||||
XCTAssertEqual(viewModel.state, .someSelected)
|
||||
|
||||
viewModel.cancelSelecting()
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, Int(bookmarksManagerMock.recentlyDeletedCategoriesCount()))
|
||||
}
|
||||
|
||||
// MARK: - Searching Tests
|
||||
func testSearchWithEmptyString() {
|
||||
viewModel.search("")
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, 4)
|
||||
}
|
||||
|
||||
func testSearchWithNoResults() {
|
||||
viewModel.search("xyz") // Assuming "xyz" matches no category names
|
||||
XCTAssertTrue(viewModel.filteredDataSource.allSatisfy { $0.content.isEmpty })
|
||||
}
|
||||
|
||||
func testCancelSearchRestoresDataSource() {
|
||||
let searchText = "test"
|
||||
viewModel.search(searchText)
|
||||
XCTAssertEqual(viewModel.state, .searching)
|
||||
XCTAssertTrue(viewModel.filteredDataSource.allSatisfy { $0.content.allSatisfy { $0.title.localizedCaseInsensitiveContains(searchText) } })
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, 2)
|
||||
|
||||
viewModel.cancelSearching()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, 4)
|
||||
}
|
||||
|
||||
// MARK: - Deletion Tests
|
||||
func testDeleteCategory() {
|
||||
let initialCount = bookmarksManagerMock.categories.count
|
||||
viewModel.deleteCategory(at: IndexPath(row: 0, section: 0))
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, initialCount - 1)
|
||||
}
|
||||
|
||||
func testDeleteAllWhenNoOneIsSelected() {
|
||||
viewModel.deleteSelectedCategories()
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, .zero)
|
||||
}
|
||||
|
||||
func testDeleteAllWhenNoSoneAreSelected() {
|
||||
viewModel.selectCategory(at: IndexPath(row: 0, section: 0))
|
||||
viewModel.selectCategory(at: IndexPath(row: 1, section: 0))
|
||||
viewModel.deleteSelectedCategories()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, 2)
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, 2)
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, Int(bookmarksManagerMock.recentlyDeletedCategoriesCount()))
|
||||
}
|
||||
|
||||
// MARK: - Recovery Tests
|
||||
func testRecoverCategory() {
|
||||
viewModel.recoverCategory(at: IndexPath(row: 0, section: 0))
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, 3)
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
}
|
||||
|
||||
func testRecoverAll() {
|
||||
viewModel.recoverSelectedCategories()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, 0)
|
||||
}
|
||||
|
||||
func testRecoverAllWhenSomeAreSelected() {
|
||||
viewModel.selectCategory(at: IndexPath(row: 0, section: 0))
|
||||
viewModel.selectCategory(at: IndexPath(row: 1, section: 0))
|
||||
viewModel.recoverSelectedCategories()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
XCTAssertEqual(bookmarksManagerMock.categories.count, 2)
|
||||
XCTAssertEqual(viewModel.filteredDataSource.flatMap { $0.content }.count, Int(bookmarksManagerMock.recentlyDeletedCategoriesCount()))
|
||||
}
|
||||
|
||||
func testSearchFiltersCategories() {
|
||||
var searchText = "test"
|
||||
viewModel.search(searchText)
|
||||
XCTAssertEqual(viewModel.state, .searching)
|
||||
XCTAssertTrue(viewModel.filteredDataSource.allSatisfy { $0.content.allSatisfy { $0.title.localizedCaseInsensitiveContains(searchText) } })
|
||||
|
||||
searchText = "te"
|
||||
viewModel.search(searchText)
|
||||
XCTAssertEqual(viewModel.state, .searching)
|
||||
XCTAssertTrue(viewModel.filteredDataSource.allSatisfy { $0.content.allSatisfy { $0.title.localizedCaseInsensitiveContains(searchText) } })
|
||||
}
|
||||
|
||||
func testDeleteAllCategories() {
|
||||
viewModel.deleteSelectedCategories()
|
||||
XCTAssertTrue(bookmarksManagerMock.categories.isEmpty)
|
||||
}
|
||||
|
||||
func testRecoverAllCategories() {
|
||||
viewModel.recoverSelectedCategories()
|
||||
XCTAssertTrue(bookmarksManagerMock.categories.isEmpty)
|
||||
}
|
||||
|
||||
func testDeleteAndRecoverAllCategoriesWhenEmpty() {
|
||||
bookmarksManagerMock.categories = []
|
||||
viewModel.fetchRecentlyDeletedCategories()
|
||||
viewModel.deleteSelectedCategories()
|
||||
viewModel.recoverSelectedCategories()
|
||||
XCTAssertTrue(viewModel.filteredDataSource.isEmpty)
|
||||
}
|
||||
|
||||
func testMultipleStateTransitions() {
|
||||
viewModel.startSelecting()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
|
||||
viewModel.startSearching()
|
||||
XCTAssertEqual(viewModel.state, .searching)
|
||||
|
||||
viewModel.cancelSearching()
|
||||
viewModel.cancelSelecting()
|
||||
XCTAssertEqual(viewModel.state, .nothingSelected)
|
||||
}
|
||||
}
|
||||
40
iphone/Maps/Tests/Classes/CarPlay/CarPlayServiceTests.swift
Normal file
40
iphone/Maps/Tests/Classes/CarPlay/CarPlayServiceTests.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class CarPlayServiceTests: XCTestCase {
|
||||
|
||||
var carPlayService: CarPlayService!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
carPlayService = CarPlayService()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
carPlayService = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testCreateEstimates() {
|
||||
let routeInfo = RouteInfo(timeToTarget: 100,
|
||||
targetDistance: 25.2,
|
||||
targetUnitsIndex: 1, // km
|
||||
distanceToTurn: 0.5,
|
||||
turnUnitsIndex: 0, // m
|
||||
streetName: "Niamiha",
|
||||
turnImageName: nil,
|
||||
nextTurnImageName: nil,
|
||||
speedMps: 40.5,
|
||||
speedLimitMps: 60,
|
||||
roundExitNumber: 0)
|
||||
let estimates = carPlayService.createEstimates(routeInfo: routeInfo)
|
||||
|
||||
guard let estimates else {
|
||||
XCTFail("Estimates should not be nil.")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(estimates.distanceRemaining, Measurement<UnitLength>(value: 25.2, unit: .kilometers))
|
||||
XCTAssertEqual(estimates.timeRemaining, 100)
|
||||
}
|
||||
}
|
||||
28
iphone/Maps/Tests/Core/TextToSpeech/MWMTextToSpeechTests.mm
Normal file
28
iphone/Maps/Tests/Core/TextToSpeech/MWMTextToSpeechTests.mm
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#import <XCTest/XCTest.h>
|
||||
#import "MWMTextToSpeech+CPP.h"
|
||||
|
||||
@interface MWMTextToSpeechTest : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation MWMTextToSpeechTest
|
||||
|
||||
- (void)testAvailableLanguages {
|
||||
MWMTextToSpeech * tts = [MWMTextToSpeech tts];
|
||||
std::vector<std::pair<std::string, std::string>> langs = tts.availableLanguages;
|
||||
decltype(langs)::value_type const defaultLang = std::make_pair("en-US", "English (United States)");
|
||||
XCTAssertTrue(std::find(langs.begin(), langs.end(), defaultLang) != langs.end());
|
||||
}
|
||||
- (void)testTranslateLocaleWithTwineString {
|
||||
XCTAssertEqual(tts::translateLocale("en"), "English");
|
||||
}
|
||||
|
||||
- (void)testTranslateLocaleWithBcp47String {
|
||||
XCTAssertEqual(tts::translateLocale("en-US"), "English (United States)");
|
||||
}
|
||||
|
||||
- (void)testTranslateLocaleWithUnknownString {
|
||||
XCTAssertEqual(tts::translateLocale("unknown"), "");
|
||||
}
|
||||
|
||||
@end
|
||||
28
iphone/Maps/Tests/Core/TextToSpeech/TTSTesterTest.m
Normal file
28
iphone/Maps/Tests/Core/TextToSpeech/TTSTesterTest.m
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#import <XCTest/XCTest.h>
|
||||
#import "TTSTester.h"
|
||||
|
||||
@interface TTSTesterTest : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation TTSTesterTest
|
||||
|
||||
TTSTester * ttsTester;
|
||||
|
||||
- (void)setUp {
|
||||
ttsTester = [[TTSTester alloc] init];
|
||||
}
|
||||
|
||||
- (void)testTestStringsWithEnglish {
|
||||
XCTAssertTrue([[ttsTester getTestStrings:@"en-US"] containsObject: @"Thank you for using our community-built maps!"]);
|
||||
}
|
||||
|
||||
- (void)testTestStringsWithGerman {
|
||||
XCTAssertTrue([[ttsTester getTestStrings:@"de-DE"] containsObject: @"Danke, dass du unsere von der Community erstellten Karten benutzt!"]);
|
||||
}
|
||||
|
||||
- (void)testTestStringsWithInvalidLanguage {
|
||||
XCTAssertNil([ttsTester getTestStrings:@"xxx"]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class TrackRecordingManagerTests: XCTestCase {
|
||||
|
||||
private var trackRecordingManager: TrackRecordingManager!
|
||||
|
||||
private var mockTrackRecorder: MockTrackRecorder.Type!
|
||||
private var mockLocationService: MockLocationService.Type!
|
||||
private var mockActivityManager: MockTrackRecordingActivityManager!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
mockTrackRecorder = MockTrackRecorder.self
|
||||
mockLocationService = MockLocationService.self
|
||||
mockActivityManager = MockTrackRecordingActivityManager()
|
||||
|
||||
trackRecordingManager = TrackRecordingManager(
|
||||
trackRecorder: mockTrackRecorder,
|
||||
locationService: mockLocationService,
|
||||
activityManager: mockActivityManager
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
trackRecordingManager = nil
|
||||
mockTrackRecorder.reset()
|
||||
mockLocationService.reset()
|
||||
mockActivityManager = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_GivenInitialSetup_WhenLocationEnabled_ThenStateIsInactive() {
|
||||
mockLocationService.locationIsProhibited = false
|
||||
mockTrackRecorder.trackRecordingIsEnabled = false
|
||||
|
||||
trackRecordingManager.setup()
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
|
||||
}
|
||||
|
||||
func test_GivenInitialSetup_WhenLocationDisabled_ThenShouldHandleErrorAndIncativeState() {
|
||||
mockLocationService.locationIsProhibited = true
|
||||
|
||||
trackRecordingManager.setup()
|
||||
|
||||
XCTAssertTrue(mockLocationService.checkLocationStatusCalled)
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
|
||||
}
|
||||
|
||||
func test_GivenStartRecording_WhenLocationEnabled_ThenSuccess() {
|
||||
mockLocationService.locationIsProhibited = false
|
||||
mockTrackRecorder.trackRecordingIsEnabled = false
|
||||
|
||||
trackRecordingManager.start()
|
||||
|
||||
XCTAssertTrue(mockTrackRecorder.startTrackRecordingCalled)
|
||||
XCTAssertTrue(mockActivityManager.startCalled)
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .active)
|
||||
}
|
||||
|
||||
func test_GivenStartRecording_WhenLocationDisabled_ThenShouldFail() {
|
||||
mockLocationService.locationIsProhibited = true
|
||||
let expectation = expectation(description: "Location is prohibited")
|
||||
trackRecordingManager.start() { result in
|
||||
switch result {
|
||||
case .success:
|
||||
XCTFail("Should not succeed")
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case LocationError.locationIsProhibited:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
XCTFail("Unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
XCTAssertFalse(self.mockTrackRecorder.startTrackRecordingCalled)
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
|
||||
}
|
||||
|
||||
func test_GivenStopRecording_WhenLocationEnabled_ThenSuccess() {
|
||||
mockTrackRecorder.trackRecordingIsEnabled = true
|
||||
mockTrackRecorder.trackRecordingIsEmpty = false
|
||||
|
||||
trackRecordingManager.stopAndSave(withName: "Test Track") { result in
|
||||
switch result {
|
||||
case .success:
|
||||
XCTAssertTrue(true)
|
||||
case .trackIsEmpty:
|
||||
XCTFail("Track should not be empty")
|
||||
}
|
||||
}
|
||||
XCTAssertTrue(mockTrackRecorder.stopTrackRecordingCalled)
|
||||
XCTAssertTrue(mockTrackRecorder.saveTrackRecordingCalled)
|
||||
XCTAssertTrue(mockActivityManager.stopCalled)
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
|
||||
}
|
||||
|
||||
func test_GivenStopRecording_WhenTrackIsEmpty_ThenShouldFail() {
|
||||
mockTrackRecorder.trackRecordingIsEnabled = true
|
||||
mockTrackRecorder.trackRecordingIsEmpty = true
|
||||
let expectation = expectation(description: "Track recording should be empty")
|
||||
trackRecordingManager.stopAndSave(withName: "Test Track") { result in
|
||||
switch result {
|
||||
case .success:
|
||||
XCTFail("Should not succeed")
|
||||
case .trackIsEmpty:
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
XCTAssertFalse(mockTrackRecorder.saveTrackRecordingCalled)
|
||||
XCTAssertTrue(trackRecordingManager.recordingState == .inactive)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mock Classes
|
||||
|
||||
private final class MockTrackRecorder: TrackRecorder {
|
||||
static var trackRecordingIsEnabled = false
|
||||
static var trackRecordingIsEmpty = false
|
||||
static var startTrackRecordingCalled = false
|
||||
static var stopTrackRecordingCalled = false
|
||||
static var saveTrackRecordingCalled = false
|
||||
|
||||
static func reset() {
|
||||
trackRecordingIsEnabled = false
|
||||
trackRecordingIsEmpty = false
|
||||
startTrackRecordingCalled = false
|
||||
stopTrackRecordingCalled = false
|
||||
saveTrackRecordingCalled = false
|
||||
}
|
||||
|
||||
static func isTrackRecordingEnabled() -> Bool {
|
||||
return trackRecordingIsEnabled
|
||||
}
|
||||
|
||||
static func isTrackRecordingEmpty() -> Bool {
|
||||
return trackRecordingIsEmpty
|
||||
}
|
||||
|
||||
static func startTrackRecording() {
|
||||
startTrackRecordingCalled = true
|
||||
trackRecordingIsEnabled = true
|
||||
}
|
||||
|
||||
static func stopTrackRecording() {
|
||||
stopTrackRecordingCalled = true
|
||||
trackRecordingIsEnabled = false
|
||||
}
|
||||
|
||||
static func saveTrackRecording(withName name: String) {
|
||||
saveTrackRecordingCalled = true
|
||||
}
|
||||
|
||||
static func setTrackRecordingUpdateHandler(_ handler: ((TrackInfo) -> Void)?) {}
|
||||
|
||||
static func trackRecordingElevationInfo() -> ElevationProfileData {
|
||||
ElevationProfileData()
|
||||
}
|
||||
}
|
||||
|
||||
private final class MockLocationService: LocationService {
|
||||
static var locationIsProhibited = false
|
||||
static var checkLocationStatusCalled = false
|
||||
|
||||
static func reset() {
|
||||
locationIsProhibited = false
|
||||
checkLocationStatusCalled = false
|
||||
}
|
||||
|
||||
static func isLocationProhibited() -> Bool {
|
||||
return locationIsProhibited
|
||||
}
|
||||
|
||||
static func checkLocationStatus() {
|
||||
checkLocationStatusCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
final class MockTrackRecordingActivityManager: TrackRecordingActivityManager {
|
||||
var startCalled = false
|
||||
var stopCalled = false
|
||||
|
||||
func start(with info: TrackInfo) throws {
|
||||
startCalled = true
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopCalled = true
|
||||
}
|
||||
|
||||
func update(_ info: TrackInfo) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class DefaultLocalDirectoryMonitorTests: XCTestCase {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
var directoryMonitor: FileSystemDispatchSourceMonitor!
|
||||
var mockDelegate: LocalDirectoryMonitorDelegateMock!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
// Setup with a temporary directory and a mock delegate
|
||||
directoryMonitor = try FileSystemDispatchSourceMonitor(fileManager: fileManager, directory: tempDirectory)
|
||||
mockDelegate = LocalDirectoryMonitorDelegateMock()
|
||||
directoryMonitor.delegate = mockDelegate
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
directoryMonitor.stop()
|
||||
mockDelegate = nil
|
||||
try? fileManager.removeItem(at: tempDirectory)
|
||||
try super.tearDownWithError()
|
||||
}
|
||||
|
||||
func testInitialization() {
|
||||
XCTAssertEqual(directoryMonitor.directory, tempDirectory, "Monitor initialized with incorrect directory.")
|
||||
XCTAssertTrue(directoryMonitor.state == .stopped, "Monitor should be stopped initially.")
|
||||
}
|
||||
|
||||
func testStartMonitoring() {
|
||||
let startExpectation = expectation(description: "Start monitoring")
|
||||
directoryMonitor.start { result in
|
||||
switch result {
|
||||
case .success:
|
||||
XCTAssertTrue(self.directoryMonitor.state == .started, "Monitor should be started.")
|
||||
case .failure(let error):
|
||||
XCTFail("Monitoring failed to start with error: \(error)")
|
||||
}
|
||||
startExpectation.fulfill()
|
||||
}
|
||||
wait(for: [startExpectation], timeout: 5.0)
|
||||
}
|
||||
|
||||
func testStopMonitoring() {
|
||||
directoryMonitor.start()
|
||||
directoryMonitor.stop()
|
||||
XCTAssertTrue(directoryMonitor.state == .stopped, "Monitor should be stopped.")
|
||||
}
|
||||
|
||||
func testPauseAndResumeMonitoring() {
|
||||
directoryMonitor.start()
|
||||
directoryMonitor.pause()
|
||||
XCTAssertTrue(directoryMonitor.state == .paused, "Monitor should be paused.")
|
||||
|
||||
directoryMonitor.resume()
|
||||
XCTAssertTrue(directoryMonitor.state == .started, "Monitor should be started.")
|
||||
}
|
||||
|
||||
func testDelegateDidFinishGathering() {
|
||||
mockDelegate.didFinishGatheringExpectation = expectation(description: "didFinishGathering called")
|
||||
directoryMonitor.start()
|
||||
wait(for: [mockDelegate.didFinishGatheringExpectation!], timeout: 5.0)
|
||||
}
|
||||
|
||||
func testDelegateDidReceiveError() {
|
||||
mockDelegate.didReceiveErrorExpectation = expectation(description: "didReceiveLocalMonitorError called")
|
||||
|
||||
let error = NSError(domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, userInfo: nil)
|
||||
directoryMonitor.delegate?.didReceiveLocalMonitorError(error)
|
||||
|
||||
wait(for: [mockDelegate.didReceiveErrorExpectation!], timeout: 1.0)
|
||||
}
|
||||
|
||||
func testContentUpdateDetection() {
|
||||
let startExpectation = expectation(description: "Start monitoring")
|
||||
let didFinishGatheringExpectation = expectation(description: "didFinishGathering called")
|
||||
let didUpdateExpectation = expectation(description: "didUpdate called")
|
||||
|
||||
mockDelegate.didFinishGatheringExpectation = didFinishGatheringExpectation
|
||||
mockDelegate.didUpdateExpectation = didUpdateExpectation
|
||||
|
||||
directoryMonitor.start { result in
|
||||
if case .success = result {
|
||||
XCTAssertTrue(self.directoryMonitor.state == .started, "Monitor should be started.")
|
||||
}
|
||||
startExpectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [startExpectation], timeout: 5)
|
||||
|
||||
let fileURL = tempDirectory.appendingPathComponent("test.kml")
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.fileManager.createFile(atPath: fileURL.path, contents: Data(), attributes: nil)
|
||||
}
|
||||
|
||||
wait(for: [didFinishGatheringExpectation, didUpdateExpectation], timeout: 20)
|
||||
}
|
||||
|
||||
func testFileWithIncorrectExtension() {
|
||||
let startExpectation = expectation(description: "Start monitoring")
|
||||
let didFinishGatheringExpectation = expectation(description: "didFinishGathering called")
|
||||
mockDelegate.didFinishGatheringExpectation = didFinishGatheringExpectation
|
||||
|
||||
let file1URL = tempDirectory.appendingPathComponent("test.kml.tmp")
|
||||
let file2URL = tempDirectory.appendingPathComponent("test2.tmp")
|
||||
let file3URL = tempDirectory.appendingPathComponent("test3.jpg")
|
||||
let correctFileURL = tempDirectory.appendingPathComponent("test.kml")
|
||||
|
||||
let fileData = Data(count: 12)
|
||||
try! fileData.write(to: file1URL, options: .atomic)
|
||||
try! fileData.write(to: file2URL, options: .atomic)
|
||||
try! fileData.write(to: file3URL, options: .atomic)
|
||||
try! fileData.write(to: correctFileURL, options: .atomic)
|
||||
|
||||
directoryMonitor.start { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("Monitoring failed to start with error: \(error)")
|
||||
case .success:
|
||||
XCTAssertTrue(self.directoryMonitor.state == .started, "Monitor should be started.")
|
||||
startExpectation.fulfill()
|
||||
}
|
||||
}
|
||||
wait(for: [startExpectation, didFinishGatheringExpectation], timeout: 5)
|
||||
|
||||
let contents = self.mockDelegate.contents.map { $0.fileUrl }
|
||||
XCTAssertFalse(contents.contains(file1URL), "File with incorrect extension should not be included")
|
||||
XCTAssertFalse(contents.contains(file2URL), "File with incorrect extension should not be included")
|
||||
XCTAssertFalse(contents.contains(file3URL), "File with incorrect extension should not be included")
|
||||
XCTAssertTrue(contents.contains(correctFileURL), "File with correct extension should be included")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
class LocalDirectoryMonitorDelegateMock: LocalDirectoryMonitorDelegate {
|
||||
var contents = LocalContents()
|
||||
|
||||
var didFinishGatheringExpectation: XCTestExpectation?
|
||||
var didUpdateExpectation: XCTestExpectation?
|
||||
var didReceiveErrorExpectation: XCTestExpectation?
|
||||
|
||||
func didFinishGathering(_ contents: LocalContents) {
|
||||
self.contents = contents
|
||||
didFinishGatheringExpectation?.fulfill()
|
||||
}
|
||||
|
||||
func didUpdate(_ contents: LocalContents, _ update: LocalContentsUpdate) {
|
||||
self.contents = contents
|
||||
didUpdateExpectation?.fulfill()
|
||||
}
|
||||
|
||||
func didReceiveLocalMonitorError(_ error: Error) {
|
||||
didReceiveErrorExpectation?.fulfill()
|
||||
}
|
||||
}
|
||||
30
iphone/Maps/Tests/Core/iCloudTests/MetadataItemStubs.swift
Normal file
30
iphone/Maps/Tests/Core/iCloudTests/MetadataItemStubs.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@testable import CoMaps__Debug_
|
||||
|
||||
extension LocalMetadataItem {
|
||||
static func stub(fileName: String,
|
||||
lastModificationDate: TimeInterval) -> LocalMetadataItem {
|
||||
let item = LocalMetadataItem(fileName: fileName,
|
||||
fileUrl: URL(string: "url")!,
|
||||
lastModificationDate: lastModificationDate)
|
||||
return item
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension CloudMetadataItem {
|
||||
static func stub(fileName: String,
|
||||
lastModificationDate: TimeInterval,
|
||||
isDownloaded: Bool = true,
|
||||
percentDownloaded: NSNumber = 100.0,
|
||||
hasUnresolvedConflicts: Bool = false) -> CloudMetadataItem {
|
||||
let item = CloudMetadataItem(fileName: fileName,
|
||||
fileUrl: URL(string: "url")!,
|
||||
isDownloaded: isDownloaded,
|
||||
percentDownloaded: percentDownloaded,
|
||||
lastModificationDate: lastModificationDate,
|
||||
downloadingError: nil,
|
||||
uploadingError: nil,
|
||||
hasUnresolvedConflicts: hasUnresolvedConflicts)
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,573 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class SynchronizationtateManagerTests: XCTestCase {
|
||||
|
||||
var syncStateManager: SynchronizationStateResolver!
|
||||
var outgoingEvents: [OutgoingSynchronizationEvent] = []
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: false)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
syncStateManager = nil
|
||||
outgoingEvents.removeAll()
|
||||
super.tearDown()
|
||||
}
|
||||
// MARK: - Test didFinishGathering without errors and on initial synchronization
|
||||
func testInitialSynchronization() {
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: true)
|
||||
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(3)) // Local only item
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(2)) // Conflicting item
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(4)) // Cloud only item
|
||||
|
||||
let localItems: LocalContents = [localItem1, localItem2]
|
||||
let cloudItems: CloudContents = [cloudItem1, cloudItem3]
|
||||
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems)))
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems)))
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { event in
|
||||
if case .resolveInitialSynchronizationConflict(let item) = event, item == localItem1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, "Expected to resolve initial synchronization conflict for localItem1")
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { event in
|
||||
if case .createLocalItem(let item) = event, item == cloudItem3 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, "Expected to create local item for cloudItem3")
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { event in
|
||||
if case .createCloudItem(let item) = event, item == localItem2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, "Expected to create cloud item for localItem2")
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { event in
|
||||
if case .didFinishInitialSynchronization = event {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, "Expected to finish initial synchronization")
|
||||
}
|
||||
|
||||
func testInitialSynchronizationWithNewerCloudItem() {
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: true)
|
||||
|
||||
let localItem = LocalMetadataItem.stub(fileName: "file", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem = CloudMetadataItem.stub(fileName: "file", lastModificationDate: TimeInterval(2))
|
||||
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringLocalContents([localItem])))
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringCloudContents([cloudItem])))
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { if case .resolveInitialSynchronizationConflict(_) = $0 { return true } else { return false } }, "Expected conflict resolution for a newer cloud item")
|
||||
}
|
||||
|
||||
func testInitialSynchronizationWithNewerLocalItem() {
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: true)
|
||||
|
||||
let localItem = LocalMetadataItem.stub(fileName: "file", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem = CloudMetadataItem.stub(fileName: "file", lastModificationDate: TimeInterval(1))
|
||||
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringLocalContents([localItem])))
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringCloudContents([cloudItem])))
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { if case .resolveInitialSynchronizationConflict(_) = $0 { return true } else { return false } }, "Expected conflict resolution for a newer local item")
|
||||
}
|
||||
|
||||
func testInitialSynchronizationWithNonConflictingItems() {
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: true)
|
||||
|
||||
let localItem = LocalMetadataItem.stub(fileName: "localFile", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem = CloudMetadataItem.stub(fileName: "cloudFile", lastModificationDate: TimeInterval(2))
|
||||
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringLocalContents([localItem])))
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringCloudContents([cloudItem])))
|
||||
|
||||
XCTAssertTrue(outgoingEvents.contains { if case .createLocalItem(_) = $0 { return true } else { return false } }, "Expected creation of local item for cloudFile")
|
||||
XCTAssertTrue(outgoingEvents.contains { if case .createCloudItem(_) = $0 { return true } else { return false } }, "Expected creation of cloud item for localFile")
|
||||
}
|
||||
|
||||
func testInitialSynchronizationWhenCloudFilesAreNotDownloadedTheDownloadingShouldStart () {
|
||||
syncStateManager = iCloudSynchronizationStateResolver(isInitialSynchronization: true)
|
||||
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(3), isDownloaded: false, percentDownloaded: 0.0)
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(4))
|
||||
|
||||
let localItems = LocalContents([localItem1])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems)))
|
||||
outgoingEvents.append(contentsOf: syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems)))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 5)
|
||||
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .resolveInitialSynchronizationConflict(let item):
|
||||
// copy local file with a new name and replace the original with the cloud file
|
||||
XCTAssertEqual(item, localItem1)
|
||||
case .updateLocalItem(let item):
|
||||
XCTAssertEqual(item, cloudItem1)
|
||||
case .startDownloading(let item):
|
||||
XCTAssertEqual(item, cloudItem2)
|
||||
case .createLocalItem(let item):
|
||||
XCTAssertEqual(item, cloudItem3)
|
||||
case .didFinishInitialSynchronization:
|
||||
XCTAssertTrue(event == outgoingEvents.last)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
// update the cloud items with the new downloaded status
|
||||
let cloudItem2Downloaded = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(3))
|
||||
let newCloudItems = [cloudItem1, cloudItem2Downloaded, cloudItem3]
|
||||
let cloudUpdate = CloudContentsUpdate(added: [], updated: [cloudItem2Downloaded], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: newCloudItems, update: cloudUpdate))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createLocalItem(let item):
|
||||
XCTAssertEqual(item, cloudItem2Downloaded)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test didFinishGathering without errors and after initial synchronization
|
||||
func testDidFinishGatheringWhenCloudAndLocalIsEmpty() {
|
||||
let localItems: LocalContents = []
|
||||
let cloudItems: CloudContents = []
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenOnlyCloudIsEmpty() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems: LocalContents = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems: CloudContents = []
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createCloudItem(let item):
|
||||
XCTAssertTrue(localItems.containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenOnlyLocalIsEmpty() {
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents()
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createLocalItem(let item):
|
||||
XCTAssertTrue(cloudItems.containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenTreCloudIsEmpty() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = [localItem1, localItem2, localItem3]
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents([]))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 3)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createCloudItem(let item):
|
||||
XCTAssertTrue(localItems.containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenLocalIsEmpty() {
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents()
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 3)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createLocalItem(let item):
|
||||
XCTAssertTrue(cloudItems.containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenLocalAndCloudAreNotEmptyAndAllFilesEqual() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenLocalAndCloudAreNotEmptyAndSomeLocalItemsAreNewer() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(3))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(4))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 2)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .updateCloudItem(let item):
|
||||
XCTAssertTrue([localItem2, localItem3].containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenLocalAndCloudAreNotEmptyAndSomeCloudItemsAreNewer() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(4))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(7))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 2)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .updateLocalItem(let item):
|
||||
XCTAssertTrue([cloudItem1, cloudItem3].containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenCloudFileNewerThanLocal() {
|
||||
let localItem = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
let cloudItem = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(8))
|
||||
|
||||
let localItems = LocalContents([localItem])
|
||||
let cloudItems = CloudContents([cloudItem])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .updateLocalItem(let item):
|
||||
XCTAssertEqual(item, cloudItem)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidFinishGatheringWhenCloudHaveSameFileBothInTrashedAndNotAndTrashedBotLocalIsNewer() {
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(9))
|
||||
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3Trashed = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(6))
|
||||
|
||||
let localItems = LocalContents([localItem3])
|
||||
let cloudItems = CloudContents([cloudItem3, cloudItem3Trashed])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .updateCloudItem(let item):
|
||||
XCTAssertEqual(item, localItem3)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test didUpdateLocalContents
|
||||
func testDidUpdateLocalContentsWhenContentWasNotChanged() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 3)
|
||||
|
||||
let newLocalItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let update = LocalContentsUpdate(added: [], updated: [], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateLocalContents(contents: newLocalItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
}
|
||||
|
||||
func testDidUpdateLocalContentsWhenNewLocalItemWasAdded() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
let localItem4 = LocalMetadataItem.stub(fileName: "file4", lastModificationDate: TimeInterval(4))
|
||||
let newLocalItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let update = LocalContentsUpdate(added: [localItem4], updated: [], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateLocalContents(contents: newLocalItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createCloudItem(let item):
|
||||
XCTAssertEqual(item, localItem4)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidUpdateLocalContentsWhenLocalItemWasUpdated() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
let localItem2Updated = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(3))
|
||||
let localItem3Updated = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(4))
|
||||
|
||||
let newLocalItems = LocalContents([localItem1, localItem2Updated, localItem3Updated])
|
||||
let update = LocalContentsUpdate(added: [], updated: [localItem2Updated, localItem3Updated], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateLocalContents(contents: newLocalItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 2)
|
||||
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .updateCloudItem(let item):
|
||||
XCTAssertTrue([localItem2Updated, localItem3Updated].containsByName(item))
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDidUpdateLocalContentsWhenLocalItemWasRemoved() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
let newLocalItems = LocalContents([localItem1, localItem2])
|
||||
let update = LocalContentsUpdate(added: [], updated: [], removed: [localItem3])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateLocalContents(contents: newLocalItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .removeCloudItem(let item):
|
||||
XCTAssertEqual(item, cloudItem3)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test didUpdateCloudContents
|
||||
func testDidUpdateCloudContentsWhenContentWasNotChanged() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
let cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
let newCloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
let update = CloudContentsUpdate(added: [], updated: [], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: newCloudItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
}
|
||||
|
||||
func testDidUpdateCloudContentsWhenContentItemWasAdded() {
|
||||
let localItem1 = LocalMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let localItem2 = LocalMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let localItem3 = LocalMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let cloudItem1 = CloudMetadataItem.stub(fileName: "file1", lastModificationDate: TimeInterval(1))
|
||||
let cloudItem2 = CloudMetadataItem.stub(fileName: "file2", lastModificationDate: TimeInterval(2))
|
||||
let cloudItem3 = CloudMetadataItem.stub(fileName: "file3", lastModificationDate: TimeInterval(3))
|
||||
|
||||
let localItems = LocalContents([localItem1, localItem2, localItem3])
|
||||
var cloudItems = CloudContents([cloudItem1, cloudItem2, cloudItem3])
|
||||
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringLocalContents(localItems))
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didFinishGatheringCloudContents(cloudItems))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
var cloudItem4 = CloudMetadataItem.stub(fileName: "file4", lastModificationDate: TimeInterval(3), isDownloaded: false, percentDownloaded: 0.0)
|
||||
cloudItems.append(cloudItem4)
|
||||
var update = CloudContentsUpdate(added: [cloudItem4], updated: [], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: cloudItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .startDownloading(let cloudMetadataItem):
|
||||
XCTAssertEqual(cloudMetadataItem, cloudItem4)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
cloudItem4.percentDownloaded = 50.0
|
||||
update = CloudContentsUpdate(added: [], updated: [cloudItem4], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: cloudItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
cloudItem4.percentDownloaded = 100.0
|
||||
update = CloudContentsUpdate(added: [], updated: [cloudItem4], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: cloudItems, update: update))
|
||||
XCTAssertEqual(outgoingEvents.count, 0)
|
||||
|
||||
cloudItem4.isDownloaded = true
|
||||
// recreate collection
|
||||
cloudItems = [cloudItem1, cloudItem2, cloudItem3, cloudItem4]
|
||||
update = CloudContentsUpdate(added: [], updated: [cloudItem4], removed: [])
|
||||
outgoingEvents = syncStateManager.resolveEvent(.didUpdateCloudContents(contents: cloudItems, update: update))
|
||||
|
||||
XCTAssertEqual(outgoingEvents.count, 1)
|
||||
outgoingEvents.forEach { event in
|
||||
switch event {
|
||||
case .createLocalItem(let cloudMetadataItem):
|
||||
XCTAssertEqual(cloudMetadataItem, cloudItem4)
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
class FileManagerMock: FileManager {
|
||||
var stubUbiquityIdentityToken: UbiquityIdentityToken?
|
||||
var shouldReturnContainerURL: Bool = true
|
||||
var stubCloudDirectory: URL?
|
||||
|
||||
override var ubiquityIdentityToken: (any UbiquityIdentityToken)? {
|
||||
return stubUbiquityIdentityToken
|
||||
}
|
||||
|
||||
override func url(forUbiquityContainerIdentifier identifier: String?) -> URL? {
|
||||
return shouldReturnContainerURL ? stubCloudDirectory ?? URL(fileURLWithPath: NSTemporaryDirectory()) : nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
class UbiquitousDirectoryMonitorDelegateMock: CloudDirectoryMonitorDelegate {
|
||||
var didFinishGatheringCalled = false
|
||||
var didUpdateCalled = false
|
||||
var didReceiveErrorCalled = false
|
||||
|
||||
var didFinishGatheringExpectation: XCTestExpectation?
|
||||
var didUpdateExpectation: XCTestExpectation?
|
||||
var didReceiveErrorExpectation: XCTestExpectation?
|
||||
|
||||
var contents = CloudContents()
|
||||
|
||||
func didFinishGathering(_ contents: CloudContents) {
|
||||
didFinishGatheringCalled = true
|
||||
didFinishGatheringExpectation?.fulfill()
|
||||
self.contents = contents
|
||||
}
|
||||
|
||||
func didUpdate(_ contents: CloudContents, _ update: CloudContentsUpdate) {
|
||||
didUpdateCalled = true
|
||||
didUpdateExpectation?.fulfill()
|
||||
self.contents = contents
|
||||
}
|
||||
|
||||
func didReceiveCloudMonitorError(_ error: Error) {
|
||||
didReceiveErrorCalled = true
|
||||
didReceiveErrorExpectation?.fulfill()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
typealias UbiquityIdentityToken = NSCoding & NSCopying & NSObjectProtocol
|
||||
|
||||
class iCloudDirectoryMonitorTests: XCTestCase {
|
||||
|
||||
var cloudMonitor: iCloudDocumentsMonitor!
|
||||
var mockFileManager: FileManagerMock!
|
||||
var mockDelegate: UbiquitousDirectoryMonitorDelegateMock!
|
||||
var cloudContainerIdentifier: String = "iCloud.app.comaps.debug"
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
mockFileManager = FileManagerMock()
|
||||
mockDelegate = UbiquitousDirectoryMonitorDelegateMock()
|
||||
cloudMonitor = iCloudDocumentsMonitor(fileManager: mockFileManager, cloudContainerIdentifier: cloudContainerIdentifier, fileType: .kml)
|
||||
cloudMonitor.delegate = mockDelegate
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
cloudMonitor = nil
|
||||
mockFileManager = nil
|
||||
mockDelegate = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testInitialization() {
|
||||
XCTAssertNotNil(cloudMonitor)
|
||||
XCTAssertEqual(cloudMonitor.containerIdentifier, cloudContainerIdentifier)
|
||||
}
|
||||
|
||||
func testCloudAvailability() {
|
||||
mockFileManager.stubUbiquityIdentityToken = NSString(string: "mockToken")
|
||||
XCTAssertTrue(cloudMonitor.isCloudAvailable())
|
||||
|
||||
mockFileManager.stubUbiquityIdentityToken = nil
|
||||
XCTAssertFalse(cloudMonitor.isCloudAvailable())
|
||||
}
|
||||
}
|
||||
311
iphone/Maps/Tests/UI/SearchOnMapTests/SearchOnMapTests.swift
Normal file
311
iphone/Maps/Tests/UI/SearchOnMapTests/SearchOnMapTests.swift
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
import XCTest
|
||||
@testable import CoMaps__Debug_
|
||||
|
||||
final class SearchOnMapTests: XCTestCase {
|
||||
|
||||
private var presenter: SearchOnMapPresenter!
|
||||
private var interactor: SearchOnMapInteractor!
|
||||
private var view: SearchOnMapViewMock!
|
||||
private var searchManager: SearchManagerMock.Type!
|
||||
private var currentState: SearchOnMapState = .searching
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
searchManager = SearchManagerMock.self
|
||||
presenter = SearchOnMapPresenter(isRouting: false,
|
||||
didChangeState: { [weak self] in self?.currentState = $0 })
|
||||
interactor = SearchOnMapInteractor(presenter: presenter, searchManager: searchManager)
|
||||
view = SearchOnMapViewMock()
|
||||
presenter.view = view
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
presenter = nil
|
||||
interactor = nil
|
||||
view = nil
|
||||
searchManager.results = .empty
|
||||
searchManager = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func test_GivenViewIsLoading_WhenViewLoads_ThenShowsHistoryAndCategory() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .historyAndCategory)
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
}
|
||||
|
||||
func test_GivenInitialState_WhenSelectCategory_ThenUpdateSearchResultsAndShowMap() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("category", source: .category)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.searchingText, query.text)
|
||||
XCTAssertEqual(view.viewModel.isTyping, false)
|
||||
|
||||
let results = SearchResult.stubResults()
|
||||
searchManager.results = results
|
||||
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .results(results))
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, false)
|
||||
}
|
||||
|
||||
func test_GivenInitialState_WhenTypeText_ThenUpdateSearchResults() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didType(query))
|
||||
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
|
||||
let results = SearchResult.stubResults()
|
||||
searchManager.results = results
|
||||
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .results(results))
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
}
|
||||
|
||||
func test_GivenInitialState_WhenTapSearch_ThenUpdateSearchResultsAndShowMap() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didType(query))
|
||||
|
||||
let results = SearchResult.stubResults()
|
||||
searchManager.results = results
|
||||
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .results(results))
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
|
||||
interactor.handle(.searchButtonDidTap(query))
|
||||
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .results(results))
|
||||
XCTAssertEqual(view.viewModel.searchingText, nil)
|
||||
XCTAssertEqual(view.viewModel.isTyping, false)
|
||||
}
|
||||
|
||||
func test_GivenSearchIsOpened_WhenMapIsDragged_ThenCollapseSearchScreen() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
|
||||
interactor.handle(.didStartDraggingMap)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .compact)
|
||||
}
|
||||
|
||||
func test_GivenSearchIsOpened_WhenModalPresentationScreenIsDragged_ThenDisableTyping() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
|
||||
interactor.handle(.didStartDraggingSearch)
|
||||
XCTAssertEqual(view.viewModel.isTyping, false)
|
||||
}
|
||||
|
||||
func test_GivenResultsOnScreen_WhenSelectResult_ThenHideSearch() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
let results = SearchResult.stubResults()
|
||||
searchManager.results = results
|
||||
|
||||
interactor.handle(.didSelectResult(results[0], withQuery: query))
|
||||
if isiPad {
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
} else {
|
||||
XCTAssertEqual(currentState, .hidden)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
|
||||
}
|
||||
}
|
||||
|
||||
func test_GivenSearchIsActive_WhenSelectPlaceOnMap_ThenHideSearch() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
|
||||
interactor.handle(.didSelectPlaceOnMap)
|
||||
|
||||
if isiPad {
|
||||
XCTAssertNotEqual(view.viewModel.presentationStep, .hidden)
|
||||
} else {
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
|
||||
}
|
||||
}
|
||||
|
||||
func test_GivenSearchIsHidden_WhenPPDeselected_ThenShowSearch() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
let results = SearchResult.stubResults()
|
||||
searchManager.results = results
|
||||
|
||||
interactor.handle(.didSelectResult(results[0], withQuery: query))
|
||||
if isiPad {
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
} else {
|
||||
XCTAssertEqual(currentState, .hidden)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
|
||||
}
|
||||
|
||||
interactor.handle(.didDeselectPlaceOnMap)
|
||||
XCTAssertEqual(currentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
|
||||
}
|
||||
|
||||
func test_GivenSearchIsOpen_WhenCloseSearch_ThenHideSearch() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
|
||||
interactor.handle(.closeSearch)
|
||||
XCTAssertEqual(currentState, .closed)
|
||||
}
|
||||
|
||||
func test_GivenSearchHasText_WhenClearSearch_ThenShowHistoryAndCategory() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
interactor.handle(.clearButtonDidTap)
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .historyAndCategory)
|
||||
XCTAssertEqual(view.viewModel.searchingText, "")
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
}
|
||||
|
||||
func test_GivenSearchExecuted_WhenNoResults_ThenShowNoResults() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("text", source: .typedText)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
searchManager.results = SearchOnMap.SearchResults([])
|
||||
interactor.onSearchCompleted()
|
||||
|
||||
XCTAssertEqual(view.viewModel.contentState, .noResults)
|
||||
}
|
||||
|
||||
func test_GivenSearchIsActive_WhenSelectSuggestion_ThenReplaceWithSuggestion() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("ca", source: .typedText)
|
||||
interactor.handle(.didType(query))
|
||||
|
||||
let result = SearchResult(titleText: "", type: .suggestion, suggestion: "cafe")
|
||||
interactor.handle(.didSelectResult(result, withQuery: query))
|
||||
|
||||
XCTAssertEqual(view.viewModel.searchingText, "cafe")
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
|
||||
XCTAssertEqual(view.viewModel.contentState, .searching)
|
||||
XCTAssertEqual(view.viewModel.isTyping, true)
|
||||
}
|
||||
|
||||
func test_GivenSearchIsActive_WhenPasteDeeplink_ThenShowResult() {
|
||||
interactor.handle(.openSearch)
|
||||
|
||||
let query = SearchQuery("om://search?cll=42.0,44.0&query=Toilet", source: .deeplink)
|
||||
interactor.handle(.didSelect(query))
|
||||
|
||||
let result = SearchResult(titleText: "some result", type: .regular, suggestion: "")
|
||||
let results = SearchOnMap.SearchResults([result])
|
||||
searchManager.results = results
|
||||
interactor.onSearchCompleted()
|
||||
|
||||
XCTAssertEqual(view.viewModel.contentState, .results(results))
|
||||
XCTAssertEqual(view.viewModel.presentationStep, .halfScreen)
|
||||
XCTAssertEqual(view.viewModel.isTyping, false) // No typing when deeplink is used
|
||||
}
|
||||
|
||||
func test_GivenSearchIsActive_WhenPresentationStepUpdate_ThenUpdateSearchMode() {
|
||||
interactor.handle(.openSearch)
|
||||
XCTAssertEqual(searchManager.searchMode(), isiPad ? .everywhereAndViewport : .everywhere)
|
||||
|
||||
interactor.handle(.didUpdatePresentationStep(.halfScreen))
|
||||
XCTAssertEqual(searchManager.searchMode(), .everywhereAndViewport)
|
||||
|
||||
interactor.handle(.didUpdatePresentationStep(.compact))
|
||||
XCTAssertEqual(searchManager.searchMode(), .everywhereAndViewport)
|
||||
|
||||
interactor.handle(.didUpdatePresentationStep(.hidden))
|
||||
XCTAssertEqual(searchManager.searchMode(), .viewport)
|
||||
|
||||
interactor.handle(.didUpdatePresentationStep(.fullScreen))
|
||||
XCTAssertEqual(searchManager.searchMode(), isiPad ? .everywhereAndViewport : .everywhere)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
private class SearchOnMapViewMock: SearchOnMapView {
|
||||
var viewModel: SearchOnMap.ViewModel = .initial
|
||||
var scrollViewDelegate: (any SearchOnMapScrollViewDelegate)?
|
||||
func render(_ viewModel: SearchOnMap.ViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
func close() {
|
||||
}
|
||||
func show() {
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchManagerMock: SearchManager {
|
||||
static var observers = ListenerContainer<MWMSearchObserver>()
|
||||
static var results = SearchOnMap.SearchResults.empty {
|
||||
didSet {
|
||||
observers.forEach { observer in
|
||||
observer.onSearchCompleted?()
|
||||
}
|
||||
}
|
||||
}
|
||||
private static var _searchMode: SearchMode = .everywhere
|
||||
|
||||
static func add(_ observer: any MWMSearchObserver) {
|
||||
self.observers.addListener(observer)
|
||||
}
|
||||
|
||||
static func remove(_ observer: any MWMSearchObserver) {
|
||||
self.observers.removeListener(observer)
|
||||
}
|
||||
|
||||
static func save(_ query: SearchQuery) {}
|
||||
static func searchQuery(_ query: SearchQuery) {}
|
||||
static func showResult(at index: UInt) {}
|
||||
static func clear() {}
|
||||
static func getResults() -> [SearchResult] { results.results }
|
||||
static func searchMode() -> SearchMode { _searchMode }
|
||||
static func setSearchMode(_ mode: SearchMode) { _searchMode = mode }
|
||||
}
|
||||
|
||||
private extension SearchResult {
|
||||
static func stubResults() -> SearchOnMap.SearchResults {
|
||||
SearchOnMap.SearchResults([
|
||||
SearchResult(),
|
||||
SearchResult(),
|
||||
SearchResult()
|
||||
])
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue