feat(intents): onscreen entity annotations + App Intents tests
All checks were successful
CI / build-and-deploy (push) Successful in 28s

- bookmarkOnscreen(_:) publishes the viewed bookmark as the on-screen
  entity (NSUserActivity.appEntityIdentifier) so Siri can resolve "this"
  / "summarize this one"; applied to the BrowserView sheets in
  BookmarksView and SearchView
- Add MarksTests (XCTest) covering entity mapping, EntityIdentifier,
  IntentRouter, and the real perform() of SearchMarksIntent and
  OpenBookmarkIntent; wired into the Marks scheme test phase

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Krishna Kumar
2026-06-17 00:16:46 -05:00
parent 389d615c8b
commit a3059f25fc
7 changed files with 256 additions and 0 deletions

View File

@@ -19,6 +19,7 @@
706642006800745A0A0188D0 /* RandomBookmarkWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F73C2A0B8AB36C6F80D5FE /* RandomBookmarkWidget.swift */; }; 706642006800745A0A0188D0 /* RandomBookmarkWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F73C2A0B8AB36C6F80D5FE /* RandomBookmarkWidget.swift */; };
706CCE40744D7F382AFFE429 /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C41E0BC28EB6359D50CFD /* BrowserView.swift */; }; 706CCE40744D7F382AFFE429 /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C41E0BC28EB6359D50CFD /* BrowserView.swift */; };
76223AA04DF97B24C1490B20 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADEAC824576633CC77370262 /* ShareViewController.swift */; }; 76223AA04DF97B24C1490B20 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADEAC824576633CC77370262 /* ShareViewController.swift */; };
778B82E075D4DAF5E8446D6F /* BookmarkOnscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1EEDFAE9FCA24192640CA7A /* BookmarkOnscreen.swift */; };
8140EEEFBB7A3510A73D1A93 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 093402014727A338745A06DA /* SwiftUI.framework */; }; 8140EEEFBB7A3510A73D1A93 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 093402014727A338745A06DA /* SwiftUI.framework */; };
849C9CB4E1E8A6ABA1BC7251 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29CB878BC334639E6194E2 /* SettingsView.swift */; }; 849C9CB4E1E8A6ABA1BC7251 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29CB878BC334639E6194E2 /* SettingsView.swift */; };
852A3E18661C190622FF5A85 /* AppIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9803BA80BEAE9A3AE4AF5E7 /* AppIntents.framework */; }; 852A3E18661C190622FF5A85 /* AppIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9803BA80BEAE9A3AE4AF5E7 /* AppIntents.framework */; };
@@ -47,6 +48,7 @@
EE540B8367519EDE679C1B70 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCC3BB2525F0F63445D419B9 /* SearchView.swift */; }; EE540B8367519EDE679C1B70 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCC3BB2525F0F63445D419B9 /* SearchView.swift */; };
EFF8E4CD63CAE1342CE3A4F0 /* IntentSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACABF0CA940312B4195456 /* IntentSupport.swift */; }; EFF8E4CD63CAE1342CE3A4F0 /* IntentSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACABF0CA940312B4195456 /* IntentSupport.swift */; };
FBAE1329DD9C3152FBB53AD4 /* BookmarkEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */; }; FBAE1329DD9C3152FBB53AD4 /* BookmarkEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */; };
FD656A44CEE8AE28B136AD85 /* AppIntentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7623601C25E481DF58371F2A /* AppIntentsTests.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -64,6 +66,13 @@
remoteGlobalIDString = CF31B2655F8F30CF6DF3C26F; remoteGlobalIDString = CF31B2655F8F30CF6DF3C26F;
remoteInfo = ShareExtension; remoteInfo = ShareExtension;
}; };
F63E42D8413C0F6ED8BB78CF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0CC2596AE7F05EAF051B2FCD /* Project object */;
proxyType = 1;
remoteGlobalIDString = 76C272776D8EBCCD3FB6715A;
remoteInfo = Marks;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@@ -101,6 +110,7 @@
64E9DEC5CD89FF346E23A14F /* MarksAppIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarksAppIntents.swift; sourceTree = "<group>"; }; 64E9DEC5CD89FF346E23A14F /* MarksAppIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarksAppIntents.swift; sourceTree = "<group>"; };
69D868AF1DAF3F1BBEACBFF6 /* ClaudeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeService.swift; sourceTree = "<group>"; }; 69D868AF1DAF3F1BBEACBFF6 /* ClaudeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeService.swift; sourceTree = "<group>"; };
759BA3FCF8BEE1D4EA1CDC17 /* String+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = "<group>"; }; 759BA3FCF8BEE1D4EA1CDC17 /* String+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Helpers.swift"; sourceTree = "<group>"; };
7623601C25E481DF58371F2A /* AppIntentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentsTests.swift; sourceTree = "<group>"; };
86B2DE40098D2B39FFE1AC36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 86B2DE40098D2B39FFE1AC36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8CA428181B35885F7D9F4D55 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; }; 8CA428181B35885F7D9F4D55 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkEntity.swift; sourceTree = "<group>"; }; 8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkEntity.swift; sourceTree = "<group>"; };
@@ -115,9 +125,11 @@
B0C6ABE160A2C90EB965D811 /* BookmarkRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRow.swift; sourceTree = "<group>"; }; B0C6ABE160A2C90EB965D811 /* BookmarkRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkRow.swift; sourceTree = "<group>"; };
B13B9F2D890C7953531AC0D2 /* TagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsView.swift; sourceTree = "<group>"; }; B13B9F2D890C7953531AC0D2 /* TagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsView.swift; sourceTree = "<group>"; };
BCC3BB2525F0F63445D419B9 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; }; BCC3BB2525F0F63445D419B9 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
C8B10AC011AF8F242E525440 /* MarksTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = MarksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
CBE3C5E420F078D499B2D926 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; }; CBE3C5E420F078D499B2D926 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
CBFB5EFC9764B22A2622EA4A /* BookmarksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksViewModel.swift; sourceTree = "<group>"; }; CBFB5EFC9764B22A2622EA4A /* BookmarksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksViewModel.swift; sourceTree = "<group>"; };
CC6B10FBB227F426A2B597C8 /* BookmarkListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListRow.swift; sourceTree = "<group>"; }; CC6B10FBB227F426A2B597C8 /* BookmarkListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListRow.swift; sourceTree = "<group>"; };
D1EEDFAE9FCA24192640CA7A /* BookmarkOnscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOnscreen.swift; sourceTree = "<group>"; };
D3A4B1E764CC88A774AF8EA5 /* EditBookmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBookmarkView.swift; sourceTree = "<group>"; }; D3A4B1E764CC88A774AF8EA5 /* EditBookmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBookmarkView.swift; sourceTree = "<group>"; };
D6ACABF0CA940312B4195456 /* IntentSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentSupport.swift; sourceTree = "<group>"; }; D6ACABF0CA940312B4195456 /* IntentSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentSupport.swift; sourceTree = "<group>"; };
D92575C7C710347F226EC74A /* MarksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarksApp.swift; sourceTree = "<group>"; }; D92575C7C710347F226EC74A /* MarksApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarksApp.swift; sourceTree = "<group>"; };
@@ -176,10 +188,19 @@
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3F5B1508DEFC894F33CBD16C /* MarksTests */ = {
isa = PBXGroup;
children = (
7623601C25E481DF58371F2A /* AppIntentsTests.swift */,
);
path = MarksTests;
sourceTree = "<group>";
};
58E8E316BE3F10C5149AADC3 /* Intents */ = { 58E8E316BE3F10C5149AADC3 /* Intents */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */, 8D650D0784B847BFEDCB3141 /* BookmarkEntity.swift */,
D1EEDFAE9FCA24192640CA7A /* BookmarkOnscreen.swift */,
9D8E2E470C9336209B7E8543 /* IntentSnippetViews.swift */, 9D8E2E470C9336209B7E8543 /* IntentSnippetViews.swift */,
D6ACABF0CA940312B4195456 /* IntentSupport.swift */, D6ACABF0CA940312B4195456 /* IntentSupport.swift */,
64E9DEC5CD89FF346E23A14F /* MarksAppIntents.swift */, 64E9DEC5CD89FF346E23A14F /* MarksAppIntents.swift */,
@@ -247,6 +268,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3952A081972B444674BEB51B /* Marks */, 3952A081972B444674BEB51B /* Marks */,
3F5B1508DEFC894F33CBD16C /* MarksTests */,
85E717A515682EBF67DD199A /* MarksWidget */, 85E717A515682EBF67DD199A /* MarksWidget */,
7A85D09B4E109E1D9CD8F5BB /* ShareExtension */, 7A85D09B4E109E1D9CD8F5BB /* ShareExtension */,
F5B3C85424137FA0CE2467B1 /* Frameworks */, F5B3C85424137FA0CE2467B1 /* Frameworks */,
@@ -258,6 +280,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
AB6C53AB14A38FCD4CC7628D /* Marks.app */, AB6C53AB14A38FCD4CC7628D /* Marks.app */,
C8B10AC011AF8F242E525440 /* MarksTests.xctest */,
23F172EC9977CD5C51B228B9 /* MarksWidget.appex */, 23F172EC9977CD5C51B228B9 /* MarksWidget.appex */,
938672D3D6ADC73B354B5EA5 /* ShareExtension.appex */, 938672D3D6ADC73B354B5EA5 /* ShareExtension.appex */,
); );
@@ -334,6 +357,24 @@
productReference = 938672D3D6ADC73B354B5EA5 /* ShareExtension.appex */; productReference = 938672D3D6ADC73B354B5EA5 /* ShareExtension.appex */;
productType = "com.apple.product-type.app-extension"; productType = "com.apple.product-type.app-extension";
}; };
F96181AD21F4A6755214E19B /* MarksTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = CE9D24DDB32E8B7EFDDD9D59 /* Build configuration list for PBXNativeTarget "MarksTests" */;
buildPhases = (
1035354FBD99C8ED4ACA93DC /* Sources */,
);
buildRules = (
);
dependencies = (
A665E75E77C12B6A3519ABF9 /* PBXTargetDependency */,
);
name = MarksTests;
packageProductDependencies = (
);
productName = MarksTests;
productReference = C8B10AC011AF8F242E525440 /* MarksTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@@ -355,6 +396,10 @@
DevelopmentTeam = AE5DZKJHGN; DevelopmentTeam = AE5DZKJHGN;
ProvisioningStyle = Automatic; ProvisioningStyle = Automatic;
}; };
F96181AD21F4A6755214E19B = {
DevelopmentTeam = AE5DZKJHGN;
ProvisioningStyle = Automatic;
};
}; };
}; };
buildConfigurationList = 4BACCD33EEE63890F06D7057 /* Build configuration list for PBXProject "Marks" */; buildConfigurationList = 4BACCD33EEE63890F06D7057 /* Build configuration list for PBXProject "Marks" */;
@@ -372,6 +417,7 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
76C272776D8EBCCD3FB6715A /* Marks */, 76C272776D8EBCCD3FB6715A /* Marks */,
F96181AD21F4A6755214E19B /* MarksTests */,
61046BF39C48393DD96687A0 /* MarksWidget */, 61046BF39C48393DD96687A0 /* MarksWidget */,
CF31B2655F8F30CF6DF3C26F /* ShareExtension */, CF31B2655F8F30CF6DF3C26F /* ShareExtension */,
); );
@@ -390,6 +436,14 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
1035354FBD99C8ED4ACA93DC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FD656A44CEE8AE28B136AD85 /* AppIntentsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2210C849B430CC6609106C62 /* Sources */ = { 2210C849B430CC6609106C62 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -419,6 +473,7 @@
457FCE503CCA82C5F27C6C90 /* Bookmark.swift in Sources */, 457FCE503CCA82C5F27C6C90 /* Bookmark.swift in Sources */,
FBAE1329DD9C3152FBB53AD4 /* BookmarkEntity.swift in Sources */, FBAE1329DD9C3152FBB53AD4 /* BookmarkEntity.swift in Sources */,
CD3013ED0FD018091D18F9FE /* BookmarkListRow.swift in Sources */, CD3013ED0FD018091D18F9FE /* BookmarkListRow.swift in Sources */,
778B82E075D4DAF5E8446D6F /* BookmarkOnscreen.swift in Sources */,
96698499C0501D0A897D7E08 /* BookmarkRow.swift in Sources */, 96698499C0501D0A897D7E08 /* BookmarkRow.swift in Sources */,
A8B8D58C5B68F54DC20126C1 /* BookmarksView.swift in Sources */, A8B8D58C5B68F54DC20126C1 /* BookmarksView.swift in Sources */,
5D86F3F0F603B248776916C7 /* BookmarksViewModel.swift in Sources */, 5D86F3F0F603B248776916C7 /* BookmarksViewModel.swift in Sources */,
@@ -452,6 +507,11 @@
target = 61046BF39C48393DD96687A0 /* MarksWidget */; target = 61046BF39C48393DD96687A0 /* MarksWidget */;
targetProxy = 22E9426D1CE4E3C473C5CD5D /* PBXContainerItemProxy */; targetProxy = 22E9426D1CE4E3C473C5CD5D /* PBXContainerItemProxy */;
}; };
A665E75E77C12B6A3519ABF9 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 76C272776D8EBCCD3FB6715A /* Marks */;
targetProxy = F63E42D8413C0F6ED8BB78CF /* PBXContainerItemProxy */;
};
A87C510E52F149E7E3CA0283 /* PBXTargetDependency */ = { A87C510E52F149E7E3CA0283 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = CF31B2655F8F30CF6DF3C26F /* ShareExtension */; target = CF31B2655F8F30CF6DF3C26F /* ShareExtension */;
@@ -571,6 +631,23 @@
}; };
name = Release; name = Release;
}; };
68D4EEAD1A7AA570E88EF498 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.magicive.marks.MarksTests;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Marks.app/Marks";
};
name = Debug;
};
6CDF13C33FDD1141CFE677AD /* Debug */ = { 6CDF13C33FDD1141CFE677AD /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -689,6 +766,23 @@
}; };
name = Release; name = Release;
}; };
E92B937142E9625F95B51699 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.magicive.marks.MarksTests;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Marks.app/Marks";
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@@ -728,6 +822,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug; defaultConfigurationName = Debug;
}; };
CE9D24DDB32E8B7EFDDD9D59 /* Build configuration list for PBXNativeTarget "MarksTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
68D4EEAD1A7AA570E88EF498 /* Debug */,
E92B937142E9625F95B51699 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = 0CC2596AE7F05EAF051B2FCD /* Project object */; rootObject = 0CC2596AE7F05EAF051B2FCD /* Project object */;

View File

@@ -67,6 +67,17 @@
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<Testables> <Testables>
<TestableReference
skipped = "NO"
parallelizable = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F96181AD21F4A6755214E19B"
BuildableName = "MarksTests.xctest"
BlueprintName = "MarksTests"
ReferencedContainer = "container:Marks.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables> </Testables>
<CommandLineArguments> <CommandLineArguments>
</CommandLineArguments> </CommandLineArguments>

View File

@@ -0,0 +1,19 @@
import SwiftUI
import AppIntents
extension View {
/// Marks `bookmark` as the on-screen entity via an `NSUserActivity`, so
/// Siri / Apple Intelligence can resolve references like "summarize this"
/// or "open this one" while the bookmark is being viewed.
///
/// iOS 26.0 ships no SwiftUI `.appEntityIdentifier` modifier, so this uses
/// the supported path: the standard `.userActivity` modifier plus
/// `NSUserActivity.appEntityIdentifier` (from `AppEntityAnnotatable`).
func bookmarkOnscreen(_ bookmark: Bookmark) -> some View {
userActivity("com.magicive.marks.viewBookmark", element: bookmark.id) { id, activity in
activity.title = bookmark.displayTitle
activity.appEntityIdentifier = EntityIdentifier(for: BookmarkEntity.self, identifier: id)
activity.isEligibleForSearch = true
}
}
}

View File

@@ -178,6 +178,7 @@ struct BookmarksView: View {
BrowserView(url: url, title: bookmark.displayTitle, claude: viewModel.claude, podcastPlayer: viewModel.podcastPlayer) { BrowserView(url: url, title: bookmark.displayTitle, claude: viewModel.claude, podcastPlayer: viewModel.podcastPlayer) {
await viewModel.archive(bookmark) await viewModel.archive(bookmark)
} }
.bookmarkOnscreen(bookmark)
} }
} }
.onChange(of: browsingBookmark) { _, new in .onChange(of: browsingBookmark) { _, new in

View File

@@ -52,6 +52,7 @@ struct SearchView: View {
) { ) {
await viewModel.archive(bookmark) await viewModel.archive(bookmark)
} }
.bookmarkOnscreen(bookmark)
} }
} }
} }

View File

@@ -0,0 +1,107 @@
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)
}
}

View File

@@ -26,6 +26,8 @@ schemes:
config: Debug config: Debug
test: test:
config: Debug config: Debug
targets:
- MarksTests
profile: profile:
config: Release config: Release
analyze: analyze:
@@ -89,6 +91,18 @@ targets:
SKIP_INSTALL: YES SKIP_INSTALL: YES
APPLICATION_EXTENSION_API_ONLY: YES APPLICATION_EXTENSION_API_ONLY: YES
MarksTests:
type: bundle.unit-test
platform: iOS
sources:
- path: MarksTests
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.magicive.marks.MarksTests
GENERATE_INFOPLIST_FILE: YES
dependencies:
- target: Marks
MarksWidget: MarksWidget:
type: app-extension type: app-extension
platform: iOS platform: iOS