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 = "
Save this: article
" 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)) } @MainActor func testSourceLibraryUpdatesMetadataAndSearch() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: root) } let library = IngestedSourceLibrary(store: IngestedSourceStore(rootURL: root)) library.addText(title: "Draft", body: "Original extracted text", tags: ["raw"]) let source = try XCTUnwrap(library.sources.first) library.update(source, title: "Cleaned Notes", bodyText: "Cleaned source body", tags: ["clean", "notes"]) let updated = try XCTUnwrap(library.sources.first) XCTAssertEqual(updated.title, "Cleaned Notes") XCTAssertEqual(updated.bodyText, "Cleaned source body") XCTAssertEqual(updated.tags, ["clean", "notes"]) XCTAssertGreaterThanOrEqual(updated.modifiedAt, source.modifiedAt) XCTAssertEqual(library.search("clean").first?.id, source.id) let reloaded = IngestedSourceStore(rootURL: root) XCTAssertEqual(try reloaded.load().first?.title, "Cleaned Notes") } }