180 lines
6.9 KiB
Swift
180 lines
6.9 KiB
Swift
import XCTest
|
|
import AppIntents
|
|
@testable import Marks
|
|
|
|
/// Tests for the App Intents layer.
|
|
///
|
|
/// Note: WWDC26's dedicated `AppIntentsTesting` framework (out-of-process
|
|
/// `IntentDefinitions(bundleIdentifier:)` / `.run()`) is not present in this
|
|
/// Xcode, so these are standard XCTest cases. They work because Marks has no
|
|
/// AppIntents extension — intents run in the app process, so `perform()` can be
|
|
/// called directly and asserted against `IntentRouter`.
|
|
final class AppIntentsTests: XCTestCase {
|
|
|
|
private func sampleBookmark(
|
|
id: Int = 42,
|
|
url: String = "https://example.com/article",
|
|
title: String = "Concurrency in Swift",
|
|
tags: [String] = ["swift", "concurrency"],
|
|
unread: Bool = true,
|
|
aiSummary: String? = "A short summary."
|
|
) -> Bookmark {
|
|
Bookmark(
|
|
id: id, url: url, title: title, description: "desc",
|
|
tagNames: tags, dateAdded: Date(), dateModified: Date(),
|
|
isArchived: false, unread: unread, shared: false,
|
|
websiteTitle: nil, websiteDescription: nil,
|
|
faviconUrl: nil, previewImageUrl: nil,
|
|
aiSummary: aiSummary, aiTags: nil
|
|
)
|
|
}
|
|
|
|
// MARK: - Entity mapping
|
|
|
|
func testEntityMapsFromBookmark() {
|
|
let entity = BookmarkEntity(from: sampleBookmark())
|
|
XCTAssertEqual(entity.id, 42)
|
|
XCTAssertEqual(entity.title, "Concurrency in Swift")
|
|
XCTAssertEqual(entity.url, URL(string: "https://example.com/article"))
|
|
XCTAssertEqual(entity.host, "example.com")
|
|
XCTAssertEqual(entity.tags, ["swift", "concurrency"])
|
|
XCTAssertTrue(entity.unread)
|
|
XCTAssertEqual(entity.summary, "A short summary.")
|
|
}
|
|
|
|
func testEntityUsesDisplayTitleFallback() {
|
|
// empty title → falls back to url (Bookmark.displayTitle)
|
|
let entity = BookmarkEntity(from: sampleBookmark(title: ""))
|
|
XCTAssertFalse(entity.title.isEmpty)
|
|
}
|
|
|
|
func testMakeBookmarkRoundTripsCoreFields() {
|
|
let entity = BookmarkEntity(from: sampleBookmark())
|
|
let back = entity.makeBookmark()
|
|
XCTAssertEqual(back.id, 42)
|
|
XCTAssertEqual(back.url, "https://example.com/article")
|
|
XCTAssertEqual(back.tagNames, ["swift", "concurrency"])
|
|
}
|
|
|
|
func testEntityIdentifierEncodesServerId() {
|
|
let entity = BookmarkEntity(from: sampleBookmark(id: 7))
|
|
let eid = EntityIdentifier(for: entity)
|
|
XCTAssertEqual(eid.identifier, "7")
|
|
XCTAssertTrue(eid.entityType == BookmarkEntity.self)
|
|
}
|
|
|
|
// MARK: - Router
|
|
|
|
@MainActor
|
|
func testRouterSearch() {
|
|
IntentRouter.shared.searchRequest = nil
|
|
IntentRouter.shared.search("hello")
|
|
XCTAssertEqual(IntentRouter.shared.searchRequest, "hello")
|
|
}
|
|
|
|
@MainActor
|
|
func testRouterOpenBookmark() {
|
|
IntentRouter.shared.openBookmarkURL = nil
|
|
IntentRouter.shared.openBookmark(url: "https://example.com")
|
|
XCTAssertEqual(IntentRouter.shared.openBookmarkURL, "https://example.com")
|
|
}
|
|
|
|
// MARK: - Intent perform() wiring (no network)
|
|
|
|
@MainActor
|
|
func testSearchIntentRoutesCriteriaTerm() async throws {
|
|
IntentRouter.shared.searchRequest = nil
|
|
let intent = SearchMarksIntent()
|
|
intent.criteria = StringSearchCriteria(term: "graphql")
|
|
_ = try await intent.perform()
|
|
XCTAssertEqual(IntentRouter.shared.searchRequest, "graphql")
|
|
}
|
|
|
|
@MainActor
|
|
func testOpenIntentRoutesURL() async throws {
|
|
IntentRouter.shared.openBookmarkURL = nil
|
|
let intent = OpenBookmarkIntent()
|
|
intent.target = BookmarkEntity(from: sampleBookmark())
|
|
_ = try await intent.perform()
|
|
XCTAssertEqual(IntentRouter.shared.openBookmarkURL, "https://example.com/article")
|
|
}
|
|
|
|
// MARK: - App Shortcuts
|
|
|
|
func testAppShortcutsAreRegistered() {
|
|
XCTAssertGreaterThanOrEqual(MarksShortcuts.appShortcuts.count, 4)
|
|
}
|
|
|
|
// MARK: - Ingest payload parsing
|
|
|
|
func testIngestParsesDirectURL() {
|
|
let payload = IngestPayloadParser.parse(item: URL(string: "https://example.com/article")!)
|
|
XCTAssertEqual(payload?.url, "https://example.com/article")
|
|
XCTAssertEqual(payload?.notes, "")
|
|
}
|
|
|
|
func testIngestFindsURLInSharedEmailText() {
|
|
let text = """
|
|
Thought you might want to save this:
|
|
|
|
https://example.com/story?utm_source=newsletter
|
|
|
|
Sent from Spark
|
|
"""
|
|
|
|
let payload = IngestPayloadParser.parse(item: text, typeIdentifier: "public.plain-text")
|
|
XCTAssertEqual(payload?.url, "https://example.com/story?utm_source=newsletter")
|
|
XCTAssertTrue(payload?.notes.contains("Sent from Spark") == true)
|
|
}
|
|
|
|
func testIngestIgnoresMailLinksAndUsesWebURL() {
|
|
let text = "mailto:friend@example.com\nRead https://example.com/read-later."
|
|
let payload = IngestPayloadParser.parse(item: text, typeIdentifier: "public.plain-text")
|
|
XCTAssertEqual(payload?.url, "https://example.com/read-later")
|
|
}
|
|
|
|
func testIngestDecodesHTMLLinksFromEmailClients() {
|
|
let html = "<p>Save this: <a href=\"https://example.com/a?x=1&y=2\">article</a></p>"
|
|
let payload = IngestPayloadParser.parse(item: html, typeIdentifier: "public.html")
|
|
XCTAssertEqual(payload?.url, "https://example.com/a?x=1&y=2")
|
|
}
|
|
|
|
// MARK: - Local sources
|
|
|
|
func testSourceStoreSavesAndLoadsMetadata() throws {
|
|
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
|
|
let store = IngestedSourceStore(rootURL: root)
|
|
let source = IngestedSource(kind: .text, title: "Meeting Notes", bodyText: "Follow up on paper receipts.", tags: ["receipts"])
|
|
try store.save([source])
|
|
|
|
let loaded = try store.load()
|
|
XCTAssertEqual(loaded.count, 1)
|
|
XCTAssertEqual(loaded.first?.id, source.id)
|
|
XCTAssertEqual(loaded.first?.kind, .text)
|
|
XCTAssertEqual(loaded.first?.title, "Meeting Notes")
|
|
XCTAssertEqual(loaded.first?.bodyText, "Follow up on paper receipts.")
|
|
XCTAssertEqual(loaded.first?.tags, ["receipts"])
|
|
}
|
|
|
|
func testSourceStoreImportsPlainTextFile() throws {
|
|
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
let input = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).txt")
|
|
defer {
|
|
try? FileManager.default.removeItem(at: root)
|
|
try? FileManager.default.removeItem(at: input)
|
|
}
|
|
|
|
try Data("Offline clipping text".utf8).write(to: input)
|
|
|
|
let store = IngestedSourceStore(rootURL: root)
|
|
let source = try store.importFile(from: input, tags: ["offline"])
|
|
|
|
XCTAssertEqual(source.kind, .file)
|
|
XCTAssertEqual(source.bodyText, "Offline clipping text")
|
|
XCTAssertEqual(source.tags, ["offline"])
|
|
XCTAssertNotNil(store.fileURL(for: source))
|
|
}
|
|
}
|