Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@v4
with:
repository: qtmleap/Mudmouth
ref: 5c97774f645023b22366db5abe86797915da2d33
ref: 3c1468aaaea5140835982bc8e38c9d3bfc49d2a0
path: Mudmouth
persist-credentials: false

Expand Down
2 changes: 1 addition & 1 deletion Interceptor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@
repositoryURL = "https://github.com/qtmleap/QuantumLeap.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.0.4;
minimumVersion = 1.0.0;
};
};
10FBBF2B2E522C5E00CC57B0 /* XCRemoteSwiftPackageReference "Runestone" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 16 additions & 2 deletions Interceptor/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct SettingsView: View {
var body: some View {
Form(content: {
QuantumLeap.Support()
.environment(\.isFirstLaunch, $showSetup)
Section {
Button("Set Up Capture") { showSetup = true }
.disabled(consentVersion != CaptureAuthorization.version)
Expand All @@ -33,13 +34,17 @@ struct SettingsView: View {
Link("Privacy Policy", destination: URL(string: "https://qleap.jp/term/interceptor_privacy_policy")!)
Link("Developers", destination: URL(string: "https://qleap.jp")!)
NavigationLink("Licenses") {
LicenseListView().licenseViewStyle(.withRepositoryAnchorLink)
LicenseListView().licenseViewStyle(InlineRepositoryLicenseViewStyle())
.navigationTitle("Licenses")
.navigationBarTitleDisplayMode(.inline)
}
}
QuantumLeap.Version()
})
.fullScreenCover(isPresented: $showSetup) { FirstLaunchView() }
.sheet(isPresented: $showSetup) {
FirstLaunchView()
.presentationDetents([.large])
}
.navigationTitle(Text("TITLE_SETTINGS"))
.navigationBarTitleDisplayMode(.inline)
}
Expand All @@ -50,3 +55,12 @@ struct SettingsView: View {
.environmentIsFirstLaunch()
.environmentObject(Tuberose.default)
}

private struct InlineRepositoryLicenseViewStyle: LicenseViewStyle {
@MainActor
func makeBody(configuration: Configuration) -> some View {
WithRepositoryAnchorLinkLicenseViewStyle()
.makeBody(configuration: configuration)
.navigationBarTitleDisplayMode(.inline)
}
}
59 changes: 50 additions & 9 deletions InterceptorUITests/InterceptorUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ final class InterceptorUITests: XCTestCase {
// Use XCTAssert and related functions to verify your tests produce the correct results.
}

@MainActor
func testHowToUseCanCloseAndReopen() throws {
let app = XCUIApplication()
app.launchArguments = ["-AppleLanguages", "(en)", "-AppleLocale", "en_US"]
app.launch()
agreeIfNeeded(app)
tab(app, named: "Settings").tap()
for _ in 0..<2 {
settingsButton(app, named: "How to Use").tap()
XCTAssertTrue(app.buttons["Next"].waitForExistence(timeout: 5))
XCTAssertTrue(app.buttons["Close"].isHittable)
attachScreenshot(app, named: "How to Use sheet")
app.buttons["Close"].tap()
XCTAssertTrue(app.buttons["Next"].waitForNonExistence(timeout: 5))
XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5))
XCTAssertFalse(app.buttons["Agree"].exists)
}
settingsButton(app, named: "Set Up Capture").tap()
XCTAssertTrue(app.buttons["Next"].waitForExistence(timeout: 5))
app.buttons["Close"].tap()
XCTAssertTrue(app.buttons["Next"].waitForNonExistence(timeout: 5))
XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5))
}

@MainActor
func testCaptureConsentLifecycle() throws {
let app = XCUIApplication()
Expand Down Expand Up @@ -348,15 +372,13 @@ final class InterceptorUITests: XCTestCase {
XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5))
let autoConnect = app.switches["Auto Connect"]
XCTAssertTrue(autoConnect.waitForExistence(timeout: 5))
let initialValue = try XCTUnwrap(autoConnect.value as? String)
// SwiftUI exposes the labeled row as the switch's accessibility frame.
// Tap the trailing control rather than the center of the row's label.
let control = autoConnect.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5))
let control = try switchControl(app, row: autoConnect)
let initialValue = try XCTUnwrap(control.value as? String)
control.tap()
let changed = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value != %@", initialValue), object: autoConnect)
let changed = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value != %@", initialValue), object: control)
XCTAssertEqual(XCTWaiter.wait(for: [changed], timeout: 5), .completed)
control.tap()
let restored = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value == %@", initialValue), object: autoConnect)
let restored = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value == %@", initialValue), object: control)
XCTAssertEqual(XCTWaiter.wait(for: [restored], timeout: 5), .completed)
attachScreenshot(app, named: "Settings")

Expand All @@ -365,16 +387,26 @@ final class InterceptorUITests: XCTestCase {
XCTAssertTrue(app.staticTexts["api.lp1.av5ja.srv.nintendo.net"].exists)
XCTAssertTrue(app.staticTexts["app.splatoon2.nintendo.net"].exists)
attachScreenshot(app, named: "Proxy Hosts")
app.navigationBars.buttons.firstMatch.tap()
returnFromSettingsDetail(app)

settingsButton(app, named: "Token List").tap()
XCTAssertTrue(app.navigationBars["Token List"].waitForExistence(timeout: 5))
attachScreenshot(app, named: "Token List")
app.navigationBars.buttons.firstMatch.tap()
returnFromSettingsDetail(app)

settingsButton(app, named: "Certificate").tap()
XCTAssertTrue(app.navigationBars["Certificate"].waitForExistence(timeout: 5))
app.navigationBars.buttons.firstMatch.tap()
returnFromSettingsDetail(app)
settingsButton(app, named: "Licenses").tap()
XCTAssertTrue(app.navigationBars["Licenses"].waitForExistence(timeout: 5))
attachScreenshot(app, named: "Licenses detail title")
let license = app.staticTexts["BetterSafariView"].firstMatch
XCTAssertTrue(license.waitForExistence(timeout: 5))
license.tap()
XCTAssertTrue(app.navigationBars["BetterSafariView"].waitForExistence(timeout: 5))
attachScreenshot(app, named: "Individual license inline title")
app.navigationBars.buttons["Licenses"].firstMatch.tap()
returnFromSettingsDetail(app)
tab(app, named: "Home").tap()

app.navigationBars.buttons.firstMatch.tap()
Expand All @@ -388,6 +420,15 @@ final class InterceptorUITests: XCTestCase {
XCTAssertFalse(app.buttons["Next"].exists, "Completed onboarding must remain dismissed after relaunch")
}

@MainActor
private func returnFromSettingsDetail(_ app: XCUIApplication) {
// iPad replaces the secondary root while keeping Settings visible in the sidebar.
// iPhone pushes the destination and exposes Settings as its back button.
let back = app.navigationBars.buttons["Settings"].firstMatch
if back.exists { back.tap() }
XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5))
}

@MainActor
private func switchControl(_ app: XCUIApplication, row: XCUIElement) throws -> XCUIElement {
// iPadOS 18 exposes the UISwitch beside its labeled row; newer versions nest it.
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This is an iOS application that uses a self-signed certificate to obtain an acce
### Requirements

- iOS 17 or later
- Xcode with Swift 6.1 or later (required by Mudmouth)
- Xcode 27 with Swift 6.4 or later (required by QuantumLeap 1.0.0)
- fastlane

The simulator build was verified with Xcode 27.0 on 2026-10-04 using
Expand All @@ -30,13 +30,14 @@ From the Interceptor directory, clone Mudmouth if it does not already exist:

```bash
git clone https://github.com/qtmleap/Mudmouth.git ../Mudmouth
git -C ../Mudmouth checkout 698d5bcb25245a26a3be925c34ded8a4c8a2ff6a
git -C ../Mudmouth checkout 3c1468aaaea5140835982bc8e38c9d3bfc49d2a0
xcodebuild -resolvePackageDependencies -project Interceptor.xcodeproj -scheme Interceptor
open Interceptor.xcodeproj
```

The revision above is the Mudmouth revision selected for the local setup on
2026-10-04. Keep the checkout on its development branch when editing Mudmouth;
2026-10-06 and matches CI. It removes the unused Introspect 1.x dependency so
QuantumLeap 1.0.0 can resolve Introspect 27.x. Keep the checkout on its development branch when editing Mudmouth;
record any dependency revision changes alongside the application changes.

To check simulator compilation without signing:
Expand Down
Loading