From a2c81fcafe006e7f081dce070ce68507c9efb24b Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 18 Aug 2026 16:40:35 +0200 Subject: [PATCH 1/5] feat: add circular progress indicator --- .../TextInput/Internal/InputText.swift | 15 ++++++++-- .../Internal/TextInputContainer.swift | 28 ++++++++++--------- ...er.swift => TextInputLabelContainer.swift} | 2 +- ...ft => TextInputLeadingIconContainer.swift} | 2 +- ...swift => TextInputTrailingContainer.swift} | 10 ++++--- .../Controls/TextInput/OUDSTextInput.swift | 13 +++++++-- 6 files changed, 45 insertions(+), 25 deletions(-) rename OUDS/Core/Components/Sources/Controls/TextInput/Internal/{LabelContainer.swift => TextInputLabelContainer.swift} (98%) rename OUDS/Core/Components/Sources/Controls/TextInput/Internal/{LeadingIconContainer.swift => TextInputLeadingIconContainer.swift} (96%) rename OUDS/Core/Components/Sources/Controls/TextInput/Internal/{TrailingActionContainer.swift => TextInputTrailingContainer.swift} (89%) diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/InputText.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/InputText.swift index 93839cdc6df5..b8e99638fe5e 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/InputText.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/InputText.swift @@ -54,10 +54,19 @@ struct InputText: View { .multilineTextAlignment(.leading) .foregroundColor(inputTextColor) .tint(cursorColor.color(for: colorScheme)) - .disabled(status == .disabled || status == .readOnly || status == .loading) + .disabled(disabled) } - // MARK: - Helper + // MARK: - Helpers + + private var disabled: Bool { + switch status { + case .disabled, .readOnly, .loading: + true + default: + false + } + } private var labelColor: MultipleColorSemanticToken { switch status { @@ -106,7 +115,7 @@ private struct SecureTextFieldModifier: ViewModifier { .textContentType(.password) .autocorrectionDisabled(true) #if !os(macOS) - .textInputAutocapitalization(.never) + .textInputAutocapitalization(.never) #endif } else { content diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift index aac4def3f5da..f91cd69ece9f 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift @@ -42,25 +42,25 @@ struct TextInputContainer: View { HStack(alignment: .center, spacing: theme.textInput.spaceColumnGapDefault) { HStack(alignment: .center, spacing: theme.textInput.spaceColumnGapDefault) { // Leading icon container - LeadingIconContainer(leadingIcon: leadingIcon, status: status) + TextInputLeadingIconContainer(leadingIcon: leadingIcon, status: status) // ZStack here to add the label above the textField when // the text is empty, the placeholder is empty and not focused // Otherwise the label is placed at the top ZStack { if labelPosition == .middle { - LabelContainer(label: label, - status: status, - interactionState: interactionState, - position: .middle) + TextInputLabelContainer(label: label, + status: status, + interactionState: interactionState, + position: .middle) } VStack(alignment: .leading, spacing: theme.textInput.spaceRowGapLabelInput) { if labelPosition == .top { - LabelContainer(label: label, - status: status, - interactionState: interactionState, - position: .top) + TextInputLabelContainer(label: label, + status: status, + interactionState: interactionState, + position: .top) } InputContainer(text: text, @@ -81,24 +81,26 @@ struct TextInputContainer: View { } } - // Trailing action container - TrailingActionContainer(trailingAction: trailingAction, status: status, interactionState: interactionState) + // Trailing container + TextInputTrailingContainer(trailingAction: trailingAction, status: status, interactionState: interactionState) } .padding(.vertical, theme.textInput.spacePaddingBlockDefault) + // TODO: A verifier, avec un leading le token devrait être + // ouds/💠_control/text-input/space/padding-inline/start .padding(.leading, theme.textInput.spacePaddingInlineDefault) .padding(.trailing, trailingPadding) .frame(minHeight: theme.textInput.sizeMinHeight, alignment: .leading) .modifier(TextInputBackgroundModifier(status: status, isOutlined: isOutlined, interactionState: interactionState)) .modifier(TextInputBorderModifier(status: status, isOutlined: isOutlined, interactionState: interactionState)) #if !os(watchOS) && !os(tvOS) - .onHover { hover = $0 } + .onHover { hover = $0 } #endif // swiftlint:enable accessibility_trait_for_button } // MARK: - Helpers - private var labelPosition: LabelContainer.Position { + private var labelPosition: TextInputLabelContainer.Position { if !text.wrappedValue.isEmpty || placeholder?.isEmpty == false || focused { .top } else { diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/LabelContainer.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLabelContainer.swift similarity index 98% rename from OUDS/Core/Components/Sources/Controls/TextInput/Internal/LabelContainer.swift rename to OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLabelContainer.swift index b012a5a1a5ce..c2408f0eb419 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/LabelContainer.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLabelContainer.swift @@ -15,7 +15,7 @@ import OUDSTokensSemantic import SwiftUI -struct LabelContainer: View { +struct TextInputLabelContainer: View { // MARK: Properties diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/LeadingIconContainer.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLeadingIconContainer.swift similarity index 96% rename from OUDS/Core/Components/Sources/Controls/TextInput/Internal/LeadingIconContainer.swift rename to OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLeadingIconContainer.swift index 238a3ae554ba..5b6f9e8869a5 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/LeadingIconContainer.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputLeadingIconContainer.swift @@ -15,7 +15,7 @@ import OUDSTokensSemantic import SwiftUI -struct LeadingIconContainer: View { +struct TextInputLeadingIconContainer: View { // MARK: - Properties diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TrailingActionContainer.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputTrailingContainer.swift similarity index 89% rename from OUDS/Core/Components/Sources/Controls/TextInput/Internal/TrailingActionContainer.swift rename to OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputTrailingContainer.swift index f2f7089c738d..b3718ab777b7 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TrailingActionContainer.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputTrailingContainer.swift @@ -15,7 +15,7 @@ import OUDSTokensSemantic import SwiftUI -struct TrailingActionContainer: View { +struct TextInputTrailingContainer: View { // MARK: - Properties @@ -48,8 +48,10 @@ struct TrailingActionContainer: View { trailingButton(for: trailingAction) } } - case .loading: - trailingButton(for: .init(image: OUDSImage(asset: Image(decorative: "ic_heart")), actionHint: "", action: {})) + case let .loading(progress): + TextInputCircularProgressIndicator(progress: progress) + .padding(.all, theme.button.spaceInsetProgressIndicatorOnlyDefault) + .padding(.all, theme.button.spaceInsetIconOnlyDefault) .accessibilityHidden(true) } } @@ -78,7 +80,7 @@ struct TrailingActionContainer: View { return OUDSButton(image: imageWithA11y, appearance: .minimal, - style: status == .loading ? .loading : .default, + style: .default, action: trailingAction.action) } } diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift index 08ac60db6dba..1bc24b7b5b51 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift @@ -262,6 +262,8 @@ public struct OUDSTextInput: View { /// - actionHint: A string that describes the purpose of the button's `action` /// - action: The action to perform when the user triggers the button public init(image: OUDSImage, actionHint: String, action: @escaping () -> Void) { + precondition(image.asset != nil, "OUDSTextInput.TrailingAction.icon must be created with an asset Image") + if actionHint.isEmpty { OL.warning("The accessibility action hint for the OUDSTextInput trailing action should not be empty, think about your disabled users!") } @@ -291,8 +293,11 @@ public struct OUDSTextInput: View { case richError(message: AttributedString) /// The `loading` state indicates that the system is processing or retrieving data related to the - /// text entered. A progress indicator appears to inform the user that an action is in progress. - case loading + /// text entered. A circular progress indicator appears to inform the user that an action is in progress. + /// The field remains editable while loading. + /// - Parameter progress: The loading progress, where 0.0 represents no progress and 1.0 represents full progress. Set this + /// value to `nil` to display a circular indeterminate progress indicator. + case loading(progress: Double? = nil) /// The`readOnly`, lets the text visible but not editable case readOnly @@ -303,8 +308,10 @@ public struct OUDSTextInput: View { public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { - case (.enabled, .enabled), (.loading, .loading), (.readOnly, .readOnly), (.disabled, .disabled): + case (.enabled, .enabled), (.readOnly, .readOnly), (.disabled, .disabled): true + case let (.loading(lhsProgress), .loading(rhsProgress)): + lhsProgress == rhsProgress case let (.error(lhsMessage), .error(rhsMessage)): lhsMessage == rhsMessage case let (.richError(lhsMessage), .richError(rhsMessage)): From 6432f35822e5427efbe07c13c24938ebffea6359 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 18 Aug 2026 17:57:56 +0200 Subject: [PATCH 2/5] feat: add circular progress indicator --- CHANGELOG.md | 1 + .../Controls/TextInput/Internal/TextInputContainer.swift | 2 -- .../Components/Sources/Controls/TextInput/OUDSTextInput.swift | 2 +- OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c2abf22bca..0d31845cc730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- `text input` component to version 1.4.1 (Orange-OpenSource/ouds-ios#1524) - `text area` component to version 1.2.1 (Orange-OpenSource/ouds-ios#1527) - themes tunings values (Orange-OpenSource/ouds-ios#1669) - `button` component to version 3.3.0 (Orange-OpenSource/ouds-ios#1583) diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift index f91cd69ece9f..1d9ba408e343 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/Internal/TextInputContainer.swift @@ -85,8 +85,6 @@ struct TextInputContainer: View { TextInputTrailingContainer(trailingAction: trailingAction, status: status, interactionState: interactionState) } .padding(.vertical, theme.textInput.spacePaddingBlockDefault) - // TODO: A verifier, avec un leading le token devrait être - // ouds/💠_control/text-input/space/padding-inline/start .padding(.leading, theme.textInput.spacePaddingInlineDefault) .padding(.trailing, trailingPadding) .frame(minHeight: theme.textInput.sizeMinHeight, alignment: .leading) diff --git a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift index 1bc24b7b5b51..bbda485911e6 100644 --- a/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift +++ b/OUDS/Core/Components/Sources/Controls/TextInput/OUDSTextInput.swift @@ -207,7 +207,7 @@ import SwiftUI /// /// ![A text input component in light and dark modes with Wireframe theme](component_textInput_Wireframe) /// -/// - Version: 1.4.0 (Figma component design version) +/// - Version: 1.4.1 (Figma component design version) /// - Since: 0.20.0 @available(iOS 15, macOS 13, visionOS 1, *) public struct OUDSTextInput: View { diff --git a/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift b/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift index 9dc4c301d127..e7ba6adf1347 100644 --- a/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift +++ b/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift @@ -92,8 +92,8 @@ public enum OUDSVersions { public static let componentSwitchVersion = "1.5.0" /// Version of the Figma specifications for the component text area (1.2.1) public static let componentTextAreaVersion = "1.2.1" - /// Version of the Figma specifications for the component text input (1.4.0) - public static let componentTextInputVersion = "1.4.0" + /// Version of the Figma specifications for the component text input (1.4.1) + public static let componentTextInputVersion = "1.4.1" // MARK: - Components versions - Dialog From fdcab0a77c3a357098ce13cacef76dc1104222fe Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Tue, 18 Aug 2026 22:40:33 +0200 Subject: [PATCH 3/5] feat: update PasswordInput to version 1.3.1 --- CHANGELOG.md | 1 + .../Sources/Controls/PasswordInput/OUDSPasswordInput.swift | 2 +- OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d31845cc730..e98d2f4a9f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- `password input` component to version 1.3.1 (Orange-OpenSource/ouds-ios#1526) - `text input` component to version 1.4.1 (Orange-OpenSource/ouds-ios#1524) - `text area` component to version 1.2.1 (Orange-OpenSource/ouds-ios#1527) - themes tunings values (Orange-OpenSource/ouds-ios#1669) diff --git a/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift b/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift index 8423f771f9ae..7fee41bca80d 100644 --- a/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift +++ b/OUDS/Core/Components/Sources/Controls/PasswordInput/OUDSPasswordInput.swift @@ -110,7 +110,7 @@ import SwiftUI /// /// ![A text input component in light and dark modes with Wireframe theme](component_passwordInput_Wireframe) /// -/// - Version: 1.3.0 (Figma component design version) +/// - Version: 1.3.1 (Figma component design version) /// - Since: 1.2.0 @available(iOS 15, macOS 13, visionOS 1, *) public struct OUDSPasswordInput: View { diff --git a/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift b/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift index e7ba6adf1347..f8cc0ed82f16 100644 --- a/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift +++ b/OUDS/Core/ThemesContract/Sources/TokenatorConstants.swift @@ -68,8 +68,8 @@ public enum OUDSVersions { public static let componentFilterChipExpandVersion = "1.5.0" /// Version of the Figma specifications for the component suggestion chip (1.5.0) public static let componentSuggestionChipVersion = "1.5.0" - /// Version of the Figma specifications for the component password input (1.3.0) - public static let componentPasswordInputVersion = "1.3.0" + /// Version of the Figma specifications for the component password input (1.3.1) + public static let componentPasswordInputVersion = "1.3.1" /// Version of the Figma specifications for the component phone number input (1.3.0) public static let componentPhoneNumberInputVersion = "1.3.0" /// Version of the Figma specifications for the component pin code input (1.3.0) From 416e2feb06fc8467799c8e6ad53261130287cb43 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Wed, 19 Aug 2026 11:03:23 +0200 Subject: [PATCH 4/5] chore: fix lint issues --- ...tring+ExtensionsForegroundColorTests.swift | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsForegroundColorTests.swift b/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsForegroundColorTests.swift index a0253beebca0..ec1d548d2d59 100644 --- a/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsForegroundColorTests.swift +++ b/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsForegroundColorTests.swift @@ -19,8 +19,6 @@ import Testing // MARK: - AttributedString Extensions Tests (foreground color only, tokens, and LocalizedStringKey overloads) -// swiftlint:disable force_unwrapping - struct AttributedStringColorExtensionsTests { // MARK: - from(text:) - Foreground color only @@ -85,17 +83,17 @@ struct AttributedStringColorExtensionsTests { // MARK: - from(_:) - LocalizedStringKey, foreground color, font and URL configurations - @Test func `from localized key with font and URL configurations applies link style`() { + @Test func `from localized key with font and URL configurations applies link style`() throws { let key: LocalizedStringKey = "Check the privacy policy" let urlColor = Color.red let urlFont = Font.system(size: 14, weight: .bold) let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -113,17 +111,17 @@ struct AttributedStringColorExtensionsTests { } } - @Test func `from localized key with foreground color token, font and URL configurations applies link style`() { + @Test func `from localized key with foreground color token, font and URL configurations applies link style`() throws { let key: LocalizedStringKey = "Check the privacy policy" let token: ColorSemanticToken = "#000000FF" let urlColor = Color.red let urlFont = Font.system(size: 14, weight: .bold) let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -142,17 +140,17 @@ struct AttributedStringColorExtensionsTests { // MARK: - from(text:) - Foreground color token, font and URL configurations - @Test func `from text with foreground color token, font and URL configurations applies link style`() { + @Test func `from text with foreground color token, font and URL configurations applies link style`() throws { let text = "Check the privacy policy" let token: ColorSemanticToken = "#123456FF" let urlColor = Color.purple let urlFont = Font.system(size: 14) let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -196,17 +194,17 @@ struct AttributedStringColorExtensionsTests { // MARK: - from(markdown:) - Foreground color token, font and URL configurations - @Test func `from markdown with foreground color token, font and URL configurations applies link style`() { + @Test func `from markdown with foreground color token, font and URL configurations applies link style`() throws { let markdown = "Check our [privacy policy](https://example.com/privacy)" let token: ColorSemanticToken = "#654321FF" let urlColor = Color.red let urlFont = Font.system(size: 14, weight: .bold) let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -226,9 +224,9 @@ struct AttributedStringColorExtensionsTests { // MARK: - AttributedStringUrlConfiguration - LocalizedStringKey initializer - @Test func `configuration from localized key resolves text and stores url`() { + @Test func `configuration from localized key resolves text and stores url`() throws { let key: LocalizedStringKey = "privacy policy" - let url = URL(string: "https://example.com/privacy")! + let url = try #require(URL(string: "https://example.com/privacy")) let color = Color.red let font = Font.system(size: 14) @@ -248,5 +246,3 @@ struct AttributedStringColorExtensionsTests { } } } - -// swiftlint:enable force_unwrapping From 0a5d69b16d451e1b93c6ee8d3627e20895803ed1 Mon Sep 17 00:00:00 2001 From: Ludovic PINEL Date: Thu, 20 Aug 2026 16:26:58 +0200 Subject: [PATCH 5/5] chore: fix lint issues --- .../AttributedString+ExtensionsTests.swift | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsTests.swift b/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsTests.swift index 4a4afe029eb7..0f6dd93eb277 100644 --- a/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsTests.swift +++ b/OUDS/Core/Components/Tests/_/RichText/AttributedString+ExtensionsTests.swift @@ -19,23 +19,21 @@ import Testing // MARK: - AttributedString Extensions Tests -// swiftlint:disable force_unwrapping - struct AttributedStringExtensionsTests { // MARK: - from(markdown:) - Configuration by URL - @Test func `from markdown with URL configuration applies color and font`() { + @Test func `from markdown with URL configuration applies color and font`() throws { let markdown = "Check our [privacy policy](https://example.com/privacy)" let urlColor = Color.red let urlFont = Font.system(size: 14, weight: .bold) let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -53,16 +51,16 @@ struct AttributedStringExtensionsTests { } } - @Test func `from markdown with URL configuration matches by URL string`() { + @Test func `from markdown with URL configuration matches by URL string`() throws { let markdown = "Visit [our site](https://example.com) for more info" let urlColor = Color.blue let urlFont = Font.system(size: 14) let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( - urlToOpen: URL(string: "https://example.com")!, + urlToOpen: #require(URL(string: "https://example.com")), color: urlColor, font: urlFont), ] @@ -144,17 +142,17 @@ struct AttributedStringExtensionsTests { // MARK: - from(markdown:) - Default Configuration - @Test func `from markdown with default configuration applies to unmatched URLs`() { + @Test func `from markdown with default configuration applies to unmatched URLs`() throws { let markdown = "Check [unknown link](https://unknown.com) and [privacy](https://example.com/privacy)" let defaultColor = Color.orange let defaultFont = Font.system(size: 16, weight: .light) let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: Color.green, font: Font.system(size: 14)), AttributedStringUrlConfiguration( @@ -202,20 +200,20 @@ struct AttributedStringExtensionsTests { // MARK: - from(markdown:) - Edge Cases - @Test func `from markdown with multiple links applies correct styles`() { + @Test func `from markdown with multiple links applies correct styles`() throws { let markdown = "Link [A](https://a.com) and [B](https://b.com) and [C](https://c.com)" let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "A", - urlToOpen: URL(string: "https://a.com")!, + urlToOpen: #require(URL(string: "https://a.com")), color: Color.red, font: Font.system(size: 14)), AttributedStringUrlConfiguration( text: "B", - urlToOpen: URL(string: "https://b.com")!, + urlToOpen: #require(URL(string: "https://b.com")), color: Color.blue, font: Font.system(size: 16)), ] @@ -243,17 +241,17 @@ struct AttributedStringExtensionsTests { // MARK: - from(text:) - Basic Tests - @Test func `from text with URL configuration applies link style`() { + @Test func `from text with URL configuration applies link style`() throws { let text = "Check the privacy policy and terms of use" let urlColor = Color.red let urlFont = Font.system(size: 14, weight: .bold) let textColor = Color.black let textFont = Font.system(size: 12) - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "privacy policy", - urlToOpen: URL(string: "https://example.com/privacy")!, + urlToOpen: #require(URL(string: "https://example.com/privacy")), color: urlColor, font: urlFont), ] @@ -271,12 +269,12 @@ struct AttributedStringExtensionsTests { } } - @Test func `from text with configuration text not found in string does not crash`() { + @Test func `from text with configuration text not found in string does not crash`() throws { let text = "Simple text without matching shard" - let configurations: [AttributedStringUrlConfiguration] = [ + let configurations: [AttributedStringUrlConfiguration] = try [ AttributedStringUrlConfiguration( text: "not present", - urlToOpen: URL(string: "https://example.com")!, + urlToOpen: #require(URL(string: "https://example.com")), color: .red, font: .system(size: 14)), ] @@ -308,5 +306,3 @@ struct AttributedStringExtensionsTests { } } } - -// swiftlint:enable force_unwrapping