Skip to content

Commit

Permalink
Add ConfigureTipKit Module (#38)
Browse files Browse the repository at this point in the history
# Add ConfigureTipKit Module

## ♻️ Current situation & Problem
This PR adds the `ConfigureTipKit` module to easily configure TipKit (or
require TipKit to be configured) across the Spezi framework ecosystem.


## ⚙️ Release Notes 
* Add new `ConfigureTipKit` module.


## 📚 Documentation
Documentation catalog was adjusted.


## ✅ Testing
Basic testing was added that calls the new code paths.

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
Supereg authored Jun 26, 2024
1 parent 0899c34 commit ff61e65
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 7 deletions.
56 changes: 51 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@
// SPDX-License-Identifier: MIT
//

import class Foundation.ProcessInfo
import PackageDescription


#if swift(<6)
let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("SwiftConcurrency")
#else
let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("SwiftConcurrency")
#endif


let package = Package(
name: "SpeziViews",
defaultLocalization: "en",
Expand All @@ -27,33 +35,71 @@ let package = Package(
.library(name: "SpeziValidation", targets: ["SpeziValidation"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.15.3")
],
] + swiftLintPackage(),
targets: [
.target(
name: "SpeziViews"
name: "SpeziViews",
dependencies: [
.product(name: "Spezi", package: "Spezi")
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
name: "SpeziPersonalInfo",
dependencies: [
.target(name: "SpeziViews")
]
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
name: "SpeziValidation",
dependencies: [
.target(name: "SpeziViews"),
.product(name: "OrderedCollections", package: "swift-collections")
]
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
),
.testTarget(
name: "SpeziViewsTests",
dependencies: [
.target(name: "SpeziViews"),
.target(name: "SpeziValidation"),
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
]
],
swiftSettings: [
swiftConcurrency
],
plugins: [] + swiftLintPlugin()
)
]
)


func swiftLintPlugin() -> [Target.PluginUsage] {
// Fully quit Xcode and open again with `open --env SPEZI_DEVELOPMENT_SWIFTLINT /Applications/Xcode.app`
if ProcessInfo.processInfo.environment["SPEZI_DEVELOPMENT_SWIFTLINT"] != nil {
[.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")]
} else {
[]
}
}

func swiftLintPackage() -> [PackageDescription.Package.Dependency] {
if ProcessInfo.processInfo.environment["SPEZI_DEVELOPMENT_SWIFTLINT"] != nil {
[.package(url: "https://github.com/realm/SwiftLint.git", .upToNextMinor(from: "0.55.1"))]
} else {
[]
}
}
57 changes: 57 additions & 0 deletions Sources/SpeziViews/Modules/ConfigureTipKit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Spezi
@_spi(TestingSupport) import SpeziFoundation
import TipKit


/// Configure TipKit.
///
/// This module allows to easily and globally configure [TipKit](https://developer.apple.com/documentation/TipKit) by calling
/// [`Tips/configure(_:)`](https://developer.apple.com/documentation/tipkit/tips/configure(_:)).
/// You can use the Spezi Dependency system to require TipKit to be configured or can use the `@Environment` property wrapper in your
/// SwiftUI views to verify that TipKit was configured when using TipKit-based View components.
///
/// - Note: The Module will automatically [`showAllTipsForTesting()`](https://developer.apple.com/documentation/tipkit/tips/showalltipsfortesting())
/// if either the Module is initialized within a SwiftUI preview or the `testingTips` <doc:SPI#RuntimeConfig> is supplied via the command line.
public class ConfigureTipKit: Module, DefaultInitializable, EnvironmentAccessible {
private let configuration: [Tips.ConfigurationOption]

@Application(\.logger) private var logger


/// Configure TipKit.
/// - Parameter configuration: TipKit configuration options.
public init(_ configuration: [Tips.ConfigurationOption]) {
self.configuration = configuration
}

/// Configure TipKit with default options.
public required convenience init() {
self.init([])
}

public func configure() {
if RuntimeConfig.testingTips || ProcessInfo.processInfo.isPreviewSimulator {
Tips.showAllTipsForTesting()
}
do {
try Tips.configure(configuration)
} catch {
logger.error("Failed to configure TipKit: \(error)")
}
}
}


extension RuntimeConfig {
/// Enable testing tips
@_spi(TestingSupport)
public static let testingTips = CommandLine.arguments.contains("--testTips")
}
3 changes: 3 additions & 0 deletions Sources/SpeziViews/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
}
}
}
},
"Dismiss" : {

},
"Error" : {
"comment" : "View State default error title",
Expand Down
41 changes: 41 additions & 0 deletions Sources/SpeziViews/SpeziViews.docc/SPI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# System Programming Interfaces

<!--
#
# This source file is part of the Stanford Spezi open-source project
#
# SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
#
# SPDX-License-Identifier: MIT
#
-->

An overview of System Programming Interfaces (SPIs) provided by SpeziViews.

## Overview

A [System Programming Interface](https://blog.eidinger.info/system-programming-interfaces-spi-in-swift-explained) is a subset of API
that is targeted only for certain users (e.g., framework developers) and might not be necessary or useful for app development.
Therefore, these interfaces are not visible by default and need to be explicitly imported.
This article provides an overview of supported SPI provided by SpeziFoundation

### TestingSupport

The `TestingSupport` SPI provides additional interfaces that are useful for unit and UI testing.
Annotate your import statement as follows.

```swift
@_spi(TestingSupport) import SpeziViews
```

#### RuntimeConfig

[`RuntimeConfig`](https://swiftpackageindex.com/stanfordspezi/spezifoundation/documentation/spezifoundation/spi#RuntimeConfig) is provided by
[SpeziFoundation](https://swiftpackageindex.com/stanfordspezi/spezifoundation/documentation/spezifoundation) for a central place to
provide runtime configurations.

SpeziViews adds the following extensions:

- `RuntimeConfig/testingTips`: Holds `true` if the `--testTips` command line flag was supplied to indicate to always show Tips when using
``ConfigureTipKit``.

7 changes: 7 additions & 0 deletions Sources/SpeziViews/SpeziViews.docc/SpeziViews.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,10 @@ Automatically adapt your view layouts to dynamic type sizes, device orientation,

- ``AnyLocalizedError``
- ``SwiftUI/EnvironmentValues/defaultErrorDescription``

### Modules

- ``ConfigureTipKit``

### System Programming Interfaces
- <doc:SPI>
14 changes: 14 additions & 0 deletions Tests/UITests/TestApp/TestApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziViews
import SwiftUI
import XCTestApp


class TestDelegate: SpeziAppDelegate {
override var configuration: Configuration {
Configuration {
ConfigureTipKit()
}
}
}


@main
struct UITestsApp: App {
@ApplicationDelegateAdaptor(TestDelegate.self) private var delegate

var body: some Scene {
WindowGroup {
SpeziViewsTargetsTests()
.spezi(delegate)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import XCTestExtensions


final class PersonalInfoViewsTests: XCTestCase {
@MainActor
override func setUpWithError() throws {
try super.setUpWithError()

Expand All @@ -22,6 +23,7 @@ final class PersonalInfoViewsTests: XCTestCase {
app.open(target: "SpeziPersonalInfo")
}

@MainActor
func testNameFields() throws {
let app = XCUIApplication()

Expand All @@ -38,6 +40,7 @@ final class PersonalInfoViewsTests: XCTestCase {
XCTAssert(app.textFields["Stanford"].waitForExistence(timeout: 2))
}

@MainActor
func testUserProfile() throws {
let app = XCUIApplication()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import XCTestExtensions


final class ValidationTests: XCTestCase {
@MainActor
override func setUpWithError() throws {
try super.setUpWithError()

Expand All @@ -22,13 +23,15 @@ final class ValidationTests: XCTestCase {
app.open(target: "SpeziValidation")
}

@MainActor
func testDefaultRules() {
let app = XCUIApplication()

XCTAssert(app.buttons["ValidationRules"].waitForExistence(timeout: 2))
app.buttons["ValidationRules"].tap()
}

@MainActor
func testValidationWithFocus() throws {
let app = XCUIApplication()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import XCTestExtensions


final class EnvironmentTests: XCTestCase {
@MainActor
override func setUpWithError() throws {
try super.setUpWithError()

Expand All @@ -22,6 +23,7 @@ final class EnvironmentTests: XCTestCase {
app.open(target: "SpeziViews")
}

@MainActor
func testDefaultErrorDescription() throws {
let app = XCUIApplication()

Expand Down
6 changes: 6 additions & 0 deletions Tests/UITests/TestAppUITests/SpeziViews/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import XCTestExtensions


final class ModelTests: XCTestCase {
@MainActor
override func setUpWithError() throws {
try super.setUpWithError()

Expand All @@ -22,6 +23,7 @@ final class ModelTests: XCTestCase {
app.open(target: "SpeziViews")
}

@MainActor
func testViewState() throws {
let app = XCUIApplication()

Expand Down Expand Up @@ -50,6 +52,7 @@ final class ModelTests: XCTestCase {
XCTAssert(app.staticTexts["View State: idle"].waitForExistence(timeout: 2))
}

@MainActor
func testOperationState() throws {
let app = XCUIApplication()

Expand Down Expand Up @@ -81,6 +84,7 @@ final class ModelTests: XCTestCase {
XCTAssert(content.contains("Operation State: error"))
}

@MainActor
func testViewStateMapper() throws {
let app = XCUIApplication()

Expand Down Expand Up @@ -117,6 +121,7 @@ final class ModelTests: XCTestCase {
XCTAssert(content.contains("Operation State: error"))
}

@MainActor
func testConditionalModifier() throws {
let app = XCUIApplication()

Expand Down Expand Up @@ -149,6 +154,7 @@ final class ModelTests: XCTestCase {
XCTAssert(app.staticTexts["Closure Condition present"].waitForExistence(timeout: 2))
}

@MainActor
func testDefaultErrorDescription() throws {
let app = XCUIApplication()

Expand Down
Loading

0 comments on commit ff61e65

Please sign in to comment.