mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-20 19:33:08 +09:00
284 lines
11 KiB
Swift
284 lines
11 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import UIKit
|
|
import XCTest
|
|
@testable import Client
|
|
import Shared
|
|
import Storage
|
|
import Deferred
|
|
import SyncTelemetry
|
|
|
|
class ActivityStreamTests: XCTestCase {
|
|
var profile: MockProfile!
|
|
var panel: ActivityStreamPanel!
|
|
var mockPingClient: MockPingClient!
|
|
var telemetry: ActivityStreamTracker!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
self.profile = MockProfile()
|
|
self.telemetry = ActivityStreamTracker(eventsTracker: MockPingClient(), sessionsTracker: MockPingClient())
|
|
self.panel = ActivityStreamPanel(profile: profile, telemetry: self.telemetry)
|
|
}
|
|
|
|
override func tearDown() {
|
|
mockPingClient = nil
|
|
}
|
|
|
|
func testDeletionOfSingleSuggestedSite() {
|
|
let siteToDelete = panel.defaultTopSites()[0]
|
|
|
|
panel.hideURLFromTopSites(siteToDelete)
|
|
let newSites = panel.defaultTopSites()
|
|
|
|
XCTAssertFalse(newSites.contains(siteToDelete, f: { (a, b) -> Bool in
|
|
return a.url == b.url
|
|
}))
|
|
}
|
|
|
|
func testDeletionOfAllDefaultSites() {
|
|
let defaultSites = panel.defaultTopSites()
|
|
defaultSites.forEach({
|
|
panel.hideURLFromTopSites($0)
|
|
})
|
|
|
|
let newSites = panel.defaultTopSites()
|
|
XCTAssertTrue(newSites.isEmpty)
|
|
}
|
|
}
|
|
|
|
// MARK: Telemetry Tests
|
|
extension ActivityStreamTests {
|
|
fileprivate func expectedPayloadForEvent(_ event: String, source: String, position: Int) -> [String: Any] {
|
|
return [
|
|
"event": event,
|
|
"page": "NEW_TAB",
|
|
"source": source,
|
|
"action_position": position,
|
|
"app_version": AppInfo.appVersion,
|
|
"build": AppInfo.buildNumber,
|
|
"locale": Locale.current.identifier,
|
|
"release_channel": AppConstants.BuildChannel.rawValue
|
|
]
|
|
}
|
|
|
|
fileprivate func expectedBadStatePayload(state: String, source: String) -> [String: Any] {
|
|
return [
|
|
"event": state,
|
|
"page": "NEW_TAB",
|
|
"source": source,
|
|
"app_version": AppInfo.appVersion,
|
|
"build": AppInfo.buildNumber,
|
|
"locale": Locale.current.identifier,
|
|
"release_channel": AppConstants.BuildChannel.rawValue
|
|
]
|
|
}
|
|
|
|
fileprivate func assertPayload(_ actual: [String: Any], matches: [String: Any]) {
|
|
XCTAssertTrue(actual.count == matches.count)
|
|
actual.enumerated().forEach { index, element in
|
|
if let actualValue = element.1 as? Int,
|
|
let matchesValue = matches[element.0] as? Int {
|
|
XCTAssertTrue(actualValue == matchesValue)
|
|
}
|
|
|
|
if let actualValue = element.1 as? String,
|
|
let matchesValue = matches[element.0] as? String {
|
|
XCTAssertTrue(actualValue == matchesValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testHighlightEmitsEventOnTap() {
|
|
let mockSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
panel.highlights = [mockSite]
|
|
panel.selectItemAtIndex(0, inSection: .highlights)
|
|
|
|
let pingsSent = (telemetry.eventsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 1)
|
|
let eventPing = pingsSent[0]
|
|
assertPayload(eventPing, matches: expectedPayloadForEvent("CLICK", source: "HIGHLIGHTS", position: 0))
|
|
}
|
|
|
|
func testContextMenuOnTopSiteEmitsRemoveEvent() {
|
|
let mockSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
let topSitesContextMenu = panel.contextMenu(for: mockSite, with: IndexPath(item: 0, section: ActivityStreamPanel.Section.topSites.rawValue))
|
|
|
|
let removeAction = topSitesContextMenu?.actions[0].find { $0.title == Strings.RemoveContextMenuTitle }
|
|
removeAction?.handler?(removeAction!)
|
|
|
|
let pingsSent = (telemetry.eventsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 1)
|
|
let removePing = pingsSent[0]
|
|
assertPayload(removePing, matches: expectedPayloadForEvent("REMOVE", source: "TOP_SITES", position: 0))
|
|
}
|
|
|
|
func testContextMenuOnHighlightsEmitsRemoveDismissEvents() {
|
|
let mockSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
let highlightsContextMenu = panel.contextMenu(for: mockSite, with: IndexPath(row: 0, section: ActivityStreamPanel.Section.highlights.rawValue))
|
|
|
|
let dismiss = highlightsContextMenu?.actions[0].find { $0.title == Strings.RemoveContextMenuTitle }
|
|
let delete = highlightsContextMenu?.actions[0].find { $0.title == Strings.DeleteFromHistoryContextMenuTitle }
|
|
|
|
dismiss?.handler?(dismiss!)
|
|
delete?.handler?(delete!)
|
|
|
|
// Check to see that they emitted telemetry events
|
|
let pingsSent = (telemetry.eventsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 2)
|
|
|
|
let dismissPing = pingsSent[0]
|
|
assertPayload(dismissPing, matches: expectedPayloadForEvent("DISMISS", source: "HIGHLIGHTS", position: 0))
|
|
|
|
let deletePing = pingsSent[1]
|
|
assertPayload(deletePing, matches: expectedPayloadForEvent("DELETE", source: "HIGHLIGHTS", position: 0))
|
|
}
|
|
|
|
func testSessionReportedWhenViewAppearsAndDisappears() {
|
|
// Simulate the panel opening and closing with a second in between for some session_duration
|
|
panel.viewWillAppear(false)
|
|
var pingsSent = (telemetry.sessionsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 0)
|
|
|
|
wait(1)
|
|
panel.viewDidDisappear(false)
|
|
|
|
pingsSent = (telemetry.sessionsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 1)
|
|
|
|
let eventPing = pingsSent[0]
|
|
XCTAssertNotNil(eventPing["session_duration"])
|
|
}
|
|
|
|
func testBadStateEventsForHighlights() {
|
|
let goodSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
goodSite.icon = Favicon(url: "http://image", date: Date(), type: .local)
|
|
goodSite.metadata = PageMetadata(id: nil,
|
|
siteURL: "http://mozilla.org",
|
|
mediaURL: "http://image",
|
|
title: "Mozilla",
|
|
description: "Web",
|
|
type: nil,
|
|
providerName: nil,
|
|
mediaDataURI: nil)
|
|
let badSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
profile.recommendations = MockRecommender(highlights: [goodSite, badSite])
|
|
|
|
// Since invalidateHighlights calls back into the main thread, we can't
|
|
// simply call .value on this to block since the app will dead lock when
|
|
// trying to call back onto a blocked main thread.
|
|
let expect = XCTestExpectation(description: "Sent bad highlight pings")
|
|
panel.getHighlights() >>> {
|
|
expect.fulfill()
|
|
}
|
|
|
|
wait(for: [expect], timeout: 3)
|
|
let pingsSent = (self.telemetry.eventsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 2)
|
|
assertPayload(pingsSent[0],
|
|
matches: expectedBadStatePayload(state: "MISSING_METADATA_IMAGE", source: "HIGHLIGHTS"))
|
|
assertPayload(pingsSent[1],
|
|
matches: expectedBadStatePayload(state: "MISSING_FAVICON", source: "HIGHLIGHTS"))
|
|
}
|
|
|
|
func testBadStateEventsForTopSites() {
|
|
let goodSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
goodSite.icon = Favicon(url: "http://image", date: Date(), type: .local)
|
|
goodSite.metadata = PageMetadata(id: nil,
|
|
siteURL: "http://mozilla.org",
|
|
mediaURL: "http://image",
|
|
title: "Mozilla",
|
|
description: "Web",
|
|
type: nil,
|
|
providerName: nil,
|
|
mediaDataURI: nil)
|
|
let badSite = Site(url: "http://mozilla.org", title: "Mozilla")
|
|
profile.history = MockTopSitesHistory(sites: [goodSite, badSite])
|
|
|
|
// Since invalidateHighlights calls back into the main thread, we can't
|
|
// simply call .value on this to block since the app will dead lock when
|
|
// trying to call back onto a blocked main thread.
|
|
let expect = XCTestExpectation(description: "Sent bad top site pings")
|
|
panel.getTopSites() >>> {
|
|
expect.fulfill()
|
|
}
|
|
|
|
wait(for: [expect], timeout: 3)
|
|
let pingsSent = (self.telemetry.eventsTracker as! MockPingClient).pingsReceived
|
|
XCTAssertEqual(pingsSent.count, 2)
|
|
assertPayload(pingsSent[0],
|
|
matches: expectedBadStatePayload(state: "MISSING_METADATA_IMAGE", source: "TOP_SITES"))
|
|
assertPayload(pingsSent[1],
|
|
matches: expectedBadStatePayload(state: "MISSING_FAVICON", source: "TOP_SITES"))
|
|
}
|
|
}
|
|
|
|
class MockPingClient: PingCentreClient {
|
|
|
|
var pingsReceived: [[String: Any]] = []
|
|
|
|
public func sendPing(_ data: [String : Any], validate: Bool) -> Success {
|
|
pingsReceived.append(data)
|
|
return succeed()
|
|
}
|
|
|
|
public func sendBatch(_ data: [[String : Any]], validate: Bool) -> Success {
|
|
pingsReceived += data
|
|
return succeed()
|
|
}
|
|
}
|
|
|
|
fileprivate class MockRecommender: HistoryRecommendations {
|
|
func repopulateHighlights() -> Success {
|
|
return succeed()
|
|
}
|
|
|
|
var highlights: [Site]
|
|
|
|
init(highlights: [Site]) {
|
|
self.highlights = highlights
|
|
}
|
|
|
|
func getHighlights() -> Deferred<Maybe<Cursor<Site>>> {
|
|
return deferMaybe(ArrayCursor(data: highlights))
|
|
}
|
|
|
|
func getRecentBookmarks(_ limit: Int) -> Deferred<Maybe<Cursor<Site>>> {
|
|
return deferMaybe(ArrayCursor(data: []))
|
|
}
|
|
|
|
func repopulate(invalidateTopSites shouldInvalidateTopSites: Bool, invalidateHighlights shouldInvalidateHighlights: Bool) -> Success {
|
|
return succeed()
|
|
}
|
|
|
|
func removeHighlightForURL(_ url: String) -> Success {
|
|
guard let foundSite = highlights.filter({ $0.url == url }).first else {
|
|
return succeed()
|
|
}
|
|
let foundIndex = highlights.index(of: foundSite)!
|
|
highlights.remove(at: foundIndex)
|
|
return succeed()
|
|
}
|
|
}
|
|
|
|
fileprivate class MockTopSitesHistory: MockableHistory {
|
|
let mockTopSites: [Site]
|
|
|
|
init(sites: [Site]) {
|
|
mockTopSites = sites
|
|
}
|
|
|
|
override func getTopSitesWithLimit(_ limit: Int) -> Deferred<Maybe<Cursor<Site>>> {
|
|
return deferMaybe(ArrayCursor(data: mockTopSites))
|
|
}
|
|
|
|
override func getPinnedTopSites() -> Deferred<Maybe<Cursor<Site>>> {
|
|
return deferMaybe(ArrayCursor(data: []))
|
|
}
|
|
|
|
override func updateTopSitesCacheIfInvalidated() -> Deferred<Maybe<Bool>> {
|
|
return deferMaybe(true)
|
|
}
|
|
}
|