a native ios client for gitbay

client ios swift

https://gitbay.org

release: screenshots, privacy manifest, export compliance, fixtures !30

merged cmc wants to merge krz/gitbay-ios:appstore-screenshots into main

4 files changed, +107 −6

gitbay.xcodeproj/project.pbxproj +2
@@ -415,6 +415,7 @@
415415 GENERATE_INFOPLIST_FILE = YES;
416416 INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
417417 INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
418 INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
418419 INFOPLIST_KEY_UILaunchScreen_Generation = YES;
419420 INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
420421 INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
@@ -447,6 +448,7 @@
447448 GENERATE_INFOPLIST_FILE = YES;
448449 INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
449450 INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
451 INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
450452 INFOPLIST_KEY_UILaunchScreen_Generation = YES;
451453 INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
452454 INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
gitbay/PrivacyInfo.xcprivacy added +33
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <!-- No tracking: the app talks only to the gitbay instance the user
6 names at sign-in, and to nothing else. -->
7 <key>NSPrivacyTracking</key>
8 <false/>
9 <key>NSPrivacyTrackingDomains</key>
10 <array/>
11
12 <!-- Nothing is collected. The API token is held in the Keychain on
13 the device and sent only to that instance; no analytics, no
14 crash reporting, no third-party SDK. -->
15 <key>NSPrivacyCollectedDataTypes</key>
16 <array/>
17
18 <key>NSPrivacyAccessedAPITypes</key>
19 <array>
20 <dict>
21 <!-- SessionStore keeps which account is active across
22 relaunches. App's own data, read and written by this
23 app only. -->
24 <key>NSPrivacyAccessedAPIType</key>
25 <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
26 <key>NSPrivacyAccessedAPITypeReasons</key>
27 <array>
28 <string>CA92.1</string>
29 </array>
30 </dict>
31 </array>
32</dict>
33</plist>
gitbay/gitbayApp.swift +11 −3
@@ -17,9 +17,17 @@ struct gitbayApp: App {
1717 .tint(.gbAccent)
1818 // The screenshot checkpoint forces appearance per run;
1919 // simulator clones ignore the base device's setting.
20 .preferredColorScheme(
21 ProcessInfo.processInfo.arguments.contains("-gb-dark") ? .dark : nil
22 )
20 .preferredColorScheme(Self.forcedColorScheme)
2321 }
2422 }
23
24 /// A clone follows whatever appearance it was created with, so the
25 /// checkpoint has to name the one it wants. Neither flag means the
26 /// app follows the system, as it does for real users.
27 private static var forcedColorScheme: ColorScheme? {
28 let args = ProcessInfo.processInfo.arguments
29 if args.contains("-gb-dark") { return .dark }
30 if args.contains("-gb-light") { return .light }
31 return nil
32 }
2533 }
gitbayUITests/LiveSmokeUITests.swift +61 −3
@@ -33,8 +33,10 @@ final class LiveSmokeUITests: XCTestCase {
3333 app = XCUIApplication()
3434 // xcodebuild runs tests on simulator clones, so the base
3535 // device's appearance never applies; force it per run.
36 if ProcessInfo.processInfo.environment["GITBAY_UITEST_DARK"] == "1" {
37 app.launchArguments.append("-gb-dark")
36 switch ProcessInfo.processInfo.environment["GITBAY_UITEST_DARK"] {
37 case "1": app.launchArguments.append("-gb-dark")
38 case "0": app.launchArguments.append("-gb-light")
39 default: break // follow the device, as a real launch does
3840 }
3941 app.launch()
4042 if name.contains("testRepoManagementFlows") {
@@ -44,6 +46,9 @@ final class LiveSmokeUITests: XCTestCase {
4446 ensureFixtureIssue()
4547 try requireFixtureBranch()
4648 }
49 if name.contains("testBlameAndEditFlows") {
50 try requireFixtureRepo()
51 }
4752 }
4853
4954 override func tearDownWithError() throws {
@@ -273,6 +278,21 @@ final class LiveSmokeUITests: XCTestCase {
273278 keys.tap()
274279 }
275280
281 /// The edit flow saves a change to a real file. Seeding a
282 /// repository's first commit is a push, which no test can make, so
283 /// this only checks and says why, rather than failing later at
284 /// "cmc/ui-smoke-edit not in the repo list".
285 private func requireFixtureRepo() throws {
286 guard let data = readCommand(["repo", "show", "cmc/ui-smoke-edit"]),
287 let envelope = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
288 else { return } // no token, or the instance is unreachable
289 try XCTSkipUnless(envelope["data"] != nil, """
290 cmc/ui-smoke-edit is missing. The edit flow saves a change to \
291 notes.txt in it; recreate the repo with that file (see its \
292 README) and run again.
293 """)
294 }
295
276296 /// Scroll a list until an element is in the hierarchy. Offscreen
277297 /// rows do not exist to XCUITest, so waitForExistence alone fails on
278298 /// anything below the fold.
@@ -291,7 +311,12 @@ final class LiveSmokeUITests: XCTestCase {
291311 /// swallowed silently, so this taps until the tab reports selected.
292312 func selectTab(_ name: String,
293313 file: StaticString = #filePath, line: UInt = #line) {
294 let tab = app.tabBars.buttons[name].firstMatch
314 // iPadOS renders a TabView as a sidebar or top bar rather than a
315 // bottom tab bar, so the control is not under app.tabBars there.
316 var tab = app.tabBars.buttons[name].firstMatch
317 if !tab.waitForExistence(timeout: 5) {
318 tab = app.buttons[name].firstMatch
319 }
295320 XCTAssertTrue(tab.waitForExistence(timeout: 15),
296321 "\(name) tab missing", file: file, line: line)
297322 // The app is interactive once its own chrome is hittable.
@@ -994,3 +1019,36 @@ extension LiveSmokeUITests {
9941019 .waitForExistence(timeout: 20), "build detail shows no timing")
9951020 }
9961021 }
1022
1023extension LiveSmokeUITests {
1024
1025 /// Store screenshots on iPad. It navigates by tab and through
1026 /// Explore rather than reusing the iPhone checkpoint: that one drives
1027 /// the repo list's `.searchable`, which iPadOS lays out differently,
1028 /// and chasing those differences is the iPad pass this release
1029 /// deferred. Tabs and a list row work on both.
1030 func testIPadScreenshotCheckpoint() throws {
1031 signInIfNeeded()
1032 snap("dashboard")
1033
1034 selectTab("Explore")
1035 snap("explore")
1036
1037 // First public repo in the list no search field involved.
1038 let firstRepo = app.cells.firstMatch
1039 XCTAssertTrue(firstRepo.waitForExistence(timeout: 20), "explore listed nothing")
1040 firstRepo.tap()
1041 XCTAssertTrue(app.staticTexts["Files"].firstMatch.waitForExistence(timeout: 20),
1042 "repo screen did not open")
1043 snap("repo")
1044
1045 selectTab("Feed")
1046 snap("feed")
1047
1048 selectTab("My Profile")
1049 XCTAssertTrue(app.staticTexts
1050 .containing(NSPredicate(format: "label BEGINSWITH 'Repositories'")).firstMatch
1051 .waitForExistence(timeout: 20), "profile did not load")
1052 snap("profile")
1053 }
1054}