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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Sources/Slipstream/W3C/Elements/Forms/ProgressView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import SwiftSoup

/// A view that displays an indicator showing the completion progress of a task.
///
/// The `ProgressView` represents an HTML `<progress>` element, which displays
/// progress towards completion of a task. It can be used to show determinate
/// progress (with a specific value) or indeterminate progress (when the value
/// is unknown).
///
/// ```swift
/// // Indeterminate progress (no value specified)
/// ProgressView()
///
/// // Determinate progress with default max (1.0)
/// ProgressView(value: 0.7)
///
/// // Custom value and max
/// ProgressView(value: 70, max: 100)
/// ```
///
/// - SeeAlso: W3C [progress](https://html.spec.whatwg.org/multipage/form-elements.html#the-progress-element) specification.
@available(iOS 17.0, macOS 14.0, *)
public struct ProgressView: View {
/// Creates a progress indicator.
///
/// - Parameters:
/// - value: The current progress value. When `nil`, the progress indicator
/// is indeterminate, indicating that an activity is ongoing with no
/// indication of how long it is expected to take. When specified, the value
/// must be between 0 and `max`.
/// - max: The maximum value, representing task completion. When `nil`, the
/// W3C default of 1.0 is used, allowing you to specify values as percentages
/// (e.g., 0.7 for 70%). When specified, must be greater than 0.
public init(value: Double? = nil, max: Double? = nil) {
self.value = value
self.max = max
}

@_documentation(visibility: private)
public func render(_ container: Element, environment: EnvironmentValues) throws {
let element = try container.appendElement("progress")

if let value {
try element.attr("value", String(value))
}
if let max {
try element.attr("max", String(max))
}
}

private let value: Double?
private let max: Double?
}
43 changes: 43 additions & 0 deletions Tests/SlipstreamTests/W3C/Forms/ProgressViewTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Testing

import Slipstream

struct ProgressViewTests {
@Test func indeterminate() throws {
try #expect(renderHTML(ProgressView()) == #"<progress></progress>"#)
}

@Test func withValue() throws {
try #expect(renderHTML(ProgressView(value: 0.7)) == #"<progress value="0.7"></progress>"#)
}

@Test func withCustomMax() throws {
try #expect(renderHTML(ProgressView(max: 100)) == #"<progress max="100.0"></progress>"#)
}

@Test func withValueAndMax() throws {
try #expect(renderHTML(ProgressView(value: 70, max: 100)) == #"<progress value="70.0" max="100.0"></progress>"#)
}

@Test func zeroValue() throws {
try #expect(renderHTML(ProgressView(value: 0, max: 100)) == #"<progress value="0.0" max="100.0"></progress>"#)
}

@Test func fullValue() throws {
try #expect(renderHTML(ProgressView(value: 100, max: 100)) == #"<progress value="100.0" max="100.0"></progress>"#)
}

@Test func percentageUseCase() throws {
// Using default max of 1.0 for percentage-style progress
try #expect(renderHTML(ProgressView(value: 0.0)) == #"<progress value="0.0"></progress>"#)
try #expect(renderHTML(ProgressView(value: 0.5)) == #"<progress value="0.5"></progress>"#)
try #expect(renderHTML(ProgressView(value: 1.0)) == #"<progress value="1.0"></progress>"#)
}

@Test func numericUseCase() throws {
// Using custom max for numeric progress tracking
try #expect(renderHTML(ProgressView(value: 25, max: 100)) == #"<progress value="25.0" max="100.0"></progress>"#)
try #expect(renderHTML(ProgressView(value: 50, max: 100)) == #"<progress value="50.0" max="100.0"></progress>"#)
try #expect(renderHTML(ProgressView(value: 75, max: 100)) == #"<progress value="75.0" max="100.0"></progress>"#)
}
}