Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ConfigureTipKit Module #38

Merged
merged 5 commits into from
Jun 26, 2024
Merged
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
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 {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
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 {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
Tips.showAllTipsForTesting()

Check warning on line 42 in Sources/SpeziViews/Modules/ConfigureTipKit.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziViews/Modules/ConfigureTipKit.swift#L42

Added line #L42 was not covered by tests
}
do {
try Tips.configure(configuration)
} catch {
logger.error("Failed to configure TipKit: \(error)")

Check warning on line 47 in Sources/SpeziViews/Modules/ConfigureTipKit.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SpeziViews/Modules/ConfigureTipKit.swift#L47

Added line #L47 was not covered by tests
}
}
}


extension RuntimeConfig {
/// Enable testing tips
@_spi(TestingSupport)
public static let testingTips = CommandLine.arguments.contains("--testTips")
Supereg marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading