Files
linkding-ios/MarksTests/AppIntentsTests.swift
2026-07-02 01:11:03 -05:00

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&amp;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))
}
}