message: [getting-started-updates] - Replaces Programmatic Views with Programmatic UIKit for consistency and clarity of what is actually being set up - MW

diff:
# Interested in seeing examples of Workflow in action?
Start by cloning the repo and checking out the 'WorkflowExample' scheme. This should give you a decent idea of how the library works. If you want to create a new project, read on.
-# CocoaPods with Programmatic Views
+# CocoaPods with Programmatic UIKit
## Adding the dependency
```ruby


message: [rename] - Adds comment as to why this class is there but does not have implementation - RAG MW

change_type: MODIFY

diff:
import UIKit
@main
-class AppDelegate: UIResponder, UIApplicationDelegate { }
+class AppDelegate: UIResponder, UIApplicationDelegate {
+ // No implementation necessary as it would only include boilerplate.
+}

license: Apache License 2.0

repo: wwt/swiftcurrent


message: [styleguide-attributes] - Adds preference for attributes on the same line as what they attribute to the StyleGuide - MW

diff:
@@ -743,6 +743,37 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
```
</details>
+* **PREFER attributes on the same line as what they are attributing.**
+
+ <details>
+
+ #### Why?
+
+ Because we naturally read lines of code as a consistent line, introducing whitespace makes it that much harder to understand the full context of the line you're trying to understand. This is the same reasoning we put behind preferring a return on the same line as the final parameter in a function declaration.
+
+ NOTE: This does not come at the cost of sanity. If you have an exceptionally large number of attributions then it might indicate that you've gotten overzealous, and it actually obscures your meaning, rather than add to it.
+
+ ```swift
+ // WRONG
+ @objc
+ func thingExposedToObjectiveC() {
+ // ...
+ }
+
+ // WRONG
+ @ViewBuilder @discardableResult @MainActor @inlinable func betYouDoNotEvenKnowWhatThisFunctionDoesAnymore() {
+ // ...
+ }
+
+ // RIGHT
+ @State var stateVar: Bool = true
+ @discardableResult func thing() -> Bool { true }
+ @MainActor class MyClass {
+ // ...
+ }
+ ```
+ </details>
+
### Functions
* **PREFER omitting `Void` return types from function definitions.**


message: [styleguide-attributes] - Splits up attributes preferences to inline for functions, properties, and closures and separate line for classes, structs, and actors - MOB

diff:
@@ -743,13 +743,13 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
```
</details>
-* **PREFER attributes on the same line as what they are attributing.**
+* **PREFER attributes on the same line as what they are attributing for properties, functions, and closures.**
<details>
#### Why?
- Because we naturally read lines of code as a consistent line, introducing whitespace makes it that much harder to understand the full context of the line you're trying to understand. This is the same reasoning used for preferring a return on the same line as the final parameter in a function declaration.
+ Because we naturally read lines of code as a consistent line, introducing whitespace makes it that much harder to understand the full context of the line you're trying to understand. This is the same reasoning used for preferring a return on the same line as the final parameter in a function declaration. A notable exception to this preference is the `@available` attribute which should always be on its own line.
NOTE: This does not come at the cost of sanity. If you have an exceptionally large number of attributions then it might indicate that you've gotten overzealous, and it actually obscures your meaning, rather than add to it.
@@ -765,10 +765,60 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
// ...
}
+ // WRONG
+ @available(iOS 14.0, *) func doSomething() {
+ // ...
+ }
+
// RIGHT
@State var stateVar: Bool = true
@discardableResult func thing() -> Bool { true }
- @MainActor class MyClass {
+ @MainActor @ViewBuilder func createViews() {
+ // ...
+ }
+
+ // RIGHT
+ @available(iOS 14.0, *)
+ func doSomething() {
+ // ...
+ }
+ ```
+ </details>
+
+* **PREFER attributes on a separate line as what they are attributing for classes, structs, and actors.**
+
+ <details>
+
+ #### Why?
+ This is the convention set by Apple in the [Swift attribute docs](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html). A notable exception to this preference is the `@available` attribute which should always be on its own line.
+
+
+ ```swift
+ // WRONG
+ @dynamicCallable class MyClass {
+ // ...
+ }
+
+ // WRONG
+ @MainActor struct MyStruct {
+ // ...
+ }
+
+ // WRONG
+ @available(iOS 14.0, *) @MainActor
+ struct MyStruct {
+ // ...
+ }
+
+ // RIGHT
+ @dynamicCallable class MyClass {
+ // ...
+ }
+
+ // RIGHT
+ @available(iOS 14.0, *)
+ @propertyWrapper
+ struct MyPropertyWrapper {
// ...
}
```


message: [styleguide-attributes] - Includes additional example with multiple attributes applied to a class and fixes a wording error - MOB

diff:
@@ -785,7 +785,7 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
```
</details>
-* **PREFER attributes on a separate line as what they are attributing for classes, structs, and actors.**
+* **PREFER attributes on a separate line from what they are attributing for classes, structs, and actors.**
<details>
@@ -821,6 +821,13 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
struct MyPropertyWrapper {
// ...
}
+
+ // RIGHT
+ @available(iOS 13, *)
+ @resultBuilder @requires_stored_property_inits
+ class WorkflowBuilder {
+ // ...
+ }
```
</details>


message: [styleguide-attributes] - Expands examples for content order to include additional attribute order - MW

diff:
@@ -2212,7 +2212,7 @@ Each guide is broken into a few sections. Sections contain a list of guidelines.
#### Why?
- Things that can potentially effect more of the codebase go first. Similarly, related things (like the same property wrappers) take sorting precedence over the below list. e.g. `@EnvironmentObject` comes before `@State`; `public` comes before `private`; `typealias`'s are potentially more affecting and thus go at the start.
+ Things that can potentially affect more of the codebase go first. Similarly, related things (like the same property wrappers) take sorting precedence over the below list. e.g. `@EnvironmentObject` comes before `@State`; `public` comes before `private`; ` @MainActor` comes before `@ViewBuilder`; `typealias`'s are potentially more affecting and thus go at the start.
Still extract as necessary into extensions, but ensure those extensions also conform to the rule.


message: [swiftui] - Fixes warnings - MOB

diff:
@@ -21,11 +21,7 @@ extension Notification.Name {
extension FlowRepresentable {
/// :nodoc: Your tests may want to manually set the closure so they can make assertions it was called, this is simply a convenience available for that.
public var proceedInWorkflowStorage: ((AnyWorkflow.PassedArgs) -> Void)? {
- get {
- {
- _workflowPointer?.proceedInWorkflowStorage?($0)
- }
- }
+ get { { _workflowPointer?.proceedInWorkflowStorage?($0) } }
set {
_workflowPointer?.proceedInWorkflowStorage = { args in
newValue?(args)
@@ -35,11 +31,7 @@ extension FlowRepresentable {
/// :nodoc: Designed for V1 and V2 people who used to assign to proceedInWorkflow for tests. This auto extracts args.
public var _proceedInWorkflow: ((Any?) -> Void)? {
- get {
- {
- _workflowPointer?.proceedInWorkflowStorage?(.args($0))
- }
- }
+ get { { _workflowPointer?.proceedInWorkflowStorage?(.args($0)) } }
set {
_workflowPointer?.proceedInWorkflowStorage = { args in
newValue?(args.extractArgs(defaultValue: nil))

diff:
-//
+// swiftlint:disable:this file_name
// AnyWorkflowExtensions.swift
// SwiftCurrent
//

diff:
import SwiftUI
import Combine
-#warning("It sucks that we have to modify production code for the sake of tests, but it is absolutely necessary for ViewInspector and totally WORTH IT to get tests around SwiftUI that aren't XCUITests")
+// Necessary for ViewInspector tests
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
final class Inspection<V> where V: View {
let notice = PassthroughSubject<UInt, Never>()

diff:
@@ -46,17 +46,16 @@ import SwiftCurrent
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public struct WorkflowView: View {
@Binding public var isPresented: Bool
- let inspection = Inspection<Self>()
+ let inspection = Inspection<Self>() // Needed for ViewInspector
/// Creates a `WorkflowView` that displays a `FlowRepresentable` when presented.
public init(isPresented: Binding<Bool>) {
_isPresented = .constant(false)
}
- #warning("NOTE: The onReceive is how the tests work...do not remove it if you want tests that work. If you do not want tests that work why precisely are you at WWT or on this project?????")
public var body: some View {
VStack {
- }.onReceive(inspection.notice) { inspection.visit(self, $0) }
+ }.onReceive(inspection.notice) { inspection.visit(self, $0) } // Needed for ViewInspector
}
/// Adds an action to perform when this `Workflow` has finished.

diff:
@@ -83,7 +83,6 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase {
.thenProceed(with: WorkflowItem(FR6.self))
.thenProceed(with: WorkflowItem(FR7.self)))
.inspection.inspect { viewUnderTest in
- #warning("NOTE: These serve a dual purpose, if the view can't be cast to the appropriate FR# then it fails AND it proceeds in the workflow in 1 line")
XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow())

diff:
@@ -11,7 +11,7 @@ import SwiftUI
@testable import SwiftCurrent_SwiftUI
-#warning("Don't forget you need to make every view you want to test with ViewInspector Inspectable")
+// Don't forget you need to make every view you want to test with ViewInspector Inspectable
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension WorkflowView: Inspectable { }

diff:
@@ -9,7 +9,6 @@ import Foundation
import SwiftUI
import ViewInspector
-#warning("These are not part of the library, take a look here so you understand what it does")
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewHosting {
static func loadView<V: View>(_ view: V) -> V {
@@ -18,19 +17,4 @@ extension ViewHosting {
}
return view
}
-
- #warning("You may not need these, since you do not use environment objects")
- static func loadView<V: View, O: ObservableObject>(_ view: V, data: O) -> V {
- defer {
- Self.host(view: view.environmentObject(data))
- }
- return view
- }
-
- static func loadView<V: View, O: ObservableObject, E>(_ view: V, data: O, keyPath: WritableKeyPath<EnvironmentValues, E>, keyValue: E) -> V {
- defer {
- Self.host(view: view.environmentObject(data).environment(keyPath, keyValue))
- }
- return view
- }
}

original_message: [swiftui] - Fixes warnings - MOB


message: [swiftui] - Makes changes to SwiftCurrent core in order to support SwiftUI - MOB

diff:
@@ -29,14 +29,29 @@ public class FlowRepresentableMetadata {
- Parameter launchStyle: the style to use when launching the `FlowRepresentable`.
- Parameter flowPersistence: a closure passing arguments to the caller and returning the preferred `FlowPersistence`.
*/
- public init<FR: FlowRepresentable>(_ flowRepresentableType: FR.Type,
+ public convenience init<FR: FlowRepresentable>(_ flowRepresentableType: FR.Type,
launchStyle: LaunchStyle = .default,
flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) {
- flowRepresentableFactory = { args in
- AnyFlowRepresentable(FR.self, args: args)
+ self.init(flowRepresentableType,
+ launchStyle: launchStyle,
+ flowPersistence: flowPersistence) { args in
+ AnyFlowRepresentable(FR.self, args: args)}
}
- self.flowPersistence = flowPersistence
+
+ /**
+ Creates an instance that holds onto metadata associated with the `FlowRepresentable`.
+
+ - Parameter flowRepresentableType: specific type of the associated `FlowRepresentable`.
+ - Parameter launchStyle: the style to use when launching the `FlowRepresentable`.
+ - Parameter flowPersistence: a closure passing arguments to the caller and returning the preferred `FlowPersistence`.
+ */
+ public init<FR: FlowRepresentable>(_ flowRepresentableType: FR.Type,
+ launchStyle: LaunchStyle = .default,
+ flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence,
+ flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) {
self.launchStyle = launchStyle
+ self.flowPersistence = flowPersistence
+ self.flowRepresentableFactory = flowRepresentableFactory
}
func setPersistence(_ args: AnyWorkflow.PassedArgs) -> FlowPersistence {

diff:
import Foundation
/// A type erased `FlowRepresentable`.
-public class AnyFlowRepresentable {
+open class AnyFlowRepresentable {
typealias WorkflowInput = Any
typealias WorkflowOutput = Any

diff:
@@ -35,6 +35,34 @@ public class AnyWorkflow {
// swiftlint:disable:next missing_docs
public func _abandon() { storageBase._abandon() }
+
+ /// Appends `FlowRepresentableMetadata` to the `Workflow`.
+ public func append(_ metadata: FlowRepresentableMetadata) {
+ storageBase.append(metadata)
+ }
+
+ /**
+ Launches the `Workflow`.
+
+ ### Discussion
+ passedArgs are passed to the first instance, it has the opportunity to load, not load and transform them, or just not load.
+ In the event an instance does not load and does not transform args, they are passed unmodified to the next instance in the `Workflow` until one loads.
+
+ - Parameter orchestrationResponder: the `OrchestrationResponder` to notify when the `Workflow` proceeds or backs up.
+ - Parameter passedArgs: the arguments to pass to the first instance(s).
+ - Parameter launchStyle: the launch style to use.
+ - Parameter onFinish: the closure to call when the last element in the workflow proceeds; called with the `AnyWorkflow.PassedArgs` the workflow finished with.
+ - Returns: the first loaded instance or nil, if none was loaded.
+ */
+ @discardableResult public func launch(withOrchestrationResponder orchestrationResponder: OrchestrationResponder,
+ passedArgs: AnyWorkflow.PassedArgs,
+ launchStyle: LaunchStyle = .default,
+ onFinish: ((AnyWorkflow.PassedArgs) -> Void)? = nil) -> AnyWorkflow.Element? {
+ storageBase.launch(withOrchestrationResponder: orchestrationResponder,
+ passedArgs: passedArgs,
+ launchStyle: launchStyle,
+ onFinish: onFinish)
+ }
}
extension AnyWorkflow: Sequence {
@@ -88,6 +116,21 @@ fileprivate class AnyWorkflowStorageBase {
func last(where _: (LinkedList<_WorkflowItem>.Element) throws -> Bool) rethrows -> LinkedList<_WorkflowItem>.Element? {
fatalError("last(where:) not overridden by AnyWorkflowStorage")
}
+
+ // https://github.com/wwt/SwiftCurrent/blob/main/.github/STYLEGUIDE.md#type-erasure
+ // swiftlint:disable:next unavailable_function
+ func append(_ metadata: FlowRepresentableMetadata) {
+ fatalError("append(:) not overriden")
+ }
+
+ // https://github.com/wwt/SwiftCurrent/blob/main/.github/STYLEGUIDE.md#type-erasure
+ // swiftlint:disable:next unavailable_function
+ @discardableResult public func launch(withOrchestrationResponder orchestrationResponder: OrchestrationResponder,
+ passedArgs: AnyWorkflow.PassedArgs,
+ launchStyle: LaunchStyle = .default,
+ onFinish: ((AnyWorkflow.PassedArgs) -> Void)? = nil) -> AnyWorkflow.Element? {
+ fatalError("launch(orchestrationResponder:passedArgs:launchStyle:onFinish) not overriden")
+ }
}
fileprivate final class AnyWorkflowStorage<F: FlowRepresentable>: AnyWorkflowStorageBase {
@@ -119,4 +162,18 @@ fileprivate final class AnyWorkflowStorage<F: FlowRepresentable>: AnyWorkflowSto
override func last(where predicate: (LinkedList<_WorkflowItem>.Element) throws -> Bool) rethrows -> LinkedList<_WorkflowItem>.Element? {
try workflow.last(where: predicate)
}
+
+ override func append(_ metadata: FlowRepresentableMetadata) {
+ workflow.append(metadata)
+ }
+
+ override func launch(withOrchestrationResponder orchestrationResponder: OrchestrationResponder,
+ passedArgs: AnyWorkflow.PassedArgs,
+ launchStyle: LaunchStyle = .default,
+ onFinish: ((AnyWorkflow.PassedArgs) -> Void)? = nil) -> AnyWorkflow.Element? {
+ workflow.launch(withOrchestrationResponder: orchestrationResponder,
+ passedArgs: passedArgs,
+ launchStyle: launchStyle,
+ onFinish: onFinish)
+ }
}

diff:
ReferencedContainer = "container:SwiftCurrentExample/SwiftCurrentExample.xcodeproj">
</BuildableReference>
</TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "SwiftCurrent_SwiftUITests"
+ BuildableName = "SwiftCurrent_SwiftUITests"
+ BlueprintName = "SwiftCurrent_SwiftUITests"
+ ReferencedContainer = "container:">
+ </BuildableReference>
+ </TestableReference>
</Testables>
</TestAction>
<LaunchAction


message: [passthrough-representable] - Bump minor version number - MW

diff:
Pod::Spec.new do |s|
s.name = 'SwiftCurrent'
- s.version = '4.1.5'
+ s.version = '4.2.0'
s.summary = 'A library for complex workflows in Swift'
s.description = <<-DESC
SwiftCurrent is a library that lets you easily manage journeys through your Swift application.


message: [testing-platforms ] - Includes macOS, tvOS, and watchOS as platforms - MW

diff:
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "SwiftCurrent",
- platforms: [.iOS(.v11)],
+ platforms: [.iOS(.v11), .macOS(.v11), .tvOS(.v14), .watchOS(.v7)],
products: [
.library(
name: "SwiftCurrent",


message: [nav-101] - Updates code snippets in Getting-Started-SwiftUI to reflect the updated DSL - MW

diff:
@@ -113,13 +113,12 @@ struct ContentView: View {
var body: some View {
if !workflowIsPresented {
Button("Present") { workflowIsPresented = true }
- }
- WorkflowLauncher(isLaunched: $workflowIsPresented, startingArgs: "SwiftCurrent") // SwiftCurrent
- .thenProceed(with: WorkflowItem(FirstView.self) // SwiftCurrent
- .applyModifiers { firstView in firstView.padding().border(Color.gray) }
- .thenProceed(with: WorkflowItem(SecondView.self) // SwiftCurrent
- .applyModifiers { $0.padding().border(Color.gray) }))
- .onFinish { passedArgs in // SwiftCurrent
+ } else {
+ WorkflowLauncher(isLaunched: $workflowIsPresented, startingArgs: "SwiftCurrent") { // SwiftCurrent
+ thenProceed(with: FirstView.self) { // SwiftCurrent
+ thenProceed(with: SecondView.self).applyModifiers { $0.padding().border(Color.gray) } // SwiftCurrent
+ }.applyModifiers { $0.firstView.padding().border(Color.gray) }
+ }.onFinish { passedArgs in // SwiftCurrent
workflowIsPresented = false
guard case .args(let emailAddress as String) = passedArgs else {
print("No email address supplied")
@@ -129,6 +128,7 @@ struct ContentView: View {
}
}
}
+}
struct Content_Previews: PreviewProvider {
static var previews: some View {
@@ -196,7 +196,9 @@ final class FirstViewController: UIWorkflowItem<Never, Never>, FlowRepresentable
Now in SwiftUI simply reference that controller.
```swift
-WorkflowLauncher(isLaunched: $workflowIsPresented) // SwiftCurrent
- .thenProceed(with: WorkflowItem(FirstViewController.self) // SwiftCurrent
- .thenProceed(with: WorkflowItem(SecondView.self))) // SwiftCurrent
+WorkflowLauncher(isLaunched: $workflowIsPresented) { // SwiftCurrent
+ thenProceed(with: FirstViewController.self) { // SwiftCurrent
+ thenProceed(with: SecondView.self) // SwiftCurrent
+ }
+}
```
\ No newline at end of file


message: [skip ci] - Updates SiwftCurrent_SwiftUI snippet in the README - MW

diff:
@@ -110,8 +110,9 @@ Then from your ContentView body, add:
```swift
import SwiftCurrent_SwiftUI
...
-WorkflowLauncher(isLaunched: .constant(true), startingArgs: "Launched")
- .thenProceed(with: WorkflowItem(ExampleView.self))
+WorkflowLauncher(isLaunched: .constant(true), startingArgs: "Launched") {
+ thenProceed(with: ExampleView.self)
+}
```
And just like that you're started!


message: Proofread and made minimal changes.

diff:
SwiftCurrent allows you to control how your workflow presents its `FlowRepresentable`s. This control over presentation is different for UIKit and SwiftUI. You can also control `FlowPersistence`, which describes what should happen to an item in a `Workflow` if it's been skipped or once the workflow has proceeded.
### UIKit
-In UIKit, you control presentation using `LaunchStyle.PresentationType`. The default is a contextual presentation mode. If it detects you are in a navigation view, it'll present by pushing onto the navigation stack. If it cannot detect a navigation view, it presents modally. Alternatively, you can explicitly state you'd like it to present modally or in a navigation stack when you define your `Workflow`.
+In UIKit, you control presentation with `LaunchStyle.PresentationType`. The default is a contextual presentation mode. If it detects you are in a navigation view, it'll present by pushing onto the navigation stack. If it cannot detect a navigation view, it presents modally. Alternatively, you can explicitly state you'd like it to present modally or in a navigation stack when you define your `Workflow`.
### In SwiftUI
-In SwiftUI you control presentation using `LaunchStyle.SwiftUI.PresentationType`. The default is simple view replacement. This is especially powerful because your workflows in SwiftUI do not need to be an entire screen; they can be just part of a view. Using the default presentation type, you can also get fine-grained control over animations. You can also explicitly state you'd like it to present modally (using a sheet or fullScreenCover) or in a navigation stack when you define your `WorkflowLauncher`.
+In SwiftUI, you control presentation using `LaunchStyle.SwiftUI.PresentationType`. The default is simple view replacement. This is especially powerful because your workflows in SwiftUI do not need to be an entire screen; they can be just part of a view. Using the default presentation type, you can also get fine-grained control over animations. You can also explicitly state you'd like it to present modally (using a sheet or fullScreenCover) or in a navigation stack when you define your `WorkflowLauncher`.
### Persistence
You can control what happens to items in your workflow using `FlowPersistence`. Using `FlowPersistence.persistWhenSkipped` means that when `FlowRepresentable.shouldLoad` returns false, the item is still stored on the workflow. If, for example, you're in a navigation stack, this means the item *is* skipped, but you can back up to it.

original_message: Proofread and made minimal changes.


message:
Update Creating Workflows in SwiftUI.md
Proofread and made minimal changes.

old_path: .github/abstract/Creating Workflows in SwiftUI.md

diff:
### Step 1:
-To create workflows in SwiftUI you should start with a view that should be part of a `Workflow` and modify it to be `FlowRepresentable`
+To create workflows in SwiftUI, start with a view that should be part of a `Workflow` and modify it to be `FlowRepresentable`.
#### Example:
```swift
@@ -17,12 +17,12 @@ struct FirstView: View, FlowRepresentable {
}
```
-> Note: The `_workflowPointer` is needed as an anchor point for your `Workflow`. You do not have to worry about setting it, you merely need space for it on your structs. SwiftUI actually does the exact same thing with a `_location` variable, it's just that Apple has secret compiler magic to hide that. Unfortunately, that compiler magic is not shared.
+> **Note:** The `_workflowPointer` is needed as an anchor point for your `Workflow`. You do not have to worry about setting it, you merely need space for it on your structs. SwiftUI actually does the exact same thing with a `_location` variable, it's just that Apple has secret compiler magic to hide that. Unfortunately, that compiler magic is not shared.
-> Note: `FlowRepresentable.proceedInWorkflow()` is what you call to have your view move forward to the next item in the `Workflow` it is part of.
+> **Note:** `FlowRepresentable.proceedInWorkflow()` is what you call to have your view move forward to the next item in the `Workflow` it is part of.
### Step 2:
-Define your `WorkflowLauncher`. This indicates whether the workflow is shown and describes what items are in it.
+Define your `WorkflowLauncher`. This indicates if the workflow is shown and describes what items are in it.
#### Example:
```swift


message:
Update Creating Workflows in UIKit.md
Proofread and made minor changes.

old_path: .github/abstract/Creating Workflows in UIKit.md

diff:
-### Step 1:
-To create workflows in UIKit you should start with a `UIViewController` that should be part of a `Workflow` and modify it to be `FlowRepresentable`
+### Step 1
+To create workflows in UIKit, start with a `UIViewController` that should be part of a `Workflow`, then modify it to be `FlowRepresentable`.
-#### Example:
+#### Example
```swift
import UIKit
import SwiftCurrent
@@ -26,12 +26,12 @@ class FirstViewController: UIWorkflowItem<String, String>, FlowRepresentable { /
}
```
-> Note: `FlowRepresentable.proceedInWorkflow()` is what you call to have your view move forward to the next item in the `Workflow` it is part of.
+> **Note:** Call `FlowRepresentable.proceedInWorkflow()` to have your view move forward to the next item in the `Workflow` it is part of.
-### Step 2:
-Define your `Workflow` and launch it. This is what allows you to configure or re-order your workflow.
+### Step 2
+Define your `Workflow` and launch it. This is what allows you to configure or reorder your workflow.
-#### Example:
+#### Example
```swift
// From the ViewController you'd like to launch the workflow
@objc private func didTapLaunchWorkflow() {


message:
Update Creating Workflows.md
Proofread and made a few changes. Note that when compile time and run time are nouns, no hyphens. When they are adjectives, they are hyphenated.

old_path: .github/abstract/Creating Workflows.md

new_path: .github/abstract/Creating Workflows.md

diff:
-### What is a workflow?
-A `Workflow` is a description for a sequence of `FlowRepresentable`s. Those operations are normally displaying screens or views. There are different ways of [creating workflows in SwiftUI](Creating%20Workflows%20in%20SwiftUI.html) and [creating workflows in UIKit](Creating%20Workflows%20in%20UIKit.html).
+### What is a Workflow?
+A `Workflow` is a description for a sequence of `FlowRepresentable`s. Those operations normally display screens or views. There are different ways of [creating workflows in SwiftUI](Creating%20Workflows%20in%20SwiftUI.html) and [creating workflows in UIKit](Creating%20Workflows%20in%20UIKit.html).
-### What is a `FlowRepresentable`?
-A `FlowRepresentable` is a protocol that describes a type that can appear in a `Workflow`. It optionally declares `FlowRepresentable.WorkflowInput` and `FlowRepresentable.WorkflowOutput`, indicating whether it requires data or passes data forward. It also provides an initializer so that it can be created by the `Workflow`. Once an item conforms to `FlowRepresentable`, it can be injected into any `Workflow`.
+### What Is a `FlowRepresentable`?
+A `FlowRepresentable` is a protocol that describes a type that can appear in a `Workflow`. It optionally declares `FlowRepresentable.WorkflowInput` and `FlowRepresentable.WorkflowOutput`, indicating if it requires data or passes data forward. It also provides an initializer so that it can be created by the `Workflow`. Once an item conforms to `FlowRepresentable`, it can be injected into any `Workflow`.
-### Valid workflow sequences
-Workflows enforce (either at compile-time or run-time) that the sequence of `FlowRepresentables` is well-formed. For example, if a particular `FlowRepresentable` has a `FlowRepresentable.WorkflowInput` of `String`, but the previous item passes an `Int` forward, that workflow is malformed. A `FlowRepresentable` can declare a `FlowRepresentable.WorkflowInput` of `Never` if it does not take in data, `Any` if it can intake multiple types, or `AnyWorkflow.PassedArgs` if it can handle being passed no data, or data of any kind. Lastly, if your `FlowRepresentable.WorkflowInput` needs to take in multiple values, you can use a tuple.
+### Valid Workflow Sequences
+Workflows enforce (either at compile time or run time) that the sequence of `FlowRepresentables` is well-formed. For example, if a particular `FlowRepresentable` has a `FlowRepresentable.WorkflowInput` of `String`, but the previous item passes an `Int` forward, that workflow is malformed. A `FlowRepresentable` can declare a `FlowRepresentable.WorkflowInput` of `Never` if it does not take in data, `Any` if it can intake multiple types, or `AnyWorkflow.PassedArgs` if it can handle being passed no data, or data of any kind. Lastly, if your `FlowRepresentable.WorkflowInput` needs to take in multiple values, you can use a tuple.
-In some cases, like UIKit, the compiler is efficient enough to give you compile-time feedback on whether a workflow is malformed. This means that runtime errors are rare. They can still occur, for example if you have Item1 declare a `FlowRepresentable.WorkflowOutput` of `AnyWorkflow.PassedArgs`, then call `FlowRepresentable.proceedInWorkflow(_:)` with `.args("string")`, but Item2 has a `FlowRepresentable.WorkflowInput` of `Int` there'll be a runtime error because the data passed forward does not meet expectations.
+In some cases, like UIKit, the compiler is efficient enough to give you compile-time feedback if a workflow is malformed. This means that run-time errors are rare. They can still occur; for example, if you have Item1 declare a `FlowRepresentable.WorkflowOutput` of `AnyWorkflow.PassedArgs`, then call `FlowRepresentable.proceedInWorkflow(_:)` with `.args("string")`, but Item2 has a `FlowRepresentable.WorkflowInput` of `Int`, there'll be a run-time error because the data passed forward does not meet expectations.
-In SwiftUI, the compiler was not efficient enough to give the same compile-time feedback on malformed workflows. When that safety was added, the compiler only allowed for small workflows to be created. To combat this, SwiftUI is heavily run-time influenced. When you create a `WorkflowLauncher`, the launcher performs a runtime check to guarantee the workflow is well-formed. This means that if you wanted to test your workflow was well-formed, all you have to do is instantiate a `WorkflowLauncher`.
\ No newline at end of file
+In SwiftUI, the compiler was not efficient enough to give the same compile-time feedback on malformed workflows. When that safety was added, the compiler only allowed for small workflows to be created. To combat this, SwiftUI is heavily run time-influenced. When you create a `WorkflowLauncher`, the launcher performs a run time check to guarantee the workflow is well-formed. This means that if you wanted to test your workflow was well-formed, all you have to do is instantiate a `WorkflowLauncher`.


message:
Update How to use SwiftCurrent with UIKit.md
Proofread and made 3 changes.

old_path: .github/abstract/How to use SwiftCurrent with UIKit.md

diff:
-SwiftCurrent works well with UIKit. To work correctly it does require `FlowRepresentable`s to be `UIViewController`s and not `UIView`s. It comes with [support for Storyboards](using-storyboards.html) with `StoryboardLoadable` and [programmatic views](using-programmatic-views.html).
\ No newline at end of file
+SwiftCurrent works well with UIKit. To work correctly, requires `FlowRepresentable`s to be `UIViewController`s and not `UIView`s. It comes with [support for Storyboards](using-storyboards.html) with `StoryboardLoadable` and [programmatic views](using-programmatic-views.html).


message:
Update Type Erasure.md
Proofread and made a couple of changes.

old_path: .github/abstract/Type Erasure.md

new_path: .github/abstract/Type Erasure.md

diff:
Sometimes it may be desirable to explicitly remove type information. Often this is when you want to pass around types with complicated generic signatures. SwiftCurrent, much like many of Apple's standard libraries, ships with several type erasers for your convenience.
### `AnyWorkflow.PassedArgs`
-The type `AnyWorkflow.PassedArgs` is worth calling out separately. It's very similar to Swift's standard `Optional` type, but with a crucial difference. Consumers of SwiftCurrent need to be able to clearly differentiate between `nil` being passed in a `Workflow` that proceeded and no arguments at all being passed. So if a `FlowRepresentable.WorkflowInput` is `Any?` that means it can accept any data, even if that data is optional, but data *must* be passed to it. If a `FlowRepresentable.WorkflowInput` is `AnyWorkflow.PassedArgs`, it means that it can take any data, including nil data, OR it can take no data at all.
\ No newline at end of file
+The type `AnyWorkflow.PassedArgs` is worth calling out separately. It's very similar to Swift's standard `Optional` type, but with a crucial difference. Consumers of SwiftCurrent need to be able to clearly differentiate between `nil` being passed in a `Workflow` that proceeded and no arguments being passed. So if a `FlowRepresentable.WorkflowInput` is `Any?`, that means it can accept any data, even if that data is optional, but data *must* be passed to it. If a `FlowRepresentable.WorkflowInput` is `AnyWorkflow.PassedArgs`, it means that it can take any data, including nil data, OR it can take no data at all.


message:
Update Getting Started with SwiftUI.md
Proofread and made changes. Please spell out SPM, then put the acronym in parentheses.
General note: In all titles, capitalize "With." All prepositions that are four or more letters are capped in titles.

old_path: .github/guides/Getting Started with SwiftUI.md

diff:
@@ -6,15 +6,15 @@ The app in this guide is going to be very simple. It consists of a view that wi
![Preview image of app](https://user-images.githubusercontent.com/79471462/131556533-f2ad1e6c-9acd-4d62-94ac-9140c9718f95.gif)
-## Adding the dependency
+## Adding the Dependency
-For instructions using Swift Package Manager (SPM) and CocoaPods, [check out our installation page.](installation.html#swift-package-manager) This guide assumes you used SPM.
+For instructions using Swift Package Manager (SPM) and CocoaPods, [check out our installation page.](installation.html#swift-package-manager) This guide assumes you use SPM.
## IMPORTANT NOTE
-SwiftCurrent is so convenient that you may miss the couple lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library.
+SwiftCurrent is so convenient that you may miss the couple of lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library.
-## Create your views
+## Create Your Views
Create two views that implement `FlowRepresentable`.
@@ -86,7 +86,7 @@ struct SecondView_Previews: PreviewProvider {
}
```
-### Let's talk about what is going on with these views
+### What Is Going on With These Views?
#### **Why is `_workflowPointer` weak?**
@@ -146,7 +146,7 @@ struct Content_Previews: PreviewProvider {
}
```
-### Let's discuss what's going on here
+### What's Going on Here?
#### **Wait, where is the `Workflow`?**
@@ -155,11 +155,11 @@ struct Content_Previews: PreviewProvider {
In SwiftUI, the Workflow type is handled by the library when you start with a WorkflowLauncher.

-#### **Where is the type safety, I heard about?**
+#### **Where is the type safety I heard about?**

-WorkflowLauncher is specialized with your startingArgs type. FlowRepresentable is specialized with the FlowRepresentable.WorkflowInput and FlowRepresentable.WorkflowOutput associated types. These all work together when creating your flow at run-time to ensure the validity of your Workflow. If the output of FirstView does not match the input of SecondView, the library will send out an error when creating the Workflow.
+WorkflowLauncher is specialized with your startingArgs type. FlowRepresentable is specialized with the FlowRepresentable.WorkflowInput and FlowRepresentable.WorkflowOutput associated types. These all work together when creating your flow at run time to ensure the validity of your Workflow. If the output of FirstView does not match the input of SecondView, the library will send an error when creating the Workflow.

#### **What's going on with this `startingArgs` and `passedArgs`?**
@@ -168,7 +168,7 @@ In SwiftUI, the Workflow type is handled by the library when you st
startingArgs are the AnyWorkflow.PassedArgs handed to the first FlowRepresentable in the workflow. These arguments are used to pass data and determine if the view should load.
-passedArgs are the AnyWorkflow.PassedArgs coming from the last view in the workflow. onFinish is only called when the user has gone through all the screens in the Workflow by navigation or skipping. For this workflow, passedArgs is going to be the output of FirstView or SecondView depending on the email signature typed in FirstView. To extract the value, we unwrap the variable within the case of .args() as we expect this workflow to return some argument.
+passedArgs are the AnyWorkflow.PassedArgs coming from the last view in the workflow. onFinish is only called when the user has gone through all the screens in the Workflow by navigation or skipping. For this workflow, passedArgs is going to be the output of FirstView or SecondView, depending on the email signature typed in FirstView. To extract the value, we unwrap the variable within the case of .args() as we expect this workflow to return some argument.

## Interoperability With UIKit


message:
Update Installation.md
Proofread and made updates.

diff:
@@ -4,17 +4,17 @@ If you want more information about installing Swift packages through Xcode, [fol
If you're a framework author and want to use SwiftCurrent as a dependency, these changes will be in your `Package.swift` file.
-## Get the package
+## Get the Package
-Add the following line to the package dependencies
+Add the following line to the package dependencies:
```swift
.package(url: "https://github.com/wwt/SwiftCurrent.git", .upToNextMajor(from: "4.5.0")),
```
-## Get the products
+## Get the Products
-Add the following products to your target dependencies.
+Add the following products to your target dependencies:
```swift
.product(name: "SwiftCurrent", package: "SwiftCurrent"),
@@ -22,7 +22,7 @@ Add the following products to your target dependencies.
.product(name: "SwiftCurrent_SwiftUI", package: "SwiftCurrent")
```
-### Your import statements for these products will be
+### Your Import Statements for These Products
```swift
import SwiftCurrent
@@ -38,18 +38,18 @@ import SwiftCurrent_SwiftUI
Set up [CocoaPods](https://cocoapods.org/) for your project, then include SwiftCurrent in your dependencies by adding the following line to your `Podfile`:
-### To use SwiftCurrent with UIKit
+### To Use SwiftCurrent With UIKit
```ruby
pod 'SwiftCurrent/UIKit'
```
-### To use SwiftCurrent with SwiftUI
+### To Use SwiftCurrent With SwiftUI
```ruby
pod 'SwiftCurrent/SwiftUI'
```
-### Your import statement will be
+### Your Import Statement
```swift
import SwiftCurrent


message:
Update Using Programmatic Views.md
Proofread and made changes.

diff:
@@ -6,7 +6,7 @@ The app in this guide is going to be very simple. It consists of a screen that
![Preview image of app](https://user-images.githubusercontent.com/79471462/131556322-56757c1d-e4ec-4581-a47c-969f536e3893.gif)
-## Adding the dependency
+## Adding the Dependency
For instructions on SPM and CocoaPods, [check out our installation page.](installation.html#swift-package-manager)
@@ -14,7 +14,7 @@ For instructions on SPM and CocoaPods, [check out our installation page.](instal
SwiftCurrent is so convenient that you may miss the couple lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library.
-## Create your view controllers
+## Create Your View Controllers
Create two view controllers that inherit from `UIWorkflowItem` and implement `FlowRepresentable`.
@@ -121,7 +121,7 @@ class SecondViewController: UIWorkflowItem<String, String>, FlowRepresentable {
}
```
-### Let's talk about what is going on with these view controllers
+### What Is Going on With These View Controllers?
#### **What's this `shouldLoad()`?**
@@ -171,9 +171,9 @@ class ViewController: UIViewController {
}
```
-### Let's discuss what's going on here
+### What Is Going on Here?
-#### **Where is the type safety, I heard about?**
+#### **Where is the type safety I heard about?**

@@ -196,12 +196,11 @@ Calling Workflow.abandon() closes all the views launched as part of
## Testing
-### Installing test dependencies
+### Installing Test Dependencies
-For our test example, we will be using a library called [UIUTest](https://github.com/nallick/UIUTest). It is optional for testing SwiftCurrent, but in order for the example to be copyable, you will need to add the UIUTest Swift Package
-to your test target.
+For our test example, we are using a library called [UIUTest](https://github.com/nallick/UIUTest). It is optional for testing SwiftCurrent, but in order for the example to be copyable, you will need to add the UIUTest Swift Package to your test target.
-### Creating tests
+### Creating Tests
```swift
import XCTest
@@ -251,26 +250,26 @@ class SecondViewControllerTests: XCTestCase {
While this team finds that testing our view controllers with [UIUTest](https://github.com/nallick/UIUTest) allows us to decrease the visibility of our properties and provide better coverage, [UIUTest](https://github.com/nallick/UIUTest) is not needed for testing SwiftCurrent. If you do not want to take the dependency, you will have to elevate visibility or find a way to invoke the `finishPressed` method.
-#### **What is going on with: `testSecondViewControllerDoesNotLoadWhenInputIsEmpty`?**
+#### **What is going on with `testSecondViewControllerDoesNotLoadWhenInputIsEmpty`?**
<details>
-This test is super simple. We create the view controller in a way that will go through the correct init, with expected arguments. Then we call <code>shouldLoad</code> to validate if the provided Input gets us the results we want.
+This test is super simple. We create the view controller in a way that will go through the correct init, with expected arguments. Then we call <code>shouldLoad</code> to validate if the provided input gets us the results we want.
</details>
-#### **What is going on with: `testProceedPassesThroughInput`?**
+#### **What is going on with `testProceedPassesThroughInput`?**
<details>
-At a high level we are loading the view controller for testing (similar to before but now with an added step of triggering lifecycle events). We update the <code>proceedInWorkflow</code> closure so that we can confirm it was called. Finally we invoke the method that will call proceed. The assert is verifying that the Output is the same as the input, as this view controller is passing it through.
+At a high level, we are loading the view controller for testing (similar to before but now with an added step of triggering lifecycle events). We update the <code>proceedInWorkflow</code> closure so that we can confirm it was called. Finally, we invoke the method that will call proceed. The assert is verifying that the output is the same as the input, as this view controller is passing it through.
</details>
#### **I added UIUTest, why isn't it hitting the finish button?**
<details>
-It's easy to forget to set the accessibility identifier on the button, please check that first. Second, if you don't call <code>loadForTesting()</code> your view controller doesn't make it to the window and the hit testing of <code>simulateTouch()</code> will also fail. Finally, make sure the button is visible and tappable on the simulator you are using.
+It's easy to forget to set the accessibility identifier on the button, please check that first. Second, if you don't call <code>loadForTesting()</code>, your view controller doesn't make it to the window and the hit testing of <code>simulateTouch()</code> will also fail. Finally, make sure the button is visible and tappable on the simulator you are using.
</details>
## Interoperability With SwiftUI
-You can use your SwiftUI `View`s that are `FlowRepresentable` in your UIKit workflows. Start with your `View`
+You can use your SwiftUI `View`s that are `FlowRepresentable` in your UIKit workflows. Start with your `View`.
```swift
import SwiftUI
@@ -286,7 +285,7 @@ struct SwiftUIView: View, FlowRepresentable { // SwiftCurrent
```
-Now in your UIKit workflow simply use a `HostedWorkflowItem`
+Now in your UIKit workflow, simply use a `HostedWorkflowItem`.
```swift
launchInto(Workflow(HostedWorkflowItem<SwiftUIView>.self)) // SwiftCurrent


message:
Update Using Storyboards.md
Proofread and updated.

old_path: .github/guides/Using Storyboards.md

new_path: .github/guides/Using Storyboards.md

diff:
@@ -6,17 +6,17 @@ The app in this guide is going to be very simple. It consists of a screen that
![Preview image of app](https://user-images.githubusercontent.com/79471462/131556008-943f5e00-b7d0-4782-974d-1e914c4179fc.gif)
-## Adding the dependency
+## Adding the Dependency
-For instructions on SPM and CocoaPods, [check out our installation page.](installation.html#swift-package-manager)
+For instructions on SPM and CocoaPods, [check out our installation page.](installation.html#swift-package-manager).
## IMPORTANT NOTE
-SwiftCurrent is so convenient that you may miss the couple lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library.
+SwiftCurrent is so convenient that you may miss the couple of lines that are calls to the library. To make it easier, we've marked our code snippets with `// SwiftCurrent` to highlight items that are coming from the library.
-## Create the convenience protocols for storyboard loading
+## Create the Convenience Protocols for Storyboard Loading
-It is best practice to use the `StoryboardLoadable` protocol to connect your `FlowRepresentable` to your Storyboard. Additionally, to limit the amount of duplicate code, you can make a convenience protocol for each storyboard.
+It is best practice to use the `StoryboardLoadable` protocol to connect your `FlowRepresentable` to your storyboard. Additionally, to limit the amount of duplicate code, you can make a convenience protocol for each storyboard.
```swift
import UIKit
@@ -33,9 +33,9 @@ extension MainStoryboardLoadable {
}
```
-> NOTE: `StoryboardLoadable` is only available in iOS 13.0 and later.
+> **NOTE:** `StoryboardLoadable` is only available in iOS 13.0 and later.
-## Create your view controllers
+## Create Your View Controllers
Create two view controllers that both conform to `MainStoryboardLoadable` and inherit from `UIWorkflowItem`.
@@ -94,7 +94,7 @@ class SecondViewController: UIWorkflowItem<String, String>, MainStoryboardLoadab
}
```
-### Let's talk about what is going on with these view controllers
+### What Is Going on With These View Controllers?
#### **Where are the `FlowRepresentable`s you mentioned earlier?**
@@ -107,9 +107,9 @@ You could declare these view controllers with <code>class FirstViewController: U
<details>
-These view controllers adhere to <code>FlowRepresentable</code> by the combination of <code>UIWorkflowItem</code> and <code>StoryboardLoadable</code>.
+These view controllers adhere to <code>FlowRepresentable</code> by the combination of <code>UIWorkflowItem</code> and <code>StoryboardLoadable</code>
<ol>
-<li> The <code>UIWorkflowItem</code> class implements a subset of the requirements for <code>FlowRepresentable</code>.</li>
+<li> The <code>UIWorkflowItem</code> class implements a subset of the requirements for <code>FlowRepresentable</code>.</li>.
<li> <code>StoryboardLoadable</code> implements the remaining subset and requires that it is only applied to a <code>FlowRepresentable</code>.</li>
</ol>
@@ -155,20 +155,20 @@ class ViewController: UIViewController {
}
```
-### Let's discuss what's going on here
+### **What Is Going on Here?**
-#### **Where is the type safety, I heard about?**
+#### **Where is the type safety I heard about?**

-The Workflow has compile-time type safety on the Input/Output types of the supplied FlowRepresentables. This means that you will get a build error if the output of FirstViewController does not match the input type of SecondViewController.
+The Workflow has compile-time type safety on the input/output types of the supplied FlowRepresentables. This means that you will get a build error if the output of FirstViewController does not match the input type of SecondViewController.

#### **What's going on with this `passedArgs`?**

-The onFinish closure for UIViewController.launchInto(_:args:withLaunchStyle:onFinish:) provides the last passed AnyWorkflow.PassedArgs in the work flow. For this Workflow, that could be the output of FirstViewController or SecondViewController depending on the email signature typed in FirstViewController. To extract the value, we unwrap the variable within the case of .args() as we expect this workflow to return some argument.
+The onFinish closure for UIViewController.launchInto(_:args:withLaunchStyle:onFinish:) provides the last passed AnyWorkflow.PassedArgs in the workflow. For this workflow, that could be the output of FirstViewController or SecondViewController depending on the email signature typed in FirstViewController. To extract the value, we unwrap the variable within the case of .args() as we expect this workflow to return some argument.

#### **Why call `abandon()`?**
@@ -180,12 +180,11 @@ Calling Workflow.abandon() closes all the views launched as part of
## Testing
-### Installing test dependencies
+### Installing Test Dependencies
-For our test example, we will be using a library called [UIUTest](https://github.com/nallick/UIUTest). It is optional for testing SwiftCurrent, but in order for the example to be copyable, you will need to add the UIUTest Swift Package
-to your test target.
+For our test example, we are using a library called [UIUTest](https://github.com/nallick/UIUTest). It is optional for testing SwiftCurrent, but in order for the example to be copyable, you will need to add the UIUTest Swift Package to your test target.
-### Creating tests
+### Creating Tests
```swift
import XCTest
@@ -236,26 +235,26 @@ class SecondViewControllerTests: XCTestCase {
While this team finds that testing our view controllers with [UIUTest](https://github.com/nallick/UIUTest) allows us to decrease the visibility of our properties and provide better coverage, [UIUTest](https://github.com/nallick/UIUTest) is not needed for testing SwiftCurrent. If you do not want to take the dependency, you will have to elevate visibility or find a way to invoke the `finishPressed` method.
-#### **What is going on with: `testSecondViewControllerDoesNotLoadWhenInputIsEmpty`?**
+#### **What is going on with `testSecondViewControllerDoesNotLoadWhenInputIsEmpty`?**
<details>
-This test is super simple. We create the view controller in a way that will go through the correct init, with expected arguments. Then we call <code>shouldLoad</code> to validate if the provided Input gets us the results we want.
+This test is super simple. We create the view controller in a way that will go through the correct init, with expected arguments. Then we call <code>shouldLoad</code> to validate if the provided input gets us the results we want.
</details>
-#### **What is going on with: `testProceedPassesThroughInput`?**
+#### **What is going on with `testProceedPassesThroughInput`?**
<details>
-At a high level we are loading the view controller for testing (similar to before but now with an added step of triggering lifecycle events). We update the <code>proceedInWorkflow</code> closure so that we can confirm it was called. Finally we invoke the method that will call proceed. The assert is verifying that the Output is the same as the input, as this view controller is passing it through.
+At a high level, we are loading the view controller for testing (similar to before but now with an added step of triggering lifecycle events). We update the <code>proceedInWorkflow</code> closure so that we can confirm it was called. Finally, we invoke the method that will call proceed. The assert is verifying that the output is the same as the input, as this view controller is passing it through.
</details>
#### **I added UIUTest, why isn't it hitting the finish button?**
<details>
-It's easy to forget to set the accessibility identifier on the button, please check that first. Second, if you don't call <code>loadForTesting()</code> your view controller doesn't make it to the window and the hit testing of <code>simulateTouch()</code> will also fail. Finally, make sure the button is visible and tappable on the simulator you are using.
+It's easy to forget to set the accessibility identifier on the button, please check that first. Second, if you don't call <code>loadForTesting()</code>, your view controller doesn't make it to the window and the hit testing of <code>simulateTouch()</code> will also fail. Finally, make sure the button is visible and tappable on the simulator you are using.
</details>
## Interoperability With SwiftUI
-You can use your SwiftUI `View`s that are `FlowRepresentable` in your UIKit workflows. Start with your `View`
+You can use your SwiftUI `View`s that are `FlowRepresentable` in your UIKit workflows. Start with your `View`.
```swift
import SwiftUI
@@ -271,7 +270,7 @@ struct SwiftUIView: View, FlowRepresentable { // SwiftCurrent
```
-Now in your UIKit workflow simply use a `HostedWorkflowItem`
+Now in your UIKit workflow, simply use a `HostedWorkflowItem`.
```swift
launchInto(Workflow(HostedWorkflowItem<SwiftUIView>.self)) // SwiftCurrent


message: Update Why This Library.md

old_path: .github/guides/Why This Library.md

new_path: .github/guides/Why This Library.md

diff:
There are many reasons to be excited about SwiftCurrent:
- Build flexibility into your applications so that you can easily make changes when you need to.
- Compose new `Workflow`s to quickly create a new journey through your app.
-- Stop components in your app from knowing more than they should, no more complicated routers or views that have to know what comes next.
-- Create flows that give your users a better experience, web frameworks can easily sign somebody in, or sign them up, then continue into a flow, so why not be able to easily do the same in Swift?
+- Stop components in your app from knowing more than they should; no more complicated routers or views that have to know what comes next.
+- Create flows that give your users a better experience. Web frameworks can easily sign somebody in, or sign them up, then continue into a flow. Why not be able to easily do the same in Swift?
- Power deep linking experiences that can get your users right where you want them with minimal overhead.
-Read on to learn about our philosophy of development. We believe these ideals take us from "just another library" to a tool you won't want to do without.
+Read on to learn about our philosophy of development. We believe these ideals take us from "just another library" to a tool you can't do without.
-### Developer Experience over Problem Solving
-There have been many attempts by others to create libraries that decouple the concept of a flow that can move forward and backwards, but they all took very different approaches. What separates us, and makes this library worth using, is our developer-centric experience. We sat down with a group of developers and asked the question "if something existed today that did what you wanted, what would that thing look like?" then we spent a lot of effort making that happen.
+### Developer Experience Over Problem Solving
+There have been many attempts by others to create libraries that decouple the concept of a flow that can move forward and backward, but they all took very different approaches. What separates us, and makes this library worth using, is our developer-centric experience. We sat down with a group of developers and asked the question, "If something existed today that did what you wanted, what would that thing look like?" then we spent a lot of effort making that happen.
-Additionally, this is a concerted effort from WWT. We do a lot of software development for large companies and this problem space is something we've regularly encountered. The concept of a Workflow has been toyed with by several projects. This library has been used to power some really great experiences already, and we intend to continue using it to do even bigger and better things in the future.
+Additionally, this is a concerted effort from WWT. We do a lot of software development for large companies and this problem space is something we've regularly encountered. The concept of a workflow has been toyed with by several projects. This library has been used to power some really great experiences already, and we intend to continue using it to do even bigger and better things in the future.
-### Flexibility over Assumptions
+### Flexibility Over Assumptions
Not everyone develops software the same way, and it would be a sad day for the industry if we thought we had all the answers. We believe strongly in a culture of innovation, and we want consumers of our library to be able to make choices they feel are correct. Workflows are not an architectural pattern; they're an abstraction you can choose to use. Much like the standard libraries that come with programming languages, the idea here is to give you tools that can be useful and let you choose the best way to use them.
-### Explicit Choices over Fast Releases
-It's not always visible, but we spend far more time talking about the right approach than we do writing code. The core team developing this library has many deliberate, considered choices to make. We would rather deliver a high-quality, well-tested, thought-through library than deliver many features quickly. We believe that this library can one day become a staple for iOS projects and are approaching it with care and dedication right from the start. If you've got an idea for how to make SwiftCurrent better make sure to [tell us!](https://github.com/wwt/SwiftCurrent/discussions), we love feedback and value community ideas.
+### Explicit Choices Over Fast Releases
+It's not always visible, but we spend far more time talking about the right approach than we do writing code. The core team developing this library has many deliberate, considered choices to make. We would rather deliver a high-quality, well-tested, thought-through library than deliver many features quickly. We believe that this library can one day become a staple for iOS projects and are approaching it with care and dedication right from the start. If you've got an idea for how to make SwiftCurrent better, make sure to [tell us!](https://github.com/wwt/SwiftCurrent/discussions), we love feedback and value community ideas.

original_message: Update Why This Library.md


message:
Update Working with Modals.md
Proofread and made changes.

old_path: .github/guides/Working with Modals.md

new_path: .github/guides/Working with Modals.md

diff:
### Presentation Types
-When you are constructing a workflow you can use `WorkflowItem.presentationType(_:)` along with a valid `LaunchStyle.SwiftUI.PresentationType` to control how a `FlowRepresentable` will be displayed. This is how you'll describe your modals and keep your view ignorant of the context it's displayed in.
+When constructing a workflow, you can use `WorkflowItem.presentationType(_:)` along with a valid `LaunchStyle.SwiftUI.PresentationType` to control how a `FlowRepresentable` will be displayed. This is how you'll describe your modals and keep your view ignorant of the context it's displayed in.
-#### Example:
+#### Example
```swift
NavigationView {
WorkflowLauncher(isLaunched: .constant(true)) {
@@ -12,15 +12,15 @@ NavigationView {
}
```
-With that you've described that `FirstView` should be presented normally. When it calls `FlowRepresentable.proceedInWorkflow()` it'll present `SecondView` using the default SwiftUI modal (sheet).
+With that, you've described that `FirstView` should be presented normally. When it calls `FlowRepresentable.proceedInWorkflow()`, it'll present `SecondView` using the default SwiftUI modal (sheet).
-> NOTE: Unlike `LaunchStyle.SwiftUI.PresentationType.navigationLink` you apply `LaunchStyle.SwiftUI.PresentationType.modal` to a view that should be launched modally.
+> **NOTE:** Unlike `LaunchStyle.SwiftUI.PresentationType.navigationLink`, you apply `LaunchStyle.SwiftUI.PresentationType.modal` to a view that should be launched modally.
### Different Modal Styles
-When you use a presentation type of `LaunchStyle.SwiftUI.PresentationType.modal` you can optionally pass it a `LaunchStyle.SwiftUI.ModalPresentationStyle`.
+When you use a presentation type of `LaunchStyle.SwiftUI.PresentationType.modal`, you can optionally pass it a `LaunchStyle.SwiftUI.ModalPresentationStyle`.
-#### Example:
-The following will use a full screen cover:
+#### Example
+The following will use a full-screen cover:
```swift
NavigationView {
WorkflowLauncher(isLaunched: .constant(true)) {


message:
Update Working with NavigationView.md
Proofread and updated.

diff:
### Presentation Types
-When you are constructing a workflow you can use `WorkflowItem.presentationType(_:)` along with a valid `LaunchStyle.SwiftUI.PresentationType` to control how a `FlowRepresentable` will be displayed. This is how you'll describe your navigation links and keep your view ignorant of the context it's displayed in.
+When constructing a workflow, you can use `WorkflowItem.presentationType(_:)` along with a valid `LaunchStyle.SwiftUI.PresentationType` to control how a `FlowRepresentable` will be displayed. This is how you'll describe your navigation links and keep your view ignorant of the context it's displayed in.
-#### Example:
+#### Example
```swift
NavigationView {
WorkflowLauncher(isLaunched: .constant(true)) {
@@ -12,15 +12,15 @@ NavigationView {
}
```
-With that you've described that `FirstView` should be wrapped in a `NavigationLink` when presented. So when it calls `FlowRepresentable.proceedInWorkflow()` it'll present `SecondView` using that `NavigationLink`.
+With that, you've described that `FirstView` should be wrapped in a `NavigationLink` when presented. When it calls `FlowRepresentable.proceedInWorkflow()`, it'll present `SecondView` using that `NavigationLink`.
-> NOTE: The `NavigationLink` is in the background of the view, to prevent your entire view from being tappable.
+> **NOTE:** The `NavigationLink` is in the background of the view to prevent your entire view from being tappable.
### Different NavigationView Styles
-SwiftCurrent comes with a convenience function on `WorkflowLauncher` that tries to pick the best `NavigationViewStyle` for a `Workflow`. Normally that's stack based navigation.
+SwiftCurrent comes with a convenience function on `WorkflowLauncher` that tries to pick the best `NavigationViewStyle` for a `Workflow`. Normally that's stack-based navigation.
-#### Example:
-The earlier example could be rewritten as
+#### Example
+The earlier example could be rewritten as:
```swift
WorkflowLauncher(isLaunched: .constant(true)) {
thenProceed(with: FirstView.self) {
@@ -29,9 +29,9 @@ WorkflowLauncher(isLaunched: .constant(true)) {
}.embedInNavigationView()
```
-This will select the stack based navigation wherever it is available, otherwise it uses the default navigation style.
+This will select the stack-based navigation wherever it is available; otherwise it uses the default navigation style.
-If you want to use column based navigation you can simply manage it yourself:
+If you want to use column-based navigation you can simply manage it yourself:
```swift
NavigationView {


diff:
-SwiftCurrent works well with UIKit. To work correctly, requires `FlowRepresentable`s to be `UIViewController`s and not `UIView`s. It comes with [support for Storyboards](using-storyboards.html) with `StoryboardLoadable` and [programmatic views](using-programmatic-views.html).
+SwiftCurrent works well with UIKit when `FlowRepresentable`s are `UIViewController`s. `UIView`s are not supported at this time. It comes with [support for Storyboards](using-storyboards.html) with `StoryboardLoadable` and [programmatic views](using-programmatic-views.html).

original_message:
Update .github/abstract/How to use SwiftCurrent with UIKit.md

Co-authored-by: Tyler Thompson <33705774+Tyler-Keith-Thompson@users.noreply.github.com>


message:
Update README.md
Changed "See These to Learn More" to "Click Here to Learn More."

diff:
@@ -75,7 +75,7 @@ And just like that, you've got a workflow! You can now add more items to it or r
# Look at Our Example Apps
We have [example apps](https://github.com/wwt/SwiftCurrent/tree/main/ExampleApps) for both SwiftUI and UIKit that show SwiftCurrent in action. They've already been tested, so you can see what it's like to test SwiftCurrent code. To run it locally, start by cloning the repo, open `SwiftCurrent.xcworkspace` and then run the `SwiftUIExample` scheme or the `UIKitExample` scheme.
-# [See These to Learn More](https://wwt.github.io/SwiftCurrent/Creating%20Workflows.html)
+# [Click Here to Learn More](https://wwt.github.io/SwiftCurrent/Creating%20Workflows.html)
For specific documentation check out:
- [Why SwiftCurrent?](https://wwt.github.io/SwiftCurrent/why-this-library.html)
- [Installation](https://wwt.github.io/SwiftCurrent/installation.html)


message: add first test case

diff:
@@ -18,6 +18,13 @@ class PrimerClassifyTest < Minitest::Test
assert_generated_class("f6", { font_size: 6 })
end
+ def test_border_margins
+ assert_generated_class("border-top-0", { border_top: 0 })
+ assert_generated_class("border-bottom-0", { border_bottom: 0 })
+ assert_generated_class("border-left-0", { border_left: 0 })
+ assert_generated_class("border-right-0", { border_right: 0 })
+ end
+
def test_m
assert_generated_class("m-4", { m: 4 })
assert_generated_class("mx-4", { mx: 4 })

license: MIT License

original_message: add first test case


message: replace old tests for border margins with no impelemntations

diff:
@@ -18,12 +18,12 @@ class PrimerClassifyTest < Minitest::Test
assert_generated_class("f6", { font_size: 6 })
end
- def test_border_margins
- assert_generated_class("border-top-0", { border_top: 0 })
- assert_generated_class("border-bottom-0", { border_bottom: 0 })
- assert_generated_class("border-left-0", { border_left: 0 })
- assert_generated_class("border-right-0", { border_right: 0 })
- end
+ # def test_border_margins
+ # assert_generated_class("border-top-0", { border_top: 0 })
+ # assert_generated_class("border-bottom-0", { border_bottom: 0 })
+ # assert_generated_class("border-left-0", { border_left: 0 })
+ # assert_generated_class("border-right-0", { border_right: 0 })
+ # end
def test_m
assert_generated_class("m-4", { m: 4 })
@@ -179,6 +179,10 @@ class PrimerClassifyTest < Minitest::Test
assert_generated_class("border-bottom", { border: :bottom })
assert_generated_class("border-right", { border: :right })
assert_generated_class("border-y", { border: :y })
+ assert_generated_class("border-top-0", { border: :top_0 })
+ assert_generated_class("border-bottom-0", { border: :bottom_0 })
+ assert_generated_class("border-left-0", { border: :left_0 })
+ assert_generated_class("border-right-0", { border: :right_0 })
end
def test_border_color


message: delete border margins original test

diff:
@@ -18,13 +18,6 @@ class PrimerClassifyTest < Minitest::Test
assert_generated_class("f6", { font_size: 6 })
end
- # def test_border_margins
- # assert_generated_class("border-top-0", { border_top: 0 })
- # assert_generated_class("border-bottom-0", { border_bottom: 0 })
- # assert_generated_class("border-left-0", { border_left: 0 })
- # assert_generated_class("border-right-0", { border_right: 0 })
- # end
-
def test_m
assert_generated_class("m-4", { m: 4 })
assert_generated_class("mx-4", { mx: 4 })

original_message: delete border margins original test


message: add support for border margins

diff:
@@ -77,7 +77,7 @@ module Primer
]
}
}.freeze
- BORDER_KEYS = [:border, :border_color].freeze
+ BORDER_KEYS = [:border, :border_color, :border_top, :border_bottom, :border_left, :border_right].freeze
TYPOGRAPHY_KEYS = [:font_size].freeze
VALID_KEYS = (
CONCAT_KEYS +
@@ -170,6 +170,8 @@ module Primer
end
dasherized_val = val.to_s.dasherize
+ dasherized_key = key.to_s.dasherize
+
breakpoint = BREAKPOINTS[index]
if BOOLEAN_MAPPINGS.has_key?(key)
@@ -195,7 +197,11 @@ module Primer
elsif key == WORD_BREAK_KEY
memo[:classes] << "wb-#{dasherized_val}"
elsif BORDER_KEYS.include?(key)
+ if (key == :border || key == :border_color)
memo[:classes] << "border-#{dasherized_val}"
+ else
+ memo[:classes] << "#{dasherized_key}-#{val}"
+ end
elsif key == DIRECTION_KEY
memo[:classes] << "flex#{breakpoint}-#{dasherized_val}"
elsif key == JUSTIFY_CONTENT_KEY

diff:
@@ -172,10 +172,13 @@ class PrimerClassifyTest < Minitest::Test
assert_generated_class("border-bottom", { border: :bottom })
assert_generated_class("border-right", { border: :right })
assert_generated_class("border-y", { border: :y })
- assert_generated_class("border-top-0", { border: :top_0 })
- assert_generated_class("border-bottom-0", { border: :bottom_0 })
- assert_generated_class("border-left-0", { border: :left_0 })
- assert_generated_class("border-right-0", { border: :right_0 })
+ end
+
+ def test_border_margins
+ assert_generated_class("border-top-0", { border_top: 0 })
+ assert_generated_class("border-bottom-0", { border_bottom: 0 })
+ assert_generated_class("border-left-0", { border_left: 0 })
+ assert_generated_class("border-right-0", { border_right: 0 })
end
def test_border_color

original_message: add support for border margins


message: create border margins constant

diff:
@@ -77,7 +77,8 @@ module Primer
]
}
}.freeze
- BORDER_KEYS = [:border, :border_color, :border_top, :border_bottom, :border_left, :border_right].freeze
+ BORDER_KEYS = [:border, :border_color].freeze
+ BORDER_MARGIN_KEYS = [:border_top, :border_bottom, :border_left, :border_right].freeze
TYPOGRAPHY_KEYS = [:font_size].freeze
VALID_KEYS = (
CONCAT_KEYS +
@@ -170,7 +171,6 @@ module Primer
end
dasherized_val = val.to_s.dasherize
- dasherized_key = key.to_s.dasherize
breakpoint = BREAKPOINTS[index]
@@ -197,11 +197,9 @@ module Primer
elsif key == WORD_BREAK_KEY
memo[:classes] << "wb-#{dasherized_val}"
elsif BORDER_KEYS.include?(key)
- if (key == :border || key == :border_color)
memo[:classes] << "border-#{dasherized_val}"
- else
- memo[:classes] << "#{dasherized_key}-#{val}"
- end
+ elsif BORDER_MARGIN_KEYS.include?(key)
+ memo[:classes] << "#{key.to_s.dasherize}-#{val}"
elsif key == DIRECTION_KEY
memo[:classes] << "flex#{breakpoint}-#{dasherized_val}"
elsif key == JUSTIFY_CONTENT_KEY

original_message: create border margins constant


message: add border-margin-keys to list of valid keys

diff:
@@ -84,6 +84,7 @@ module Primer
CONCAT_KEYS +
BOOLEAN_MAPPINGS.keys +
BORDER_KEYS +
+ BORDER_MARGIN_KEYS +
TYPOGRAPHY_KEYS +
TEXT_KEYS +
[
@@ -171,6 +172,7 @@ module Primer
end
dasherized_val = val.to_s.dasherize
+ dasherized_key = key.to_s.dasherize
breakpoint = BREAKPOINTS[index]


message: update version to 0.0.8

diff:
# main
+# 0.0.8
+
* Add support for border margins, such as: `border_top: 0`.
*Natasha Umer*

diff:
@@ -5,7 +5,7 @@ module Primer
module VERSION
MAJOR = 0
MINOR = 0
- PATCH = 7
+ PATCH = 8
STRING = [MAJOR, MINOR, PATCH].join(".")
end

original_message: update version to 0.0.8


message: modify contributing directions

diff:
@@ -34,8 +34,7 @@ If you are the current maintainer of this gem:
1. Add version heading to `CHANGELOG.md`.
1. Make a PR to primer/view_components.
1. Merge primer/view_components PR
+1. Create a GitHub [release](https://github.com/primer/view_components/releases/new) with the pushed tag and populate it with a list of the changes from `CHANGELOG.md`. The title is the same as the release version. Example title: v0.0.8
1. Get latest changes from default branch: `git pull origin main`
1. Build gem: `gem build primer_view_components.gemspec`
-1. Tag and push: `git tag vx.xx.xx; git push --tags`
-1. Create a GitHub [release](https://github.com/primer/view_components/releases/new) with the pushed tag and populate it with a list of the changes from `CHANGELOG.md`.
1. Push to rubygems.org -- `gem push primer_view_components-VERSION.gem`

original_message: modify contributing directions


message: add .so_sym method to size and color in state component

diff:
@@ -37,12 +37,12 @@ module Primer
)
@kwargs = kwargs
@kwargs[:title] = title
- @kwargs[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, TAG_DEFAULT)
+ @kwargs[:tag] = fetch_or_fallback(TAG_OPTIONS, tag.to_sym, TAG_DEFAULT)
@kwargs[:classes] = class_names(
@kwargs[:classes],
"State",
- COLOR_MAPPINGS[fetch_or_fallback(COLOR_OPTIONS, color, COLOR_DEFAULT)],
- SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
+ COLOR_MAPPINGS[fetch_or_fallback(COLOR_OPTIONS, color.to_sym, COLOR_DEFAULT)],
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size.to_sym, SIZE_DEFAULT)]
)
end


message: add story for state component

change_type: ADD

diff:
+# frozen_string_literal: true
+
+class Primer::StateComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:state) do
+ controls do
+ title "this is the title"
+ select(:color, Primer::StoriesHelper.array_to_options(Primer::StateComponent::COLOR_OPTIONS), :default)
+ select(:size, Primer::StoriesHelper.array_to_options(Primer::StateComponent::SIZE_MAPPINGS.keys), :default)
+ select(:tag, Primer::StoriesHelper.array_to_options(Primer::StateComponent::TAG_OPTIONS), :span)
+ end
+
+ content do
+ "This is a state!"
+ end
+ end
+end

original_message: add story for state component


message: use options constant instad of calling keys method

diff:
@@ -7,7 +7,7 @@ class Primer::StateComponentStories < ViewComponent::Storybook::Stories
controls do
title "this is the title"
select(:color, Primer::StoriesHelper.array_to_options(Primer::StateComponent::COLOR_OPTIONS), :default)
- select(:size, Primer::StoriesHelper.array_to_options(Primer::StateComponent::SIZE_MAPPINGS.keys), :default)
+ select(:size, Primer::StoriesHelper.array_to_options(Primer::StateComponent::SIZE_OPTIONS), :default)
select(:tag, Primer::StoriesHelper.array_to_options(Primer::StateComponent::TAG_OPTIONS), :span)
end


message: add test for storybook count

diff:
@@ -5,6 +5,41 @@ require "test_helper"
class PrimerComponentTest < Minitest::Test
include Primer::ComponentTestHelpers
+
+
+ # COMPONENTS_WITH_ARGS = [
+ # [Primer::AvatarComponent, { alt: "github", src: "https://github.com/github.png" }],
+ # [Primer::BaseComponent, { tag: :div }],
+ # [Primer::BlankslateComponent, { title: "Foo" }],
+ # [Primer::BorderBoxComponent, {}, proc { |component| component.slot(:header) { "Foo" } }],
+ # [Primer::BoxComponent, {}],
+ # [Primer::BreadcrumbComponent, {}, proc { |component| component.slot(:item) { "Foo" } }],
+ # [Primer::ButtonComponent, {}],
+ # [Primer::CounterComponent, { count: 1 }],
+ # [Primer::DetailsComponent, {}, lambda do |component|
+ # component.slot(:summary) { "Foo" }
+ # component.slot(:body) { "Bar" }
+ # end],
+ # [Primer::DropdownMenuComponent, {}],
+ # [Primer::FlexComponent, {}],
+ # [Primer::FlashComponent, {}],
+ # [Primer::FlexItemComponent, { flex_auto: true }],
+ # [Primer::HeadingComponent, {}],
+ # [Primer::LabelComponent, { title: "Hello!" }],
+ # [Primer::LayoutComponent, {}],
+ # [Primer::LinkComponent, { href: "https://www.google.com" }],
+ # [Primer::OcticonComponent, { icon: "people" }],
+ # [Primer::PopoverComponent, {}, proc { |component| component.slot(:body) }],
+ # [Primer::ProgressBarComponent, {}, proc { |component| component.slot(:item) }],
+ # [Primer::StateComponent, { title: "Open" }],
+ # [Primer::SubheadComponent, { heading: "Foo" }, proc { |component| component.slot(:heading) { "Foo" } }],
+ # [Primer::TextComponent, {}],
+ # [Primer::TimelineItemComponent, {}, proc { |component| component.slot(:body) { "Foo" } }],
+ # [Primer::UnderlineNavComponent, {}],
+ # ]
+
+ # write a step when it's ititerating to see if a story file exists for that component and if it doesnt you can raise an error, placeholder story file for each one. That will allow us to ensure that we always have a story everytime we have a component.
+ # or write a story first. cc / john.
def test_primer_components_provide_a_consistent_interface
# Components with any arguments necessary to make them render
components_with_args = [
@@ -80,6 +115,19 @@ class PrimerComponentTest < Minitest::Test
end
end
+ # def components_storybook_count
+ #
+ # expected_components_count = COMPONENTS_WITH_ARGS.length
+
+ # storybook_count = Dir[Rails.root.join("stories/primer/**/*.rb")].length
+
+ # #if expected_components_count != storybook_count
+
+ # message = "\n The storybook count is less than the expected components count "
+
+ # assert_equal expected_components_count, storybook_count, message
+ # end
+
def render_component(component, args, proc)
render_inline(component.new(**args)) do |c|
proc.call(c) if proc.present?

original_message: add test for storybook count


message: add a test for the storybook count

diff:
@@ -5,44 +5,8 @@ require "test_helper"
class PrimerComponentTest < Minitest::Test
include Primer::ComponentTestHelpers
-
-
- # COMPONENTS_WITH_ARGS = [
- # [Primer::AvatarComponent, { alt: "github", src: "https://github.com/github.png" }],
- # [Primer::BaseComponent, { tag: :div }],
- # [Primer::BlankslateComponent, { title: "Foo" }],
- # [Primer::BorderBoxComponent, {}, proc { |component| component.slot(:header) { "Foo" } }],
- # [Primer::BoxComponent, {}],
- # [Primer::BreadcrumbComponent, {}, proc { |component| component.slot(:item) { "Foo" } }],
- # [Primer::ButtonComponent, {}],
- # [Primer::CounterComponent, { count: 1 }],
- # [Primer::DetailsComponent, {}, lambda do |component|
- # component.slot(:summary) { "Foo" }
- # component.slot(:body) { "Bar" }
- # end],
- # [Primer::DropdownMenuComponent, {}],
- # [Primer::FlexComponent, {}],
- # [Primer::FlashComponent, {}],
- # [Primer::FlexItemComponent, { flex_auto: true }],
- # [Primer::HeadingComponent, {}],
- # [Primer::LabelComponent, { title: "Hello!" }],
- # [Primer::LayoutComponent, {}],
- # [Primer::LinkComponent, { href: "https://www.google.com" }],
- # [Primer::OcticonComponent, { icon: "people" }],
- # [Primer::PopoverComponent, {}, proc { |component| component.slot(:body) }],
- # [Primer::ProgressBarComponent, {}, proc { |component| component.slot(:item) }],
- # [Primer::StateComponent, { title: "Open" }],
- # [Primer::SubheadComponent, { heading: "Foo" }, proc { |component| component.slot(:heading) { "Foo" } }],
- # [Primer::TextComponent, {}],
- # [Primer::TimelineItemComponent, {}, proc { |component| component.slot(:body) { "Foo" } }],
- # [Primer::UnderlineNavComponent, {}],
- # ]
-
- # write a step when it's ititerating to see if a story file exists for that component and if it doesnt you can raise an error, placeholder story file for each one. That will allow us to ensure that we always have a story everytime we have a component.
- # or write a story first. cc / john.
- def test_primer_components_provide_a_consistent_interface
# Components with any arguments necessary to make them render
- components_with_args = [
+ COMPONENTS_WITH_ARGS = [
[Primer::AvatarComponent, { alt: "github", src: "https://github.com/github.png" }],
[Primer::BaseComponent, { tag: :div }],
[Primer::BlankslateComponent, { title: "Foo" }],
@@ -73,12 +37,13 @@ class PrimerComponentTest < Minitest::Test
[Primer::UnderlineNavComponent, {}],
]
+ def test_primer_components_provide_a_consistent_interface
ignored_components = ["Primer::Component"]
primer_component_files_count = Dir["app/**/*component.rb"].count
- assert_equal primer_component_files_count, components_with_args.length + ignored_components.count, "Primer component added. Please update this test with an entry for your new component <3"
+ assert_equal primer_component_files_count, COMPONENTS_WITH_ARGS.length + ignored_components.count, "Primer component added. Please update this test with an entry for your new component <3"
- components_with_args.each do |component, args, proc|
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
# component renders hash arguments
render_component(component, { my: 4 }.merge(args), proc)
assert_selector(".my-4")
@@ -115,18 +80,15 @@ class PrimerComponentTest < Minitest::Test
end
end
- # def components_storybook_count
- #
- # expected_components_count = COMPONENTS_WITH_ARGS.length
-
- # storybook_count = Dir[Rails.root.join("stories/primer/**/*.rb")].length
+ def test_components_storybook_count
+ expected_missing_stories = 13
- # #if expected_components_count != storybook_count
+ expected_components_count = COMPONENTS_WITH_ARGS.length
- # message = "\n The storybook count is less than the expected components count "
+ storybook_count = Dir[Rails.root.join("../stories/primer/**/*.rb")].length
- # assert_equal expected_components_count, storybook_count, message
- # end
+ assert_equal expected_components_count, storybook_count + expected_missing_stories
+ end
def render_component(component, args, proc)
render_inline(component.new(**args)) do |c|

original_message: add a test for the storybook count


message: change value string to symbol

diff:
@@ -36,8 +36,8 @@ module Primer
@kwargs[:classes] = class_names(
"Label",
kwargs[:classes],
- SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme)],
- VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant)]
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme.to_sym)],
+ VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant.to_sym)]
)
end

original_message: change value string to symbol


message: add story to label component

diff:
+# frozen_string_literal: true
+
+class Primer::LabelComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:label) do
+ controls do
+ title "this is a label"
+ select(:scheme, Primer::StoriesHelper.array_to_options(Primer::LabelComponent::SCHEME_MAPPINGS.keys), :blue)
+ select(:variant, Primer::StoriesHelper.array_to_options(Primer::LabelComponent::VARIANT_MAPPINGS.keys), :large)
+ end
+
+ content do
+ "This is a label"
+ end
+ end
+end

original_message: add story to label component


message: add bundle install to procfile

old_path: Procfile

new_path: Procfile

diff:
-stories: cd demo; bin/rails view_component_storybook:write_stories_json
-rails: cd demo; STORYBOOK=true bin/rails s -p 4000
+stories: cd demo; bundle install; bin/rails view_component_storybook:write_stories_json
+rails: cd demo; bundle install; STORYBOOK=true bin/rails s -p 4000
storybook: cd demo; STORYBOOK=true bin/yarn storybook -p 5000

original_message: add bundle install to procfile


message: removie stories helper array and to_sym

diff:
@@ -36,8 +36,8 @@ module Primer
@kwargs[:classes] = class_names(
"Label",
kwargs[:classes],
- SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme.to_sym)],
- VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant.to_sym)]
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme)],
+ VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant)]
)
end

diff:
@@ -6,8 +6,8 @@ class Primer::LabelComponentStories < ViewComponent::Storybook::Stories
story(:label) do
controls do
title "this is a label"
- select(:scheme, Primer::StoriesHelper.array_to_options(Primer::LabelComponent::SCHEME_MAPPINGS.keys), :blue)
- select(:variant, Primer::StoriesHelper.array_to_options(Primer::LabelComponent::VARIANT_MAPPINGS.keys), :large)
+ select(:scheme, (Primer::LabelComponent::SCHEME_MAPPINGS.keys), :blue)
+ select(:variant, (Primer::LabelComponent::VARIANT_MAPPINGS.keys), :large)
end
content do

original_message: removie stories helper array and to_sym


message: add storybook for link component

diff:
+# frozen_string_literal: true
+
+class Primer::LinkComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:link) do
+ controls do
+ href "https://github.com/"
+ muted false
+ end
+
+ content do
+ "This is a link!"
+ end
+ end
+end

original_message: add storybook for link component


message: decrement storybook counter and add comments for clarity

diff:
@@ -81,7 +81,10 @@ class PrimerComponentTest < Minitest::Test
end
def test_components_storybook_count
- expected_missing_stories = 14
+
+ # Should be deprecated each time a new storybook is added to a component
+ # Should be incremented if a new view component is added without a storybook
+ expected_missing_stories = 13
expected_components_count = COMPONENTS_WITH_ARGS.length


message: deprecate storybook linter counter

diff:
@@ -81,7 +81,7 @@ class PrimerComponentTest < Minitest::Test
end
def test_components_storybook_count
- expected_missing_stories = 14
+ expected_missing_stories = 13
expected_components_count = COMPONENTS_WITH_ARGS.length

original_message: deprecate storybook linter counter


message: fix trailing whitespace

diff:
@@ -81,7 +81,11 @@ class PrimerComponentTest < Minitest::Test
end
def test_components_storybook_count
- expected_missing_stories = 13
+ Should be deprecated each time a new storybook is added to a component
+
+ # Should be incremented if a new view component is added without a storybook
+ # Should be incremented if a new view component is added without a storybook
+ expected_missing_stories = 12
expected_components_count = COMPONENTS_WITH_ARGS.length

original_message: fix trailing whitespace


message: Scaffold spinner component

diff: +<span title="<%= @title %>"><%= content %>

diff:
+module Primer
+ class SpinnerComponent < Primer::Component
+ def initialize(title:)
+ @title = title
+ end
+ end
+end
\ No newline at end of file

diff:
@@ -43,6 +43,7 @@ require_relative "link_component"
require_relative "octicon_component"
require_relative "popover_component"
require_relative "progress_bar_component"
+require_relative "spinner_component"
require_relative "state_component"
require_relative "subhead_component"
require_relative "text_component"

diff:
+# frozen_string_literal: true
+
+class Primer::SpinnerComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:spinner) do
+ controls do
+ text(:title, "github")
+ end
+ end
+end
+
\ No newline at end of file

original_message: Scaffold spinner component


message: Create Spinner component

old_path: Rakefile

new_path: Rakefile

diff:
@@ -61,7 +61,8 @@ namespace :docs do
Primer::AvatarComponent,
Primer::BaseComponent,
Primer::BlankslateComponent,
- Primer::CounterComponent
+ Primer::CounterComponent,
+ Primer::SpinnerComponent
]
components.each do |component|
documentation = registry.get(component.name)

diff:
-<span title="<%= @title %>"><%= content %>
+<svg width="<%= @size %>" height="<%= @size %>" viewBox="0 0 16 16" color="var(--color-icon-primary)" fill="none">
+ <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" />
+ <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke">
+ <animateTransform
+ attributeName="transform"
+ type="rotate"
+ from="0 8 8"
+ to="360 8 8"
+ dur="1s"
+ repeatCount="indefinite"
+ />
+ </path>
+</svg>
+
+

diff:
+# frozen_string_literal: true
+
module Primer
class SpinnerComponent < Primer::Component
- def initialize(title:)
- @title = title
+
+ #
+ # @example 48|Default
+ # <%= render(Primer::SpinnerComponent.new()) %>
+ #
+ # @example 32|Small
+ # <%= render(Primer::SpinnerComponent.new(size: 16)) %>
+ #
+ # @param size [Integer] Sets the size of the spinner
+ def initialize(size: 32)
+ @size = size
end
end
end
\ No newline at end of file

diff:
+---
+title: Primer::SpinnerComponent
+---
+
+
+
+## Examples
+
+### Default
+
+<iframe style="width: 100%; border: 0px; height: 48px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg width='32' height='32' viewBox='0 0 16 16' color='var(--color-icon-primary)' fill='none'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
+
+```erb
+<%= render(Primer::SpinnerComponent.new()) %>
+```
+
+### Small
+
+<iframe style="width: 100%; border: 0px; height: 32px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg width='16' height='16' viewBox='0 0 16 16' color='var(--color-icon-primary)' fill='none'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
+
+```erb
+<%= render(Primer::SpinnerComponent.new(size: 16)) %>
+```
+
+## Arguments
+
+| Name | Type | Default | Description |
+| :- | :- | :-: | :- |
+| size | Integer | 32 | Sets the size of the spinner |

diff:
url: /components/blankslate
- title: Counter
url: /components/counter
+ - title: Spinner
+ url: /components/spinner

diff:
@@ -5,7 +5,7 @@ class Primer::SpinnerComponentStories < ViewComponent::Storybook::Stories
story(:spinner) do
controls do
- text(:title, "github")
+ size 32
end
end
end

original_message: Create Spinner component


message: Use BaseComponent

diff:
-<svg width="<%= @size %>" height="<%= @size %>" viewBox="0 0 16 16" color="var(--color-icon-primary)" fill="none">
+<%= render Primer::BaseComponent.new(**@kwargs) do %>
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" />
<path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke">
- <animateTransform
- attributeName="transform"
- type="rotate"
- from="0 8 8"
- to="360 8 8"
- dur="1s"
- repeatCount="indefinite"
- />
+ <animateTransform attributeName="transform" type="rotate" from="0 8 8" to="360 8 8" dur="1s" repeatCount="indefinite" />
</path>
-</svg>
+<% end %>

diff:
@@ -11,8 +11,16 @@ module Primer
# <%= render(Primer::SpinnerComponent.new(size: 16)) %>
#
# @param size [Integer] Sets the size of the spinner
- def initialize(size: 32)
- @size = size
+ def initialize(size: 32, **kwargs)
+ @kwargs = kwargs
+ @kwargs[:tag] ||= :svg
+ @kwargs[:width] = size
+ @kwargs[:height] = size
+ @kwargs[:viewBox] = "0 0 16 16"
+ @kwargs[:fill] = :none
+ # Setting `box-sizing: content-box` allows consumers to add padding
+ # to the spinner without shrinking the icon
+ @kwargs[:style] = "box-sizing: content-box; color: var(--color-icon-primary);"
end
end
end
\ No newline at end of file

diff:
@@ -8,7 +8,7 @@ title: Primer::SpinnerComponent
### Default
-<iframe style="width: 100%; border: 0px; height: 48px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg width='32' height='32' viewBox='0 0 16 16' color='var(--color-icon-primary)' fill='none'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
+<iframe style="width: 100%; border: 0px; height: 48px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg viewBox='0 0 16 16' fill='none' style='box-sizing: content-box; color: var(--color-icon-primary);' width='32' height='32'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
```erb
<%= render(Primer::SpinnerComponent.new()) %>
@@ -16,7 +16,7 @@ title: Primer::SpinnerComponent
### Small
-<iframe style="width: 100%; border: 0px; height: 32px;" srcdoc="<html><head><link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'></head><body><svg width='16' height='16' viewBox='0 0 16 16' color='var(--color-icon-primary)' fill='none'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg></body></html>"></iframe>
+<iframe style="width: 100%; border: 0px; height: 32px;" srcdoc="<html><head><link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'></head><body><svg viewBox='0 0 16 16' fill='none' style='box-sizing: content-box; color: var(--color-icon-primary);' width='16' height='16'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg></body></html>"></iframe>
```erb
<%= render(Primer::SpinnerComponent.new(size: 16)) %>

original_message: Use BaseComponent


message: Add spinner tests

diff:
+# frozen_string_literal: true
+
+require "test_helper"
+
+class PrimerSpinnerComponentTest < Minitest::Test
+ include Primer::ComponentTestHelpers
+
+ def test_renders_an_svg
+ render_inline(Primer::SpinnerComponent.new())
+
+ assert_selector("spinner")
+ end
+
+ def test_defaults_to_size_32
+ render_inline(Primer::SpinnerComponent.new())
+
+ assert_selector("svg[height=32][width=32]")
+ end
+
+ def test_sets_size_height_and_width
+ render_inline(Primer::SpinnerComponent.new(size: 16))
+
+ assert_selector("svg[height=16][width=16]")
+ end
+end

original_message: Add spinner tests


message: Add component description comment

diff:
# frozen_string_literal: true
module Primer
+ # Use Primer::SpinnerComponent to let users know that content is being loaded.
class SpinnerComponent < Primer::Component
#

original_message: Add component description comment


message: Always use SVG tag

diff:
@@ -14,7 +14,7 @@ module Primer
# @param size [Integer] Sets the size of the spinner
def initialize(size: 32, **kwargs)
@kwargs = kwargs
- @kwargs[:tag] ||= :svg
+ @kwargs[:tag] = :svg
@kwargs[:width] = size
@kwargs[:height] = size
@kwargs[:viewBox] = "0 0 16 16"

original_message:
Always use SVG tag

Co-authored-by: Joel Hawksley <joelhawksley@github.com>


message: Restrict size argument

diff:
@@ -4,19 +4,30 @@ module Primer
# Use Primer::SpinnerComponent to let users know that content is being loaded.
class SpinnerComponent < Primer::Component
+ DEFAULT_SIZE = :medium
+ SIZE_MAPPINGS = {
+ :small => 16,
+ DEFAULT_SIZE => 32,
+ :large => 64,
+ }.freeze
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
+
#
# @example 48|Default
# <%= render(Primer::SpinnerComponent.new) %>
#
# @example 32|Small
- # <%= render(Primer::SpinnerComponent.new(size: 16)) %>
+ # <%= render(Primer::SpinnerComponent.new(size: :small)) %>
+ #
+ # @example 80|Large
+ # <%= render(Primer::SpinnerComponent.new(size: :large)) %>
#
- # @param size [Integer] Sets the size of the spinner
- def initialize(size: 32, **kwargs)
+ # @param size [Symbol] Sets the size of the spinner
+ def initialize(size: DEFAULT_SIZE, **kwargs)
@kwargs = kwargs
@kwargs[:tag] ||= :svg
- @kwargs[:width] = size
- @kwargs[:height] = size
+ @kwargs[:width] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
+ @kwargs[:height] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
@kwargs[:viewBox] = "0 0 16 16"
@kwargs[:fill] = :none
# Setting `box-sizing: content-box` allows consumers to add padding

diff:
@@ -19,11 +19,19 @@ Use Primer::SpinnerComponent to let users know that content is being loaded.
<iframe style="width: 100%; border: 0px; height: 32px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg viewBox='0 0 16 16' fill='none' style='box-sizing: content-box; color: var(--color-icon-primary);' width='16' height='16'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
```erb
-<%= render(Primer::SpinnerComponent.new(size: 16)) %>
+<%= render(Primer::SpinnerComponent.new(size: :small)) %>
+```
+
+### Large
+
+<iframe style="width: 100%; border: 0px; height: 80px;" srcdoc="<link href='https://unpkg.com/@primer/css/dist/primer.css' rel='stylesheet'><svg viewBox='0 0 16 16' fill='none' style='box-sizing: content-box; color: var(--color-icon-primary);' width='64' height='64'> <circle cx='8' cy='8' r='7' stroke='currentColor' stroke-opacity='0.25' stroke-width='2' vector-effect='non-scaling-stroke' /> <path d='M15 8a7.002 7.002 0 00-7-7' stroke='currentColor' stroke-width='2' stroke-linecap='round' vector-effect='non-scaling-stroke'> <animateTransform attributeName='transform' type='rotate' from='0 8 8' to='360 8 8' dur='1s' repeatCount='indefinite' /> </path></svg>">
+
+```erb
+<%= render(Primer::SpinnerComponent.new(size: :large)) %>
```
## Arguments
| Name | Type | Default | Description |
| :- | :- | :-: | :- |
-| size | Integer | 32 | Sets the size of the spinner |
+| size | Symbol | DEFAULT_SIZE | Sets the size of the spinner |

diff:
@@ -5,7 +5,7 @@ class Primer::SpinnerComponentStories < ViewComponent::Storybook::Stories
story(:spinner) do
controls do
- size 32
+ select(:size, Primer::SpinnerComponent::SIZE_OPTIONS, :medium)
end
end
end

diff:
@@ -17,9 +17,15 @@ class PrimerSpinnerComponentTest < Minitest::Test
assert_selector("svg[height=32][width=32]")
end
- def test_sets_size_height_and_width
- render_inline(Primer::SpinnerComponent.new(size: 16))
+ def test_size_small
+ render_inline(Primer::SpinnerComponent.new(size: :small))
assert_selector("svg[height=16][width=16]")
end
+
+ def test_size_large
+ render_inline(Primer::SpinnerComponent.new(size: :large))
+
+ assert_selector("svg[height=64][width=64]")
+ end
end

original_message: Restrict size argument