mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-02-07 08:22:07 +00:00
Separate AppContext for previews and UI testing (#961)
Clarify the use of contexts: - **Production** (.shared) - **Previews** (.mock → .forPreviews) - ONLY use it in UILibrary for, well, previews - This context has dumb profiles with UUIDs as names - Registry is fake - **UI Tests** (.forUITesting) - Add new context for UI testing - Selected based on command line arguments - This context has mock data tuned for decent screenshots - Registry is real Share the same InAppProcessor in .shared and .forTesting contexts because the app behavior was inconsistent regarding e.g. in-app purchases.
This commit is contained in:
parent
962361cb9f
commit
2a467e0c7e
@ -16,6 +16,8 @@
|
|||||||
0E483E812CE64D6B00584B32 /* Shared+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */; };
|
0E483E812CE64D6B00584B32 /* Shared+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */; };
|
||||||
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E822CE6501100584B32 /* Shared+App.swift */; };
|
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E483E822CE6501100584B32 /* Shared+App.swift */; };
|
||||||
0E60512C2CE5393C00F763D4 /* PassepartoutImplementations in Frameworks */ = {isa = PBXBuildFile; productRef = 0E60512B2CE5393C00F763D4 /* PassepartoutImplementations */; };
|
0E60512C2CE5393C00F763D4 /* PassepartoutImplementations in Frameworks */ = {isa = PBXBuildFile; productRef = 0E60512B2CE5393C00F763D4 /* PassepartoutImplementations */; };
|
||||||
|
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */; };
|
||||||
|
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */; };
|
||||||
0E757F132CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */; };
|
0E757F132CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */; };
|
||||||
0E757F202CD0D22B006E13E1 /* PassepartoutLoginItem.app in Embed Login Item */ = {isa = PBXBuildFile; fileRef = 0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */; platformFilters = (macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
0E757F202CD0D22B006E13E1 /* PassepartoutLoginItem.app in Embed Login Item */ = {isa = PBXBuildFile; fileRef = 0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */; platformFilters = (macos, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
0E757F232CD0D2BD006E13E1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F212CD0D2B7006E13E1 /* AppDelegate.swift */; };
|
0E757F232CD0D2BD006E13E1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E757F212CD0D2B7006E13E1 /* AppDelegate.swift */; };
|
||||||
@ -134,6 +136,8 @@
|
|||||||
0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+Tunnel.swift"; sourceTree = "<group>"; };
|
0E483E7F2CE64D6B00584B32 /* Shared+Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+Tunnel.swift"; sourceTree = "<group>"; };
|
||||||
0E483E822CE6501100584B32 /* Shared+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+App.swift"; sourceTree = "<group>"; };
|
0E483E822CE6501100584B32 /* Shared+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+App.swift"; sourceTree = "<group>"; };
|
||||||
0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = "<group>"; };
|
0E5DFDDC2CDB8F9100F2DE70 /* Passepartout.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.storekit; sourceTree = "<group>"; };
|
||||||
|
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppContext+Testing.swift"; sourceTree = "<group>"; };
|
||||||
|
0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileManager+Testing.swift"; sourceTree = "<group>"; };
|
||||||
0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassepartoutLoginItem.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
0E757F102CD0CFFC006E13E1 /* PassepartoutLoginItem.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassepartoutLoginItem.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassepartoutLoginItemApp.swift; sourceTree = "<group>"; };
|
0E757F122CD0CFFC006E13E1 /* PassepartoutLoginItemApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassepartoutLoginItemApp.swift; sourceTree = "<group>"; };
|
||||||
0E757F182CD0CFFD006E13E1 /* LoginItem.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItem.entitlements; sourceTree = "<group>"; };
|
0E757F182CD0CFFD006E13E1 /* LoginItem.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItem.entitlements; sourceTree = "<group>"; };
|
||||||
@ -277,6 +281,15 @@
|
|||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
0E6EEEE62CF8CB090076E2B0 /* Testing */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0E6EEEE12CF8CABA0076E2B0 /* AppContext+Testing.swift */,
|
||||||
|
0E6EEEE22CF8CABA0076E2B0 /* ProfileManager+Testing.swift */,
|
||||||
|
);
|
||||||
|
path = Testing;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
0E757F112CD0CFFC006E13E1 /* LoginItem */ = {
|
0E757F112CD0CFFC006E13E1 /* LoginItem */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -323,6 +336,7 @@
|
|||||||
0E7E3D612B9345FD002BBDB4 /* Shared */ = {
|
0E7E3D612B9345FD002BBDB4 /* Shared */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0E6EEEE62CF8CB090076E2B0 /* Testing */,
|
||||||
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */,
|
0EC797402B9378E000C093B7 /* AppContext+Shared.swift */,
|
||||||
0EC797412B9378E000C093B7 /* Shared.swift */,
|
0EC797412B9378E000C093B7 /* Shared.swift */,
|
||||||
0E483E822CE6501100584B32 /* Shared+App.swift */,
|
0E483E822CE6501100584B32 /* Shared+App.swift */,
|
||||||
@ -673,6 +687,8 @@
|
|||||||
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
|
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
|
||||||
0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */,
|
0EC797422B9378E000C093B7 /* AppContext+Shared.swift in Sources */,
|
||||||
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */,
|
0EE8D7E12CD112C200F6600C /* App+tvOS.swift in Sources */,
|
||||||
|
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */,
|
||||||
|
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */,
|
||||||
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */,
|
0E483E842CE6501100584B32 /* Shared+App.swift in Sources */,
|
||||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -32,9 +32,9 @@ import UITesting
|
|||||||
@MainActor
|
@MainActor
|
||||||
final class AppDelegate: NSObject {
|
final class AppDelegate: NSObject {
|
||||||
let context: AppContext = {
|
let context: AppContext = {
|
||||||
guard !AppCommandLine.contains(.uiTesting) else {
|
if AppCommandLine.contains(.uiTesting) {
|
||||||
pp_log(.app, .info, "UI tests: mock AppContext")
|
pp_log(.app, .info, "UI tests: mock AppContext")
|
||||||
return .mock(withRegistry: .shared)
|
return .forUITesting(withRegistry: .shared)
|
||||||
}
|
}
|
||||||
return .shared
|
return .shared
|
||||||
}()
|
}()
|
||||||
|
@ -133,6 +133,20 @@
|
|||||||
ReferencedContainer = "container:">
|
ReferencedContainer = "container:">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "UITesting"
|
||||||
|
BuildableName = "UITesting"
|
||||||
|
BlueprintName = "UITesting"
|
||||||
|
ReferencedContainer = "container:">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
|
@ -165,8 +165,8 @@ extension AboutCoordinator {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AboutCoordinator(
|
AboutCoordinator(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock
|
tunnel: .forPreviews
|
||||||
)
|
)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
}
|
}
|
||||||
|
@ -270,7 +270,7 @@ extension AppCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var overriddenLayout: ProfilesLayout {
|
var overriddenLayout: ProfilesLayout {
|
||||||
guard !isUITesting else {
|
if isUITesting {
|
||||||
return isBigDevice ? .grid : .list
|
return isBigDevice ? .grid : .list
|
||||||
}
|
}
|
||||||
return layout
|
return layout
|
||||||
@ -306,8 +306,8 @@ extension AppCoordinator {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AppCoordinator(
|
AppCoordinator(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
registry: Registry()
|
registry: Registry()
|
||||||
)
|
)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
@ -110,7 +110,7 @@ private extension AppToolbar {
|
|||||||
Text("AppToolbar")
|
Text("AppToolbar")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
AppToolbar(
|
AppToolbar(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
registry: Registry(),
|
registry: Registry(),
|
||||||
layout: .constant(.list),
|
layout: .constant(.list),
|
||||||
isImporting: .constant(false),
|
isImporting: .constant(false),
|
||||||
|
@ -123,7 +123,7 @@ private extension InstalledProfileView {
|
|||||||
style: .installedProfile,
|
style: .installedProfile,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
preview: .init(profile ?? .mock),
|
preview: .init(profile ?? .forPreviews),
|
||||||
interactiveManager: interactiveManager,
|
interactiveManager: interactiveManager,
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
flow: flow
|
flow: flow
|
||||||
@ -298,9 +298,9 @@ private struct HeaderView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
InstalledProfileView(
|
InstalledProfileView(
|
||||||
layout: layout,
|
layout: layout,
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
profile: .mock,
|
profile: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default(),
|
errorHandler: .default(),
|
||||||
nextProfileId: .constant(nil)
|
nextProfileId: .constant(nil)
|
||||||
@ -313,9 +313,9 @@ private struct ContentView: View {
|
|||||||
ForEach(0..<3) { _ in
|
ForEach(0..<3) { _ in
|
||||||
ProfileRowView(
|
ProfileRowView(
|
||||||
style: .full,
|
style: .full,
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
preview: .init(.mock),
|
preview: .init(.forPreviews),
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default(),
|
errorHandler: .default(),
|
||||||
nextProfileId: .constant(nil),
|
nextProfileId: .constant(nil),
|
||||||
|
@ -70,7 +70,7 @@ struct OnboardingModifier: ViewModifier {
|
|||||||
|
|
||||||
private extension OnboardingModifier {
|
private extension OnboardingModifier {
|
||||||
func advance() {
|
func advance() {
|
||||||
guard !isUITesting else {
|
if isUITesting {
|
||||||
pp_log(.app, .info, "UI tests: skip onboarding")
|
pp_log(.app, .info, "UI tests: skip onboarding")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ struct ProfileCardView: View {
|
|||||||
Section {
|
Section {
|
||||||
ProfileCardView(
|
ProfileCardView(
|
||||||
style: .compact,
|
style: .compact,
|
||||||
preview: .init(.mock)
|
preview: .init(.forPreviews)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
ProfileCardView(
|
ProfileCardView(
|
||||||
style: .full,
|
style: .full,
|
||||||
preview: .init(.mock)
|
preview: .init(.forPreviews)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,8 +157,8 @@ private struct PreviewView: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
ProfileContainerView(
|
ProfileContainerView(
|
||||||
layout: layout,
|
layout: layout,
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
registry: Registry(),
|
registry: Registry(),
|
||||||
isImporting: .constant(false),
|
isImporting: .constant(false),
|
||||||
errorHandler: .default()
|
errorHandler: .default()
|
||||||
|
@ -158,9 +158,9 @@ private extension ProfileContextMenu {
|
|||||||
Menu("Menu") {
|
Menu("Menu") {
|
||||||
ProfileContextMenu(
|
ProfileContextMenu(
|
||||||
style: .installedProfile,
|
style: .installedProfile,
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
preview: .init(.mock),
|
preview: .init(.forPreviews),
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default()
|
errorHandler: .default()
|
||||||
)
|
)
|
||||||
|
@ -150,8 +150,8 @@ private extension ProfileGridView {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ProfileGridView(
|
ProfileGridView(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default()
|
errorHandler: .default()
|
||||||
)
|
)
|
||||||
|
@ -149,8 +149,8 @@ private extension ProfileListView {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ProfileListView(
|
ProfileListView(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default()
|
errorHandler: .default()
|
||||||
)
|
)
|
||||||
|
@ -205,14 +205,14 @@ private extension ProfileRowView {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
let profile: Profile = .mock
|
let profile: Profile = .forPreviews
|
||||||
let profileManager: ProfileManager = .mock
|
let profileManager: ProfileManager = .forPreviews
|
||||||
|
|
||||||
return Form {
|
return Form {
|
||||||
ProfileRowView(
|
ProfileRowView(
|
||||||
style: .compact,
|
style: .compact,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
preview: .init(profile),
|
preview: .init(profile),
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default(),
|
errorHandler: .default(),
|
||||||
|
@ -187,7 +187,7 @@ private extension DiagnosticsView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
DiagnosticsView(profileManager: .mock, tunnel: .mock) {
|
DiagnosticsView(profileManager: .forPreviews, tunnel: .forPreviews) {
|
||||||
[
|
[
|
||||||
.init(date: Date(), url: URL(string: "http://one.com")!),
|
.init(date: Date(), url: URL(string: "http://one.com")!),
|
||||||
.init(date: Date().addingTimeInterval(-60), url: URL(string: "http://two.com")!),
|
.init(date: Date().addingTimeInterval(-60), url: URL(string: "http://two.com")!),
|
||||||
|
@ -60,8 +60,8 @@ private extension ModuleDetailView {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ModuleDetailView(
|
ModuleDetailView(
|
||||||
profileEditor: ProfileEditor(profile: .mock),
|
profileEditor: ProfileEditor(profile: .forPreviews),
|
||||||
moduleId: Profile.mock.modules.first?.id,
|
moduleId: Profile.forPreviews.modules.first?.id,
|
||||||
moduleViewFactory: DefaultModuleViewFactory(registry: Registry())
|
moduleViewFactory: DefaultModuleViewFactory(registry: Registry())
|
||||||
)
|
)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
@ -164,7 +164,7 @@ private extension ProfileCoordinator {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ProfileCoordinator(
|
ProfileCoordinator(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
profileEditor: ProfileEditor(profile: .newMockProfile()),
|
profileEditor: ProfileEditor(profile: .newMockProfile()),
|
||||||
initialModuleId: nil,
|
initialModuleId: nil,
|
||||||
registry: Registry(),
|
registry: Registry(),
|
||||||
|
@ -147,7 +147,7 @@ private extension ModuleListView {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ModuleListView(
|
ModuleListView(
|
||||||
profileEditor: ProfileEditor(profile: .mock),
|
profileEditor: ProfileEditor(profile: .forPreviews),
|
||||||
selectedModuleId: .constant(nil),
|
selectedModuleId: .constant(nil),
|
||||||
errorModuleIds: .constant([]),
|
errorModuleIds: .constant([]),
|
||||||
paywallReason: .constant(nil)
|
paywallReason: .constant(nil)
|
||||||
|
@ -136,8 +136,8 @@ private extension AppCoordinator {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
AppCoordinator(
|
AppCoordinator(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
registry: Registry()
|
registry: Registry()
|
||||||
)
|
)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
@ -231,7 +231,7 @@ private extension ActiveProfileView {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
try? await ProviderManager.mock.fetchIndex(from: [API.bundled])
|
try? await ProviderManager.forPreviews.fetchIndex(from: [API.bundled])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +247,7 @@ private struct ContentPreview: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ActiveProfileView(
|
ActiveProfileView(
|
||||||
profile: profile,
|
profile: profile,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
isSwitching: $isSwitching,
|
isSwitching: $isSwitching,
|
||||||
focusedField: $focusedField,
|
focusedField: $focusedField,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
|
@ -115,7 +115,7 @@ private extension ProfileListView {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
#Preview("List") {
|
#Preview("List") {
|
||||||
ContentPreview(profileManager: .mock)
|
ContentPreview(profileManager: .forPreviews)
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Empty") {
|
#Preview("Empty") {
|
||||||
@ -131,7 +131,7 @@ private struct ContentPreview: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ProfileListView(
|
ProfileListView(
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
focusedField: $focusedField,
|
focusedField: $focusedField,
|
||||||
interactiveManager: InteractiveManager(),
|
interactiveManager: InteractiveManager(),
|
||||||
errorHandler: .default()
|
errorHandler: .default()
|
||||||
|
@ -190,8 +190,8 @@ private extension ProfileView {
|
|||||||
|
|
||||||
#Preview("List") {
|
#Preview("List") {
|
||||||
ProfileView(
|
ProfileView(
|
||||||
profileManager: .mock,
|
profileManager: .forPreviews,
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
errorHandler: .default(),
|
errorHandler: .default(),
|
||||||
showsSidePanel: true
|
showsSidePanel: true
|
||||||
)
|
)
|
||||||
@ -201,7 +201,7 @@ private extension ProfileView {
|
|||||||
#Preview("Empty") {
|
#Preview("Empty") {
|
||||||
ProfileView(
|
ProfileView(
|
||||||
profileManager: ProfileManager(profiles: []),
|
profileManager: ProfileManager(profiles: []),
|
||||||
tunnel: .mock,
|
tunnel: .forPreviews,
|
||||||
errorHandler: .default(),
|
errorHandler: .default(),
|
||||||
showsSidePanel: true
|
showsSidePanel: true
|
||||||
)
|
)
|
||||||
|
@ -145,7 +145,7 @@ private struct DetailView: View {
|
|||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
SettingsView(tunnel: .mock)
|
SettingsView(tunnel: .forPreviews)
|
||||||
.themeNavigationStack()
|
.themeNavigationStack()
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,7 @@ extension ProfileManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
requiredFeatures = allProfiles.reduce(into: [:]) {
|
requiredFeatures = allProfiles.reduce(into: [:]) {
|
||||||
guard let ineligible = processor.verify($1.value), !ineligible.isEmpty else {
|
guard let ineligible = processor.requiredFeatures($1.value), !ineligible.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$0[$1.key] = ineligible
|
$0[$1.key] = ineligible
|
||||||
|
@ -35,7 +35,7 @@ public final class InAppProcessor: ObservableObject, Sendable {
|
|||||||
|
|
||||||
private nonisolated let _preview: (Profile) -> ProfilePreview
|
private nonisolated let _preview: (Profile) -> ProfilePreview
|
||||||
|
|
||||||
private nonisolated let _verify: (IAPManager, Profile) -> Set<AppFeature>?
|
private nonisolated let _requiredFeatures: (IAPManager, Profile) -> Set<AppFeature>?
|
||||||
|
|
||||||
private nonisolated let _willRebuild: (IAPManager, Profile.Builder) throws -> Profile.Builder
|
private nonisolated let _willRebuild: (IAPManager, Profile.Builder) throws -> Profile.Builder
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ public final class InAppProcessor: ObservableObject, Sendable {
|
|||||||
title: @escaping (Profile) -> String,
|
title: @escaping (Profile) -> String,
|
||||||
isIncluded: @escaping (IAPManager, Profile) -> Bool,
|
isIncluded: @escaping (IAPManager, Profile) -> Bool,
|
||||||
preview: @escaping (Profile) -> ProfilePreview,
|
preview: @escaping (Profile) -> ProfilePreview,
|
||||||
verify: @escaping (IAPManager, Profile) -> Set<AppFeature>?,
|
requiredFeatures: @escaping (IAPManager, Profile) -> Set<AppFeature>?,
|
||||||
willRebuild: @escaping (IAPManager, Profile.Builder) throws -> Profile.Builder,
|
willRebuild: @escaping (IAPManager, Profile.Builder) throws -> Profile.Builder,
|
||||||
willInstall: @escaping (IAPManager, Profile) throws -> Profile
|
willInstall: @escaping (IAPManager, Profile) throws -> Profile
|
||||||
) {
|
) {
|
||||||
@ -54,7 +54,7 @@ public final class InAppProcessor: ObservableObject, Sendable {
|
|||||||
_title = title
|
_title = title
|
||||||
_isIncluded = isIncluded
|
_isIncluded = isIncluded
|
||||||
_preview = preview
|
_preview = preview
|
||||||
_verify = verify
|
_requiredFeatures = requiredFeatures
|
||||||
_willRebuild = willRebuild
|
_willRebuild = willRebuild
|
||||||
_willInstall = willInstall
|
_willInstall = willInstall
|
||||||
}
|
}
|
||||||
@ -75,8 +75,8 @@ extension InAppProcessor: ProfileProcessor {
|
|||||||
_preview(profile)
|
_preview(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func verify(_ profile: Profile) -> Set<AppFeature>? {
|
public func requiredFeatures(_ profile: Profile) -> Set<AppFeature>? {
|
||||||
_verify(iapManager, profile)
|
_requiredFeatures(iapManager, profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
|
public func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
|
||||||
|
@ -31,7 +31,7 @@ public protocol ProfileProcessor {
|
|||||||
|
|
||||||
func preview(from profile: Profile) -> ProfilePreview
|
func preview(from profile: Profile) -> ProfilePreview
|
||||||
|
|
||||||
func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder
|
func requiredFeatures(_ profile: Profile) -> Set<AppFeature>?
|
||||||
|
|
||||||
func verify(_ profile: Profile) -> Set<AppFeature>?
|
func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ extension View {
|
|||||||
|
|
||||||
public func withMockEnvironment() -> some View {
|
public func withMockEnvironment() -> some View {
|
||||||
task {
|
task {
|
||||||
try? await AppContext.mock.profileManager.observeLocal()
|
try? await AppContext.forPreviews.profileManager.observeLocal()
|
||||||
}
|
}
|
||||||
.withEnvironment(from: .mock, theme: Theme())
|
.withEnvironment(from: .forPreviews, theme: Theme())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// AppContext+Mock.swift
|
// AppContext+Previews.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 6/22/24.
|
// Created by Davide De Rosa on 6/22/24.
|
||||||
@ -23,25 +23,18 @@
|
|||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import CommonUtils
|
import CommonUtils
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension AppContext {
|
extension AppContext {
|
||||||
public static let mock: AppContext = .mock(withRegistry: Registry())
|
public static let forPreviews: AppContext = {
|
||||||
|
|
||||||
public static func mock(withRegistry registry: Registry) -> AppContext {
|
|
||||||
let iapManager = IAPManager(
|
let iapManager = IAPManager(
|
||||||
customUserLevel: .subscriber,
|
customUserLevel: .subscriber,
|
||||||
inAppHelper: FakeAppProductHelper(),
|
inAppHelper: FakeAppProductHelper(),
|
||||||
receiptReader: FakeAppReceiptReader(),
|
receiptReader: FakeAppReceiptReader(),
|
||||||
betaChecker: TestFlightChecker(),
|
betaChecker: TestFlightChecker(),
|
||||||
unrestrictedFeatures: [
|
|
||||||
.interactiveLogin,
|
|
||||||
.onDemand
|
|
||||||
],
|
|
||||||
productsAtBuild: { _ in
|
productsAtBuild: { _ in
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
@ -57,17 +50,23 @@ extension AppContext {
|
|||||||
preview: {
|
preview: {
|
||||||
$0.localizedPreview
|
$0.localizedPreview
|
||||||
},
|
},
|
||||||
verify: { _, _ in
|
requiredFeatures: { _, _ in
|
||||||
nil
|
nil
|
||||||
},
|
},
|
||||||
willRebuild: { _, builder in
|
willRebuild: { _, builder in
|
||||||
builder
|
builder
|
||||||
},
|
},
|
||||||
willInstall: { _, profile in
|
willInstall: { _, profile in
|
||||||
try profile.withProviderModules()
|
profile
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let profileManager: ProfileManager = .mock(withRegistry: registry, processor: processor)
|
let profileManager = {
|
||||||
|
let profiles: [Profile] = (0..<20)
|
||||||
|
.reduce(into: []) { list, _ in
|
||||||
|
list.append(.newMockProfile())
|
||||||
|
}
|
||||||
|
return ProfileManager(profiles: profiles)
|
||||||
|
}()
|
||||||
let tunnelEnvironment = InMemoryEnvironment()
|
let tunnelEnvironment = InMemoryEnvironment()
|
||||||
let tunnel = ExtendedTunnel(
|
let tunnel = ExtendedTunnel(
|
||||||
tunnel: Tunnel(strategy: FakeTunnelStrategy(environment: tunnelEnvironment)),
|
tunnel: Tunnel(strategy: FakeTunnelStrategy(environment: tunnelEnvironment)),
|
||||||
@ -84,35 +83,35 @@ extension AppContext {
|
|||||||
migrationManager: migrationManager,
|
migrationManager: migrationManager,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
providerManager: providerManager,
|
providerManager: providerManager,
|
||||||
registry: registry,
|
registry: Registry(),
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
|
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
|
||||||
)
|
)
|
||||||
}
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Shortcuts
|
// MARK: - Shortcuts
|
||||||
|
|
||||||
extension IAPManager {
|
extension IAPManager {
|
||||||
public static var mock: IAPManager {
|
public static var forPreviews: IAPManager {
|
||||||
AppContext.mock.iapManager
|
AppContext.forPreviews.iapManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileManager {
|
extension ProfileManager {
|
||||||
public static var mock: ProfileManager {
|
public static var forPreviews: ProfileManager {
|
||||||
AppContext.mock.profileManager
|
AppContext.forPreviews.profileManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtendedTunnel {
|
extension ExtendedTunnel {
|
||||||
public static var mock: ExtendedTunnel {
|
public static var forPreviews: ExtendedTunnel {
|
||||||
AppContext.mock.tunnel
|
AppContext.forPreviews.tunnel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProviderManager {
|
extension ProviderManager {
|
||||||
public static var mock: ProviderManager {
|
public static var forPreviews: ProviderManager {
|
||||||
AppContext.mock.providerManager
|
AppContext.forPreviews.providerManager
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Profile+Mock.swift
|
// Profile+Previews.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 11/4/24.
|
// Created by Davide De Rosa on 11/4/24.
|
||||||
@ -27,7 +27,7 @@ import Foundation
|
|||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension Profile {
|
extension Profile {
|
||||||
public static let mock: Profile = {
|
public static let forPreviews: Profile = {
|
||||||
var profile = Profile.Builder()
|
var profile = Profile.Builder()
|
||||||
profile.name = "Mock profile"
|
profile.name = "Mock profile"
|
||||||
do {
|
do {
|
||||||
@ -63,7 +63,7 @@ extension Profile {
|
|||||||
|
|
||||||
public static func newMockProfile(withName name: String? = nil) -> Profile {
|
public static func newMockProfile(withName name: String? = nil) -> Profile {
|
||||||
do {
|
do {
|
||||||
var copy = mock.builder(withNewId: true)
|
var copy = forPreviews.builder(withNewId: true)
|
||||||
copy.name = name ?? String(copy.id.uuidString.prefix(8))
|
copy.name = name ?? String(copy.id.uuidString.prefix(8))
|
||||||
return try copy.tryBuild()
|
return try copy.tryBuild()
|
||||||
} catch {
|
} catch {
|
@ -76,9 +76,9 @@ private extension ConnectionStatusText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview("Connected") {
|
#Preview("Connected") {
|
||||||
ConnectionStatusText(tunnel: .mock)
|
ConnectionStatusText(tunnel: .forPreviews)
|
||||||
.task {
|
.task {
|
||||||
try? await ExtendedTunnel.mock.connect(with: .mock)
|
try? await ExtendedTunnel.forPreviews.connect(with: .forPreviews)
|
||||||
}
|
}
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
@ -95,9 +95,9 @@ private extension ConnectionStatusText {
|
|||||||
} catch {
|
} catch {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
return ConnectionStatusText(tunnel: .mock)
|
return ConnectionStatusText(tunnel: .forPreviews)
|
||||||
.task {
|
.task {
|
||||||
try? await ExtendedTunnel.mock.connect(with: profile)
|
try? await ExtendedTunnel.forPreviews.connect(with: profile)
|
||||||
}
|
}
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
.withMockEnvironment()
|
.withMockEnvironment()
|
||||||
|
@ -30,14 +30,14 @@ import PassepartoutKit
|
|||||||
final class MockProfileProcessor: ProfileProcessor {
|
final class MockProfileProcessor: ProfileProcessor {
|
||||||
var isIncludedCount = 0
|
var isIncludedCount = 0
|
||||||
|
|
||||||
var willRebuildCount = 0
|
|
||||||
|
|
||||||
var verifyCount = 0
|
|
||||||
|
|
||||||
var isIncludedBlock: (Profile) -> Bool = { _ in true }
|
var isIncludedBlock: (Profile) -> Bool = { _ in true }
|
||||||
|
|
||||||
|
var requiredFeaturesCount = 0
|
||||||
|
|
||||||
var requiredFeatures: Set<AppFeature>?
|
var requiredFeatures: Set<AppFeature>?
|
||||||
|
|
||||||
|
var willRebuildCount = 0
|
||||||
|
|
||||||
func title(for profile: Profile) -> String {
|
func title(for profile: Profile) -> String {
|
||||||
profile.name
|
profile.name
|
||||||
}
|
}
|
||||||
@ -51,13 +51,13 @@ final class MockProfileProcessor: ProfileProcessor {
|
|||||||
ProfilePreview(profile)
|
ProfilePreview(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requiredFeatures(_ profile: Profile) -> Set<AppFeature>? {
|
||||||
|
requiredFeaturesCount += 1
|
||||||
|
return requiredFeatures
|
||||||
|
}
|
||||||
|
|
||||||
func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
|
func willRebuild(_ builder: Profile.Builder) throws -> Profile.Builder {
|
||||||
willRebuildCount += 1
|
willRebuildCount += 1
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func verify(_ profile: Profile) -> Set<AppFeature>? {
|
|
||||||
verifyCount += 1
|
|
||||||
return requiredFeatures
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,8 @@ extension ProfileManagerTests {
|
|||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
|
|
||||||
XCTAssertEqual(processor.isIncludedCount, 1)
|
XCTAssertEqual(processor.isIncludedCount, 1)
|
||||||
|
XCTAssertEqual(processor.requiredFeaturesCount, 1)
|
||||||
XCTAssertEqual(processor.willRebuildCount, 0)
|
XCTAssertEqual(processor.willRebuildCount, 0)
|
||||||
XCTAssertEqual(processor.verifyCount, 1)
|
|
||||||
XCTAssertEqual(sut.requiredFeatures(forProfileWithId: profile.id), processor.requiredFeatures)
|
XCTAssertEqual(sut.requiredFeatures(forProfileWithId: profile.id), processor.requiredFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,10 @@ import UITesting
|
|||||||
|
|
||||||
extension AppContext {
|
extension AppContext {
|
||||||
static let shared: AppContext = {
|
static let shared: AppContext = {
|
||||||
|
let iapManager: IAPManager = .sharedForApp
|
||||||
|
let processor = InAppProcessor.shared(iapManager) {
|
||||||
|
$0.localizedPreview
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: ProfileManager
|
// MARK: ProfileManager
|
||||||
|
|
||||||
@ -65,7 +69,7 @@ extension AppContext {
|
|||||||
backupRepository: Configuration.ProfileManager.backupProfileRepository,
|
backupRepository: Configuration.ProfileManager.backupProfileRepository,
|
||||||
remoteRepositoryBlock: remoteRepositoryBlock,
|
remoteRepositoryBlock: remoteRepositoryBlock,
|
||||||
mirrorsRemoteRepository: Configuration.ProfileManager.mirrorsRemoteRepository,
|
mirrorsRemoteRepository: Configuration.ProfileManager.mirrorsRemoteRepository,
|
||||||
processor: IAPManager.sharedProcessor
|
processor: processor
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -74,7 +78,7 @@ extension AppContext {
|
|||||||
let tunnel = ExtendedTunnel(
|
let tunnel = ExtendedTunnel(
|
||||||
tunnel: Tunnel(strategy: Configuration.ExtendedTunnel.strategy),
|
tunnel: Tunnel(strategy: Configuration.ExtendedTunnel.strategy),
|
||||||
environment: .shared,
|
environment: .shared,
|
||||||
processor: IAPManager.sharedProcessor,
|
processor: processor,
|
||||||
interval: Constants.shared.tunnel.refreshInterval
|
interval: Constants.shared.tunnel.refreshInterval
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +122,7 @@ extension AppContext {
|
|||||||
let migrationManager = MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
let migrationManager = MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
||||||
|
|
||||||
return AppContext(
|
return AppContext(
|
||||||
iapManager: .sharedForApp,
|
iapManager: iapManager,
|
||||||
migrationManager: migrationManager,
|
migrationManager: migrationManager,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
providerManager: providerManager,
|
providerManager: providerManager,
|
||||||
|
@ -37,44 +37,6 @@ extension IAPManager {
|
|||||||
betaChecker: Configuration.IAPManager.betaChecker,
|
betaChecker: Configuration.IAPManager.betaChecker,
|
||||||
productsAtBuild: Configuration.IAPManager.productsAtBuild
|
productsAtBuild: Configuration.IAPManager.productsAtBuild
|
||||||
)
|
)
|
||||||
|
|
||||||
static let sharedProcessor = InAppProcessor(
|
|
||||||
iapManager: sharedForApp,
|
|
||||||
title: {
|
|
||||||
Configuration.ProfileManager.sharedTitle($0)
|
|
||||||
},
|
|
||||||
isIncluded: {
|
|
||||||
Configuration.ProfileManager.isIncluded($0, $1)
|
|
||||||
},
|
|
||||||
preview: {
|
|
||||||
$0.localizedPreview
|
|
||||||
},
|
|
||||||
verify: { iap, profile in
|
|
||||||
do {
|
|
||||||
try iap.verify(profile)
|
|
||||||
return nil
|
|
||||||
} catch AppError.ineligibleProfile(let requiredFeatures) {
|
|
||||||
return requiredFeatures
|
|
||||||
} catch {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
},
|
|
||||||
willRebuild: { _, builder in
|
|
||||||
builder
|
|
||||||
},
|
|
||||||
willInstall: { iap, profile in
|
|
||||||
try iap.verify(profile)
|
|
||||||
|
|
||||||
// validate provider modules
|
|
||||||
do {
|
|
||||||
_ = try profile.withProviderModules()
|
|
||||||
return profile
|
|
||||||
} catch {
|
|
||||||
pp_log(.app, .error, "Unable to inject provider modules: \(error)")
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Configuration
|
// MARK: - Configuration
|
||||||
@ -90,7 +52,7 @@ private extension Configuration.IAPManager {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static let simulatedInAppHelper: any AppProductHelper = {
|
static let simulatedInAppHelper: any AppProductHelper = {
|
||||||
guard !AppCommandLine.contains(.fakeIAP) else {
|
if AppCommandLine.contains(.fakeIAP) {
|
||||||
return FakeAppProductHelper()
|
return FakeAppProductHelper()
|
||||||
}
|
}
|
||||||
return inAppHelper
|
return inAppHelper
|
||||||
@ -98,7 +60,7 @@ private extension Configuration.IAPManager {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
static var simulatedAppReceiptReader: AppReceiptReader {
|
static var simulatedAppReceiptReader: AppReceiptReader {
|
||||||
guard !AppCommandLine.contains(.fakeIAP) else {
|
if AppCommandLine.contains(.fakeIAP) {
|
||||||
guard let mockHelper = inAppHelper as? FakeAppProductHelper else {
|
guard let mockHelper = inAppHelper as? FakeAppProductHelper else {
|
||||||
fatalError("When .isFakeIAP, productHelper is expected to be MockAppProductHelper")
|
fatalError("When .isFakeIAP, productHelper is expected to be MockAppProductHelper")
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,50 @@ extension TunnelEnvironment where Self == AppGroupEnvironment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: InAppProcessor
|
||||||
|
|
||||||
|
extension InAppProcessor {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
static func shared(_ iapManager: IAPManager, preview: @escaping (Profile) -> ProfilePreview) -> InAppProcessor {
|
||||||
|
InAppProcessor(
|
||||||
|
iapManager: iapManager,
|
||||||
|
title: {
|
||||||
|
Configuration.ProfileManager.sharedTitle($0)
|
||||||
|
},
|
||||||
|
isIncluded: {
|
||||||
|
Configuration.ProfileManager.isIncluded($0, $1)
|
||||||
|
},
|
||||||
|
preview: preview,
|
||||||
|
requiredFeatures: { iap, profile in
|
||||||
|
do {
|
||||||
|
try iap.verify(profile)
|
||||||
|
return nil
|
||||||
|
} catch AppError.ineligibleProfile(let requiredFeatures) {
|
||||||
|
return requiredFeatures
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
willRebuild: { _, builder in
|
||||||
|
builder
|
||||||
|
},
|
||||||
|
willInstall: { iap, profile in
|
||||||
|
try iap.verify(profile)
|
||||||
|
|
||||||
|
// validate provider modules
|
||||||
|
do {
|
||||||
|
_ = try profile.withProviderModules()
|
||||||
|
return profile
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .error, "Unable to inject provider modules: \(error)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Configuration
|
// MARK: - Configuration
|
||||||
|
|
||||||
enum Configuration {
|
enum Configuration {
|
||||||
|
73
Passepartout/Shared/Testing/AppContext+Testing.swift
Normal file
73
Passepartout/Shared/Testing/AppContext+Testing.swift
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// AppContext+Testing.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/28/24.
|
||||||
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of Passepartout.
|
||||||
|
//
|
||||||
|
// Passepartout is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Passepartout is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
|
import CommonUtils
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
import UILibrary
|
||||||
|
|
||||||
|
extension AppContext {
|
||||||
|
static func forUITesting(withRegistry registry: Registry) -> AppContext {
|
||||||
|
let iapManager = IAPManager(
|
||||||
|
customUserLevel: .subscriber,
|
||||||
|
inAppHelper: FakeAppProductHelper(),
|
||||||
|
receiptReader: FakeAppReceiptReader(),
|
||||||
|
betaChecker: TestFlightChecker(),
|
||||||
|
productsAtBuild: { _ in
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let processor = InAppProcessor.shared(iapManager) {
|
||||||
|
$0.localizedPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
let profileManager: ProfileManager = .forTesting(
|
||||||
|
withRegistry: .shared,
|
||||||
|
processor: processor
|
||||||
|
)
|
||||||
|
let tunnelEnvironment = InMemoryEnvironment()
|
||||||
|
let tunnel = ExtendedTunnel(
|
||||||
|
tunnel: Tunnel(strategy: FakeTunnelStrategy(environment: tunnelEnvironment)),
|
||||||
|
environment: tunnelEnvironment,
|
||||||
|
processor: processor,
|
||||||
|
interval: Constants.shared.tunnel.refreshInterval
|
||||||
|
)
|
||||||
|
let providerManager = ProviderManager(
|
||||||
|
repository: InMemoryProviderRepository()
|
||||||
|
)
|
||||||
|
let migrationManager = MigrationManager()
|
||||||
|
|
||||||
|
return AppContext(
|
||||||
|
iapManager: iapManager,
|
||||||
|
migrationManager: migrationManager,
|
||||||
|
profileManager: profileManager,
|
||||||
|
providerManager: providerManager,
|
||||||
|
registry: registry,
|
||||||
|
tunnel: tunnel,
|
||||||
|
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// ProfileManager+Mock.swift
|
// ProfileManager+Testing.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 11/28/24.
|
// Created by Davide De Rosa on 11/28/24.
|
||||||
@ -28,7 +28,7 @@ import Foundation
|
|||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
extension ProfileManager {
|
extension ProfileManager {
|
||||||
public static func mock(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager {
|
public static func forTesting(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager {
|
||||||
let repository = InMemoryProfileRepository()
|
let repository = InMemoryProfileRepository()
|
||||||
let remoteRepository = InMemoryProfileRepository()
|
let remoteRepository = InMemoryProfileRepository()
|
||||||
let manager = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let manager = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
Loading…
Reference in New Issue
Block a user