From 94de0c11eace6e14809f36df8324398917424b35 Mon Sep 17 00:00:00 2001 From: Michael Zimmermann Date: Thu, 12 Oct 2023 12:41:51 +0200 Subject: [PATCH 1/6] [Fix] Remove broken test. --- .../View/SwiftUI/ChipViewSnapshotTests.swift | 146 ------------------ 1 file changed, 146 deletions(-) delete mode 100644 core/Sources/Components/Chip/View/SwiftUI/ChipViewSnapshotTests.swift diff --git a/core/Sources/Components/Chip/View/SwiftUI/ChipViewSnapshotTests.swift b/core/Sources/Components/Chip/View/SwiftUI/ChipViewSnapshotTests.swift deleted file mode 100644 index 96f830def..000000000 --- a/core/Sources/Components/Chip/View/SwiftUI/ChipViewSnapshotTests.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// ChipViewSnapshotTests.swift -// SparkCoreTests -// -// Created by michael.zimmermann on 20.09.23. -// Copyright © 2023 Adevinta. All rights reserved. -// - -import XCTest -import SwiftUI -import SnapshotTesting - -@testable import SparkCore -@testable import Spark - -final class ChipViewSnapshotTests: SwiftUIComponentTestCase { - - // MARK: - Properties - - private let theme: Theme = SparkTheme.shared - private var icon = Image(systemName: "person.2.circle.fill") - private let title = "Title" - - // MARK: - Tests - - func test_swiftUI_chip_with_all_intents_outlined() throws { - - let variations = ChipIntent.allCases.map{ ChipVariation(intent: $0, variant: .outlined) } - - for variation in variations { - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - icon: self.icon, - title: self.title - ).fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - sizes: [.medium], - testName: variation.testName() - ) - } - } - - func test_swiftUI_chip_with_all_variants_basic() throws { - - let variations = ChipVariant.allCases.map{ ChipVariation(intent: .basic, variant: $0) } - - for variation in variations { - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - icon: self.icon, - title: self.title - ).fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - sizes: [.medium], - testName: variation.testName() - ) - } - } - - func test_swiftUI_chip_all_sizes() throws { - let variation = ChipVariation(intent: .basic, variant: .filled) - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - icon: self.icon, - title: self.title - ).fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - testName: variation.testName() - ) - } - - func test_swiftUI_chip_with_icon_only() throws { - let variation = ChipVariation(intent: .info, variant: .filled) - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - icon: self.icon - ).fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - sizes: [.medium], - testName: variation.testName() - ) - } - - func test_swiftUI_chip_with_icon_and_leading_title() throws { - let variation = ChipVariation(intent: .basic, variant: .outlined) - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - alignment: .trailingIcon, - title: self.title - ).fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - sizes: [.medium], - testName: variation.testName() - ) - } - - func test_swiftUI_chip_with_extra_component_only() throws { - let variation = ChipVariation(intent: .main, variant: .dashed) - - let component = AnyView(Image(systemName: "xmark.circle")) - let view = ChipView( - theme: self.theme, - intent: variation.intent, - variant: variation.variant, - icon: self.icon, - title: self.title - ) - .component(component) - .fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - sizes: [.medium], - testName: variation.testName() - ) - } -} - -private struct ChipVariation { - let intent: ChipIntent - let variant: ChipVariant - - func testName(_ function: String = #function) -> String { - return "\(function)-\(self.intent)-\(self.variant)" - } -} From 4b97cca2ff047fae8d1b9ef9a7cfec3a20dceea2 Mon Sep 17 00:00:00 2001 From: Robin Lemaire Date: Thu, 12 Oct 2023 17:43:04 +0200 Subject: [PATCH 2/6] [Thread-507] Replace RunLoop to new UIScheduler --- ...ift => Publisher+SubscribeExtension.swift} | 4 +- .../Common/Combine/Global/UIScheduler.swift | 76 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) rename core/Sources/Common/Combine/Global/{Publisher-Subscribe.swift => Publisher+SubscribeExtension.swift} (86%) create mode 100644 core/Sources/Common/Combine/Global/UIScheduler.swift diff --git a/core/Sources/Common/Combine/Global/Publisher-Subscribe.swift b/core/Sources/Common/Combine/Global/Publisher+SubscribeExtension.swift similarity index 86% rename from core/Sources/Common/Combine/Global/Publisher-Subscribe.swift rename to core/Sources/Common/Combine/Global/Publisher+SubscribeExtension.swift index a8ec0e76d..0bc30b1a0 100644 --- a/core/Sources/Common/Combine/Global/Publisher-Subscribe.swift +++ b/core/Sources/Common/Combine/Global/Publisher+SubscribeExtension.swift @@ -1,5 +1,5 @@ // -// Publisher-Subscribe.swift +// Publisher+SubscribeExtension.swift // SparkCore // // Created by michael.zimmermann on 05.07.23. @@ -12,7 +12,7 @@ import Foundation extension Publisher where Failure == Never { func subscribe( in subscriptions: inout Set, - on scheduler: S = RunLoop.main, + on scheduler: S = UIScheduler.shared, action: @escaping (Self.Output) -> Void ) where S: Scheduler { self .receive(on: scheduler) diff --git a/core/Sources/Common/Combine/Global/UIScheduler.swift b/core/Sources/Common/Combine/Global/UIScheduler.swift new file mode 100644 index 000000000..564caee38 --- /dev/null +++ b/core/Sources/Common/Combine/Global/UIScheduler.swift @@ -0,0 +1,76 @@ +// +// UIScheduler.swift +// SparkCore +// +// Created by robin.lemaire on 12/10/2023. +// Copyright © 2023 Adevinta. All rights reserved. +// + +import Combine +import Foundation + +/// Combine custom scheduler which schedule action in DispatchQueue.main +struct UIScheduler: Scheduler { + + // MARK: - Type Alias + + typealias SchedulerOptions = Never + typealias SchedulerTimeType = DispatchQueue.SchedulerTimeType + + // MARK: - Static Properties + + /// Shared instance + static let shared = Self() + + // MARK: - Public Properties + + /// This scheduler's definition of the current moment in time. + var now: SchedulerTimeType { self.dispatchQueue.now } + /// The minimum tolerance allowed by the scheduler. + var minimumTolerance: SchedulerTimeType.Stride { self.dispatchQueue.minimumTolerance } + + // MARK: - Private Properties + + private let dispatchQueue: DispatchQueue + private let key = DispatchSpecificKey() + private let value: UInt8 = 0 + + // MARK: - Initialization + + private init(dispatchQueue: DispatchQueue = .main) { + self.dispatchQueue = dispatchQueue + self.dispatchQueue.setSpecific(key: self.key, value: self.value) + } + + // MARK: - Schedule + + /// Performs the action at the next possible opportunity. Maybe immediat if we don't need to change thread + func schedule(options _: SchedulerOptions? = nil, _ action: @escaping () -> Void) { + if DispatchQueue.getSpecific(key: self.key) == self.value { + action() + } else { + self.dispatchQueue.schedule(action) + } + } + + /// Performs the action at some time after the specified date. + func schedule( + after date: SchedulerTimeType, + tolerance: SchedulerTimeType.Stride, + options _: SchedulerOptions? = nil, + _ action: @escaping () -> Void + ) { + self.dispatchQueue.schedule(after: date, tolerance: tolerance, options: nil, action) + } + + /// Performs the action at some time after the specified date, at the specified frequency, optionally taking into account tolerance if possible. + func schedule( + after date: SchedulerTimeType, + interval: SchedulerTimeType.Stride, + tolerance: SchedulerTimeType.Stride, + options _: SchedulerOptions? = nil, + _ action: @escaping () -> Void + ) -> Cancellable { + self.dispatchQueue.schedule(after: date, interval: interval, tolerance: tolerance, options: nil, action) + } +} From 2b07992d3dd93a4ab6c02664704117c35353dd31 Mon Sep 17 00:00:00 2001 From: Robin Lemaire Date: Tue, 10 Oct 2023 16:49:30 +0200 Subject: [PATCH 3/6] [Tests-1544] Tag snapshot refactoring --- .../View/Common/TagSutSnapshotStrategy.swift | 16 ++ .../Tag/View/Common/TagSutSnapshotTests.swift | 265 ++++++++++++++++-- .../View/SwiftUI/TagViewSnapshotTests.swift | 68 ++--- .../View/UIKit/TagUIViewSnapshotTests.swift | 103 ++++--- 4 files changed, 336 insertions(+), 116 deletions(-) create mode 100644 core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift diff --git a/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift b/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift new file mode 100644 index 000000000..76cf94772 --- /dev/null +++ b/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift @@ -0,0 +1,16 @@ +// +// TagSutSnapshotStrategy.swift +// SparkCoreSnapshotTests +// +// Created by robin.lemaire on 10/10/2023. +// Copyright © 2023 Adevinta. All rights reserved. +// + +enum TagSutSnapshotStrategy: String, CaseIterable { + case test1 + case test2 + case test3 + case test4 + case test5 + case test6 +} diff --git a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift index 17e81225f..ba0fbe68e 100644 --- a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift @@ -6,40 +6,271 @@ // Copyright © 2023 Adevinta. All rights reserved. // +import UIKit +import SwiftUI @testable import SparkCore struct TagSutSnapshotTests { + // MARK: - Type Alias + + typealias Constants = ComponentSnapshotTestConstants + // MARK: - Properties + let strategy: TagSutSnapshotStrategy + let intent: TagIntent let variant: TagVariant + let iconImage: ImageEither? + let text: String? + private let isLongText: Bool + var width: CGFloat? { + return self.isLongText ? 100 : nil + } + let modes: [ComponentSnapshotTestMode] + let sizes: [UIContentSizeCategory] // MARK: - Getter - func testName(on function: String = #function) -> String { - return "\(function)-\(self.intent)-\(self.variant)" + func testName() -> String { + return [ + "\(self.strategy.rawValue)", + "\(self.intent)", + "\(self.variant)", + self.iconImage != nil ? "withImage" : "withoutImage", + self.isLongText ? "longText" : "normalText" + ].joined(separator: "-") + } + + // MARK: - Testing Strategy + + static func test( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + switch strategy { + case .test1: + return self.test1( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + case .test2: + return self.test2( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + case .test3: + return self.test3( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + case .test4: + return self.test4( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + case .test5: + return self.test5( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + case .test6: + return self.test6( + for: strategy, + isSwiftUIComponent: isSwiftUIComponent + ) + } } - // MARK: - Cases + /// Test 1 + /// + /// Description: To test all intents + /// + /// Content: + /// - intents: all + /// - variant : tinted + /// - content : icon + text + /// - theme : light / dark + /// - size : default + static func test1( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + let intents = TagIntent.allCases - static var allCases: [Self] { - return Self.allVariantCases(for: .alert) + - Self.allVariantCases(for: .danger) + - Self.allVariantCases(for: .info) + - Self.allVariantCases(for: .neutral) + - Self.allVariantCases(for: .main) + - Self.allVariantCases(for: .support) + - Self.allVariantCases(for: .success) + - Self.allVariantCases(for: .accent) + - Self.allVariantCases(for: .basic) + return intents.map { + .init( + strategy: strategy, + intent: $0, + variant: .tinted, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: Constants.modes, + sizes: [Constants.size] + ) + } } - private static func allVariantCases(for intent: TagIntent) -> [Self] { + /// Test 2 + /// + /// Description: To test all variants + /// + /// Content: + /// - intent: main + /// - variant : all + /// - content : text only + /// - theme : light / dark + /// - size : default + static func test2( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + let variants = TagVariant.allCases + + return variants.map { + .init( + strategy: strategy, + intent: .main, + variant: $0, + iconImage: nil, + text: "Text", + isLongText: false, + modes: Constants.modes, + sizes: [Constants.size] + ) + } + } + + /// Test 3 + /// + /// Description: To test all color for filled variant + /// + /// Content: + /// - intents: all + /// - variant : filled + /// - content : icon + text + /// - theme : default + /// - size : default + static func test3( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + let intents = TagIntent.allCases + + return intents.map { + .init( + strategy: strategy, + intent: $0, + variant: .filled, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: [Constants.mode], + sizes: [Constants.size] + ) + } + } + + /// Test 4 + /// + /// Description: To test content resilience + /// + /// Content: + /// - intent: neutral + /// - variant : tinted + /// - content : icon only + /// - theme : default + /// - size : default + static func test4( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { return [ - .init(intent: intent, variant: .filled), - .init(intent: intent, variant: .outlined), - .init(intent: intent, variant: .tinted) + .init( + strategy: strategy, + intent: .neutral, + variant: .tinted, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: nil, + isLongText: false, + modes: [Constants.mode], + sizes: [Constants.size] + ) ] } + + /// Test 5 + /// + /// Description: To test content resilience + /// + /// Content: + /// - intent: neutral + /// - variant : tinted + /// - content : long text + /// - theme : default + /// - size : default + static func test5( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + return [ + .init( + strategy: strategy, + intent: .neutral, + variant: .tinted, + iconImage: nil, + text: "Very very very long long text", + isLongText: true, + modes: [Constants.mode], + sizes: [Constants.size] + ) + ] + } + + /// Test 6 + /// + /// Description: To test a11y sizes + /// + /// Content: + /// - intent: main + /// - variant : tinted + /// - content : icon + text + /// - theme : default + /// - size : xs, medium, xxxl + static func test6( + for strategy: TagSutSnapshotStrategy, + isSwiftUIComponent: Bool + ) -> [TagSutSnapshotTests] { + return [ + .init( + strategy: strategy, + intent: .main, + variant: .tinted, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: [Constants.mode], + sizes: Constants.sizes + ) + ] + } +} + +// MARK: - Private Extensions + +private extension ImageEither { + + static func mock(isSwiftUIComponent: Bool) -> Self { + return isSwiftUIComponent ? .right(Image.mock) : .left(UIImage.mock) + } +} + +private extension Image { + static let mock = Image(systemName: "person.2.circle.fill") +} + +private extension UIImage { + static var mock = UIImage(systemName: "person.2.circle.fill") ?? UIImage() } diff --git a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift index 27a5fc303..8c0f3fb30 100644 --- a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift @@ -17,57 +17,33 @@ final class TagViewSnapshotTests: SwiftUIComponentSnapshotTestCase { // MARK: - Properties private let theme: Theme = SparkTheme.shared - private var iconImage = Image(systemName: "person.2.circle.fill") - private let text = "Text" // MARK: - Tests - func test_swiftUI_tag_with_only_image_for_all_intent_and_variant() throws { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = TagView(theme: self.theme) - .intent(sut.intent) - .variant(sut.variant) - .iconImage(self.iconImage) - .fixedSize() + func test() { + let strategies = TagSutSnapshotStrategy.allCases - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() - ) - } - } - - func test_swiftUI_tag_with_only_text_for_all_intent_and_variant() { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = TagView(theme: self.theme) - .intent(sut.intent) - .variant(sut.variant) - .text(self.text) - .fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() - ) - } - } - - func test_swiftUI_tag_with_image_and_text_for_all_intent_and_variant() throws { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = TagView(theme: self.theme) - .intent(sut.intent) - .variant(sut.variant) - .iconImage(self.iconImage) - .text(self.text) - .fixedSize() - - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() + for strategy in strategies { + let suts = TagSutSnapshotTests.test( + for: strategy, + isSwiftUIComponent: true ) + for sut in suts { + let view = TagView(theme: self.theme) + .intent(sut.intent) + .variant(sut.variant) + .iconImage(sut.iconImage?.rightValue) + .text(sut.text) + .frame(width: sut.width) + .fixedSize() + + self.assertSnapshot( + matching: view, + modes: sut.modes, + sizes: sut.sizes, + testName: sut.testName() + ) + } } } } diff --git a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift index a474b0e90..8c1a80fd6 100644 --- a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift @@ -16,67 +16,64 @@ final class TagUIViewSnapshotTests: UIKitComponentSnapshotTestCase { // MARK: - Properties private let theme: Theme = SparkTheme.shared - private var iconImage: UIImage { - get throws { - try XCTUnwrap( - UIImage(systemName: "person.2.circle.fill"), - "IconImage shouldn't be nil" - ) - } - } - private let text = "Text" // MARK: - Tests - func test_uiKit_tag_with_only_image_for_all_intent_and_variant() throws { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = try TagUIView( - theme: self.theme, - intent: sut.intent, - variant: sut.variant, - iconImage: self.iconImage - ) - - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() - ) - } - } + func test() { + let strategies = TagSutSnapshotStrategy.allCases - func test_uiKit_tag_with_only_text_for_all_intent_and_variant() { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = TagUIView( - theme: self.theme, - intent: sut.intent, - variant: sut.variant, - text: self.text + for strategy in strategies { + let suts = TagSutSnapshotTests.test( + for: strategy, + isSwiftUIComponent: false ) + for sut in suts { - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() - ) - } - } + var view: TagUIView? + switch (sut.iconImage, sut.text) { + case (nil, nil): + XCTFail("Icon or text should be set") - func test_uiKit_tag_with_image_and_text_for_all_intent_and_variant() throws { - let suts = TagSutSnapshotTests.allCases - for sut in suts { - let view = try TagUIView( - theme: self.theme, - intent: sut.intent, - variant: sut.variant, - iconImage: self.iconImage, - text: self.text - ) + case (let iconImage?, nil): + view = TagUIView( + theme: self.theme, + intent: sut.intent, + variant: sut.variant, + iconImage: iconImage.leftValue + ) + case (nil, let text?): + view = TagUIView( + theme: self.theme, + intent: sut.intent, + variant: sut.variant, + text: text + ) + case let (iconImage?, text?): + view = TagUIView( + theme: self.theme, + intent: sut.intent, + variant: sut.variant, + iconImage: iconImage.leftValue, + text: text + ) + } - self.assertSnapshotInDarkAndLight( - matching: view, - testName: sut.testName() - ) + guard let view else { + return + } + + view.translatesAutoresizingMaskIntoConstraints = false + if let width = sut.width { + view.widthAnchor.constraint(equalToConstant: width).isActive = true + } + + self.assertSnapshot( + matching: view, + modes: sut.modes, + sizes: sut.sizes, + testName: sut.testName() + ) + } } } } From 0e210cac1c3039b5753f2a00f0b03f19f0952fbc Mon Sep 17 00:00:00 2001 From: Robin Lemaire Date: Thu, 12 Oct 2023 14:25:46 +0200 Subject: [PATCH 4/6] [Tests-1544] Tag snapshot refactoring --- .../Common/TagScenarioSnapshotTests.swift | 196 ++++++++++++++ .../View/Common/TagSutSnapshotStrategy.swift | 16 -- .../Tag/View/Common/TagSutSnapshotTests.swift | 244 +----------------- .../View/SwiftUI/TagViewSnapshotTests.swift | 9 +- .../View/UIKit/TagUIViewSnapshotTests.swift | 9 +- .../ComponentSnapshotTestConstants.swift | 12 +- .../Enum/ComponentSnapshotTestMode.swift | 2 +- .../SwiftUIComponentSnapshotTestCase.swift | 6 +- .../UIKitComponentSnapshotTestCase.swift | 14 +- 9 files changed, 225 insertions(+), 283 deletions(-) create mode 100644 core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift delete mode 100644 core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift diff --git a/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift b/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift new file mode 100644 index 000000000..74d6cc371 --- /dev/null +++ b/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift @@ -0,0 +1,196 @@ +// +// TagScenarioSnapshotTestsTests.swift +// SparkCoreSnapshotTests +// +// Created by robin.lemaire on 10/10/2023. +// Copyright © 2023 Adevinta. All rights reserved. +// + +@testable import SparkCore +import UIKit +import SwiftUI + +enum TagScenarioSnapshotTests: String, CaseIterable { + case test1 + case test2 + case test3 + case test4 + case test5 + + // MARK: - Type Alias + + typealias Constants = ComponentSnapshotTestConstants + + // MARK: - Stubs + + func sut(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + switch self { + case .test1: + return self.test1(isSwiftUIComponent: isSwiftUIComponent) + case .test2: + return self.test2(isSwiftUIComponent: isSwiftUIComponent) + case .test3: + return self.test3(isSwiftUIComponent: isSwiftUIComponent) + case .test4: + return self.test4(isSwiftUIComponent: isSwiftUIComponent) + case .test5: + return self.test5(isSwiftUIComponent: isSwiftUIComponent) + } + } + + // MARK: - Scenarios + + /// Test 1 + /// + /// Description: To test all intents + /// + /// Content: + /// - intents: all + /// - variant: tinted + /// - content: icon + text + /// - mode: all + /// - size: default + private func test1(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + let intents = TagIntent.allCases + + return intents.map { + .init( + scenario: self, + intent: $0, + variant: .tinted, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: Constants.Modes.all, + sizes: Constants.Sizes.default + ) + } + } + + /// Test 2 + /// + /// Description: To test all variants + /// + /// Content: + /// - intent: main + /// - variant: all + /// - content: text only + /// - mode: all + /// - size: default + private func test2(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + let variants = TagVariant.allCases + + return variants.map { + .init( + scenario: self, + intent: .main, + variant: $0, + iconImage: nil, + text: "Text", + isLongText: false, + modes: Constants.Modes.all, + sizes: Constants.Sizes.default + ) + } + } + + /// Test 3 + /// + /// Description: To test all color for filled variant + /// + /// Content: + /// - intents: all + /// - variant: filled + /// - content: icon + text + /// - mode: default + /// - size: default + private func test3(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + let intents = TagIntent.allCases + + return intents.map { + .init( + scenario: self, + intent: $0, + variant: .filled, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: Constants.Modes.default, + sizes: Constants.Sizes.default + ) + } + } + + /// Test 4 + /// + /// Description: To test content resilience + /// + /// Content: + /// - intent: neutral + /// - variant: tinted + /// - content: all (icon only / long text/ long text + icon) + /// - mode: default + /// - size: default + private func test4(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + let contents: [(isIconImage: Bool, isLongText: Bool)] = [ + (isIconImage: true, isLongText: false), // Only Icon + (isIconImage: false, isLongText: true), // Only long text + (isIconImage: true, isLongText: true) // Icon + Long text + ] + + return contents.map { + .init( + scenario: self, + intent: .neutral, + variant: .tinted, + iconImage: $0.isIconImage ? .mock(isSwiftUIComponent: isSwiftUIComponent) : nil, + text: $0.isLongText ? "Very very long long text" : nil, + isLongText: $0.isLongText, + modes: Constants.Modes.default, + sizes: Constants.Sizes.default + ) + } + } + + /// Test 6 + /// + /// Description: To test a11y sizes + /// + /// Content: + /// - intent: main + /// - variant: tinted + /// - content: icon + text + /// - mode: default + /// - size: all + private func test5(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + return [ + .init( + scenario: self, + intent: .main, + variant: .tinted, + iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), + text: "Text", + isLongText: false, + modes: Constants.Modes.default, + sizes: Constants.Sizes.all + ) + ] + } +} + +// MARK: - Private Extensions + +private extension ImageEither { + + static func mock(isSwiftUIComponent: Bool) -> Self { + return isSwiftUIComponent ? .right(Image.mock) : .left(UIImage.mock) + } +} + +private extension Image { + static let mock = Image(systemName: "person.2.circle.fill") +} + +private extension UIImage { + static var mock = UIImage(systemName: "person.2.circle.fill") ?? UIImage() +} diff --git a/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift b/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift deleted file mode 100644 index 76cf94772..000000000 --- a/core/Sources/Components/Tag/View/Common/TagSutSnapshotStrategy.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// TagSutSnapshotStrategy.swift -// SparkCoreSnapshotTests -// -// Created by robin.lemaire on 10/10/2023. -// Copyright © 2023 Adevinta. All rights reserved. -// - -enum TagSutSnapshotStrategy: String, CaseIterable { - case test1 - case test2 - case test3 - case test4 - case test5 - case test6 -} diff --git a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift index ba0fbe68e..1fff56846 100644 --- a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift @@ -7,24 +7,19 @@ // import UIKit -import SwiftUI @testable import SparkCore struct TagSutSnapshotTests { - // MARK: - Type Alias - - typealias Constants = ComponentSnapshotTestConstants - // MARK: - Properties - let strategy: TagSutSnapshotStrategy + let scenario: TagScenarioSnapshotTests let intent: TagIntent let variant: TagVariant let iconImage: ImageEither? let text: String? - private let isLongText: Bool + let isLongText: Bool var width: CGFloat? { return self.isLongText ? 100 : nil } @@ -35,242 +30,11 @@ struct TagSutSnapshotTests { func testName() -> String { return [ - "\(self.strategy.rawValue)", + "\(self.scenario.rawValue)", "\(self.intent)", "\(self.variant)", self.iconImage != nil ? "withImage" : "withoutImage", - self.isLongText ? "longText" : "normalText" + self.text != nil ? (self.isLongText ? "longText" : "normalText") : "withoutText" ].joined(separator: "-") } - - // MARK: - Testing Strategy - - static func test( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - switch strategy { - case .test1: - return self.test1( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - case .test2: - return self.test2( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - case .test3: - return self.test3( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - case .test4: - return self.test4( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - case .test5: - return self.test5( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - case .test6: - return self.test6( - for: strategy, - isSwiftUIComponent: isSwiftUIComponent - ) - } - } - - /// Test 1 - /// - /// Description: To test all intents - /// - /// Content: - /// - intents: all - /// - variant : tinted - /// - content : icon + text - /// - theme : light / dark - /// - size : default - static func test1( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - let intents = TagIntent.allCases - - return intents.map { - .init( - strategy: strategy, - intent: $0, - variant: .tinted, - iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), - text: "Text", - isLongText: false, - modes: Constants.modes, - sizes: [Constants.size] - ) - } - } - - /// Test 2 - /// - /// Description: To test all variants - /// - /// Content: - /// - intent: main - /// - variant : all - /// - content : text only - /// - theme : light / dark - /// - size : default - static func test2( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - let variants = TagVariant.allCases - - return variants.map { - .init( - strategy: strategy, - intent: .main, - variant: $0, - iconImage: nil, - text: "Text", - isLongText: false, - modes: Constants.modes, - sizes: [Constants.size] - ) - } - } - - /// Test 3 - /// - /// Description: To test all color for filled variant - /// - /// Content: - /// - intents: all - /// - variant : filled - /// - content : icon + text - /// - theme : default - /// - size : default - static func test3( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - let intents = TagIntent.allCases - - return intents.map { - .init( - strategy: strategy, - intent: $0, - variant: .filled, - iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), - text: "Text", - isLongText: false, - modes: [Constants.mode], - sizes: [Constants.size] - ) - } - } - - /// Test 4 - /// - /// Description: To test content resilience - /// - /// Content: - /// - intent: neutral - /// - variant : tinted - /// - content : icon only - /// - theme : default - /// - size : default - static func test4( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - return [ - .init( - strategy: strategy, - intent: .neutral, - variant: .tinted, - iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), - text: nil, - isLongText: false, - modes: [Constants.mode], - sizes: [Constants.size] - ) - ] - } - - /// Test 5 - /// - /// Description: To test content resilience - /// - /// Content: - /// - intent: neutral - /// - variant : tinted - /// - content : long text - /// - theme : default - /// - size : default - static func test5( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - return [ - .init( - strategy: strategy, - intent: .neutral, - variant: .tinted, - iconImage: nil, - text: "Very very very long long text", - isLongText: true, - modes: [Constants.mode], - sizes: [Constants.size] - ) - ] - } - - /// Test 6 - /// - /// Description: To test a11y sizes - /// - /// Content: - /// - intent: main - /// - variant : tinted - /// - content : icon + text - /// - theme : default - /// - size : xs, medium, xxxl - static func test6( - for strategy: TagSutSnapshotStrategy, - isSwiftUIComponent: Bool - ) -> [TagSutSnapshotTests] { - return [ - .init( - strategy: strategy, - intent: .main, - variant: .tinted, - iconImage: .mock(isSwiftUIComponent: isSwiftUIComponent), - text: "Text", - isLongText: false, - modes: [Constants.mode], - sizes: Constants.sizes - ) - ] - } -} - -// MARK: - Private Extensions - -private extension ImageEither { - - static func mock(isSwiftUIComponent: Bool) -> Self { - return isSwiftUIComponent ? .right(Image.mock) : .left(UIImage.mock) - } -} - -private extension Image { - static let mock = Image(systemName: "person.2.circle.fill") -} - -private extension UIImage { - static var mock = UIImage(systemName: "person.2.circle.fill") ?? UIImage() } diff --git a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift index 8c0f3fb30..487ccd9e5 100644 --- a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift @@ -21,13 +21,10 @@ final class TagViewSnapshotTests: SwiftUIComponentSnapshotTestCase { // MARK: - Tests func test() { - let strategies = TagSutSnapshotStrategy.allCases + let scenarios = TagScenarioSnapshotTests.allCases - for strategy in strategies { - let suts = TagSutSnapshotTests.test( - for: strategy, - isSwiftUIComponent: true - ) + for scenario in scenarios { + let suts = scenario.sut(isSwiftUIComponent: true) for sut in suts { let view = TagView(theme: self.theme) .intent(sut.intent) diff --git a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift index 8c1a80fd6..3dcb9bce0 100644 --- a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift @@ -20,13 +20,10 @@ final class TagUIViewSnapshotTests: UIKitComponentSnapshotTestCase { // MARK: - Tests func test() { - let strategies = TagSutSnapshotStrategy.allCases + let scenarios = TagScenarioSnapshotTests.allCases - for strategy in strategies { - let suts = TagSutSnapshotTests.test( - for: strategy, - isSwiftUIComponent: false - ) + for scenario in scenarios { + let suts = scenario.sut(isSwiftUIComponent: false) for sut in suts { var view: TagUIView? diff --git a/core/Unit-tests/TestCase/Component/Constants/ComponentSnapshotTestConstants.swift b/core/Unit-tests/TestCase/Component/Constants/ComponentSnapshotTestConstants.swift index 701a3776b..a76ce72cd 100644 --- a/core/Unit-tests/TestCase/Component/Constants/ComponentSnapshotTestConstants.swift +++ b/core/Unit-tests/TestCase/Component/Constants/ComponentSnapshotTestConstants.swift @@ -15,9 +15,13 @@ enum ComponentSnapshotTestConstants { static let imagePrecision: Float = 0.98 static let imagePerceptualPrecision: Float = 0.98 - static let modes: [ComponentSnapshotTestMode] = ComponentSnapshotTestMode.allCases - static let mode: ComponentSnapshotTestMode = .light + enum Modes { + static let all: [ComponentSnapshotTestMode] = [.light, .dark] + static let `default`: [ComponentSnapshotTestMode] = [.light] + } - static let sizes: [UIContentSizeCategory] = [.extraSmall, .medium, .accessibilityExtraExtraExtraLarge] - static let size: UIContentSizeCategory = .medium + enum Sizes { + static let all: [UIContentSizeCategory] = [.extraSmall, .medium, .accessibilityExtraExtraExtraLarge] + static let `default`: [UIContentSizeCategory] = [.medium] + } } diff --git a/core/Unit-tests/TestCase/Component/Enum/ComponentSnapshotTestMode.swift b/core/Unit-tests/TestCase/Component/Enum/ComponentSnapshotTestMode.swift index e1d1233f8..c9d0d7b57 100644 --- a/core/Unit-tests/TestCase/Component/Enum/ComponentSnapshotTestMode.swift +++ b/core/Unit-tests/TestCase/Component/Enum/ComponentSnapshotTestMode.swift @@ -8,7 +8,7 @@ import UIKit -enum ComponentSnapshotTestMode: String, CaseIterable { +enum ComponentSnapshotTestMode: String { case dark case light diff --git a/core/Unit-tests/TestCase/Component/TestCase/SwiftUIComponentSnapshotTestCase.swift b/core/Unit-tests/TestCase/Component/TestCase/SwiftUIComponentSnapshotTestCase.swift index 6e46907bf..24f8421a6 100644 --- a/core/Unit-tests/TestCase/Component/TestCase/SwiftUIComponentSnapshotTestCase.swift +++ b/core/Unit-tests/TestCase/Component/TestCase/SwiftUIComponentSnapshotTestCase.swift @@ -23,8 +23,8 @@ open class SwiftUIComponentSnapshotTestCase: SnapshotTestCase { func assertSnapshot( matching view: @autoclosure () -> some View, named name: String? = nil, - modes: [ComponentSnapshotTestMode] = Constants.modes, - sizes: [UIContentSizeCategory] = Constants.sizes, + modes: [ComponentSnapshotTestMode], + sizes: [UIContentSizeCategory], record recording: Bool = Constants.record, timeout: TimeInterval = Constants.timeout, file: StaticString = #file, @@ -37,7 +37,7 @@ open class SwiftUIComponentSnapshotTestCase: SnapshotTestCase { matching: view().environment( \.sizeCategory, ContentSizeCategory(size) ?? .extraSmall - ), + ).background(Color.gray), as: .image( precision: Constants.imagePrecision, perceptualPrecision: Constants.imagePerceptualPrecision, diff --git a/core/Unit-tests/TestCase/Component/TestCase/UIKitComponentSnapshotTestCase.swift b/core/Unit-tests/TestCase/Component/TestCase/UIKitComponentSnapshotTestCase.swift index 154d73a3c..1420b4e2e 100644 --- a/core/Unit-tests/TestCase/Component/TestCase/UIKitComponentSnapshotTestCase.swift +++ b/core/Unit-tests/TestCase/Component/TestCase/UIKitComponentSnapshotTestCase.swift @@ -23,8 +23,8 @@ open class UIKitComponentSnapshotTestCase: SnapshotTestCase { func assertSnapshot( matching view: @autoclosure () -> some UIView, named name: String? = nil, - modes: [ComponentSnapshotTestMode] = Constants.modes, - sizes: [UIContentSizeCategory] = Constants.sizes, + modes: [ComponentSnapshotTestMode], + sizes: [UIContentSizeCategory], record recording: Bool = Constants.record, delay: TimeInterval = 0, timeout: TimeInterval = Constants.timeout, @@ -47,15 +47,15 @@ open class UIKitComponentSnapshotTestCase: SnapshotTestCase { ) ) ), - named: Helpers.testName( + named: name, + record: recording, + timeout: timeout, + file: file, + testName: Helpers.testName( testName, mode: mode, size: size ), - record: recording, - timeout: timeout, - file: file, - testName: testName, line: line ) } From f896762eac3191bc91ab7246e0ad2c2fee40251e Mon Sep 17 00:00:00 2001 From: Robin Lemaire Date: Fri, 13 Oct 2023 15:32:02 +0200 Subject: [PATCH 5/6] [Tests-1544] Tag snapshot: replace SUT to configuration --- .../Common/TagScenarioSnapshotTests.swift | 14 +++++----- .../Tag/View/Common/TagSutSnapshotTests.swift | 4 +-- .../View/SwiftUI/TagViewSnapshotTests.swift | 20 ++++++------- .../View/UIKit/TagUIViewSnapshotTests.swift | 28 ++++++++++--------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift b/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift index 74d6cc371..3c7c3e89a 100644 --- a/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/Common/TagScenarioSnapshotTests.swift @@ -21,9 +21,9 @@ enum TagScenarioSnapshotTests: String, CaseIterable { typealias Constants = ComponentSnapshotTestConstants - // MARK: - Stubs + // MARK: - Configurations - func sut(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + func configuration(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { switch self { case .test1: return self.test1(isSwiftUIComponent: isSwiftUIComponent) @@ -50,7 +50,7 @@ enum TagScenarioSnapshotTests: String, CaseIterable { /// - content: icon + text /// - mode: all /// - size: default - private func test1(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + private func test1(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { let intents = TagIntent.allCases return intents.map { @@ -77,7 +77,7 @@ enum TagScenarioSnapshotTests: String, CaseIterable { /// - content: text only /// - mode: all /// - size: default - private func test2(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + private func test2(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { let variants = TagVariant.allCases return variants.map { @@ -104,7 +104,7 @@ enum TagScenarioSnapshotTests: String, CaseIterable { /// - content: icon + text /// - mode: default /// - size: default - private func test3(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + private func test3(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { let intents = TagIntent.allCases return intents.map { @@ -131,7 +131,7 @@ enum TagScenarioSnapshotTests: String, CaseIterable { /// - content: all (icon only / long text/ long text + icon) /// - mode: default /// - size: default - private func test4(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + private func test4(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { let contents: [(isIconImage: Bool, isLongText: Bool)] = [ (isIconImage: true, isLongText: false), // Only Icon (isIconImage: false, isLongText: true), // Only long text @@ -162,7 +162,7 @@ enum TagScenarioSnapshotTests: String, CaseIterable { /// - content: icon + text /// - mode: default /// - size: all - private func test5(isSwiftUIComponent: Bool) -> [TagSutSnapshotTests] { + private func test5(isSwiftUIComponent: Bool) -> [TagConfigurationSnapshotTests] { return [ .init( scenario: self, diff --git a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift index 1fff56846..1565ec5de 100644 --- a/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/Common/TagSutSnapshotTests.swift @@ -1,5 +1,5 @@ // -// TagSutSnapshotTests.swift +// TagConfigurationSnapshotTests.swift // SparkCoreTests // // Created by robin.lemaire on 05/05/2023. @@ -9,7 +9,7 @@ import UIKit @testable import SparkCore -struct TagSutSnapshotTests { +struct TagConfigurationSnapshotTests { // MARK: - Properties diff --git a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift index 487ccd9e5..5096ea103 100644 --- a/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/SwiftUI/TagViewSnapshotTests.swift @@ -24,21 +24,21 @@ final class TagViewSnapshotTests: SwiftUIComponentSnapshotTestCase { let scenarios = TagScenarioSnapshotTests.allCases for scenario in scenarios { - let suts = scenario.sut(isSwiftUIComponent: true) - for sut in suts { + let configurations = scenario.configuration(isSwiftUIComponent: true) + for configuration in configurations { let view = TagView(theme: self.theme) - .intent(sut.intent) - .variant(sut.variant) - .iconImage(sut.iconImage?.rightValue) - .text(sut.text) - .frame(width: sut.width) + .intent(configuration.intent) + .variant(configuration.variant) + .iconImage(configuration.iconImage?.rightValue) + .text(configuration.text) + .frame(width: configuration.width) .fixedSize() self.assertSnapshot( matching: view, - modes: sut.modes, - sizes: sut.sizes, - testName: sut.testName() + modes: configuration.modes, + sizes: configuration.sizes, + testName: configuration.testName() ) } } diff --git a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift index 3dcb9bce0..8fe9251d5 100644 --- a/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift +++ b/core/Sources/Components/Tag/View/UIKit/TagUIViewSnapshotTests.swift @@ -9,6 +9,8 @@ import XCTest import SnapshotTesting +// TODO: rename SUT to Configuration + @testable import SparkCore final class TagUIViewSnapshotTests: UIKitComponentSnapshotTestCase { @@ -23,33 +25,33 @@ final class TagUIViewSnapshotTests: UIKitComponentSnapshotTestCase { let scenarios = TagScenarioSnapshotTests.allCases for scenario in scenarios { - let suts = scenario.sut(isSwiftUIComponent: false) - for sut in suts { + let configurations = scenario.configuration(isSwiftUIComponent: false) + for configuration in configurations { var view: TagUIView? - switch (sut.iconImage, sut.text) { + switch (configuration.iconImage, configuration.text) { case (nil, nil): XCTFail("Icon or text should be set") case (let iconImage?, nil): view = TagUIView( theme: self.theme, - intent: sut.intent, - variant: sut.variant, + intent: configuration.intent, + variant: configuration.variant, iconImage: iconImage.leftValue ) case (nil, let text?): view = TagUIView( theme: self.theme, - intent: sut.intent, - variant: sut.variant, + intent: configuration.intent, + variant: configuration.variant, text: text ) case let (iconImage?, text?): view = TagUIView( theme: self.theme, - intent: sut.intent, - variant: sut.variant, + intent: configuration.intent, + variant: configuration.variant, iconImage: iconImage.leftValue, text: text ) @@ -60,15 +62,15 @@ final class TagUIViewSnapshotTests: UIKitComponentSnapshotTestCase { } view.translatesAutoresizingMaskIntoConstraints = false - if let width = sut.width { + if let width = configuration.width { view.widthAnchor.constraint(equalToConstant: width).isActive = true } self.assertSnapshot( matching: view, - modes: sut.modes, - sizes: sut.sizes, - testName: sut.testName() + modes: configuration.modes, + sizes: configuration.sizes, + testName: configuration.testName() ) } } From b7c3606f62cf413d2fb4120ea1d135ea515b9aff Mon Sep 17 00:00:00 2001 From: Robin Lemaire Date: Fri, 13 Oct 2023 17:48:14 +0200 Subject: [PATCH 6/6] [Component-518] Tag UIKit Height: Add scalability --- .../Components/Tag/View/UIKit/TagUIView.swift | 103 +++++++++++------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/core/Sources/Components/Tag/View/UIKit/TagUIView.swift b/core/Sources/Components/Tag/View/UIKit/TagUIView.swift index 5e2696741..4fbf904f8 100644 --- a/core/Sources/Components/Tag/View/UIKit/TagUIView.swift +++ b/core/Sources/Components/Tag/View/UIKit/TagUIView.swift @@ -18,15 +18,30 @@ public final class TagUIView: UIView { // MARK: - Components private lazy var contentStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: - [ - self.iconImageView, - self.textLabel - ]) + let stackView = UIStackView( + arrangedSubviews: + [ + self.iconStackView, + self.textLabel + ]) stackView.axis = .horizontal return stackView }() + private lazy var iconStackView: UIStackView = { + let stackView = UIStackView( + arrangedSubviews: + [ + self.topIconSpaceView, + self.iconImageView, + self.bottomIconSpaceView + ]) + stackView.axis = .vertical + return stackView + }() + + private let topIconSpaceView = UIView() + private var iconImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit @@ -38,6 +53,8 @@ public final class TagUIView: UIView { return imageView }() + private let bottomIconSpaceView = UIView() + private var textLabel: UILabel = { let label = UILabel() label.numberOfLines = 1 @@ -99,13 +116,13 @@ public final class TagUIView: UIView { private var heightConstraint: NSLayoutConstraint? private var contentStackViewLeadingConstraint: NSLayoutConstraint? - private var contentStackViewTopConstraint: NSLayoutConstraint? private var contentStackViewTrailingConstraint: NSLayoutConstraint? - private var contentStackViewBottomConstraint: NSLayoutConstraint? - private var height: CGFloat = TagConstants.height - @ScaledUIMetric private var smallSpacing: CGFloat = 0 - @ScaledUIMetric private var mediumSpacing: CGFloat = 0 + private var topIconSpaceViewTopConstraint: NSLayoutConstraint? + + @ScaledUIMetric private var height: CGFloat = TagConstants.height + @ScaledUIMetric private var iconVerticalSpacing: CGFloat = 0 + @ScaledUIMetric private var contentHorizontalSpacing: CGFloat = 0 private var _colors: TagColors? { didSet { @@ -241,10 +258,10 @@ public final class TagUIView: UIView { private func reloadUIFromTheme() { // Spacing - self.smallSpacing = self.theme.layout.spacing.small - self._smallSpacing.update(traitCollection: self.traitCollection) - self.mediumSpacing = self.theme.layout.spacing.medium - self._mediumSpacing.update(traitCollection: self.traitCollection) + self.iconVerticalSpacing = self.theme.layout.spacing.small + self._iconVerticalSpacing.update(traitCollection: self.traitCollection) + self.contentHorizontalSpacing = self.theme.layout.spacing.medium + self._contentHorizontalSpacing.update(traitCollection: self.traitCollection) // View self.setBorderWidth(self.theme.border.width.small) @@ -278,30 +295,30 @@ public final class TagUIView: UIView { } private func reloadUIFromSpacing() { - self.reloadUIFromSmallSpacing() - self.reloadUIFromMediumSpacing() + self.reloadUIFromIconVerticalSpacing() + self.reloadUIFromContentHorizontalSpacing() } - private func reloadUIFromSmallSpacing() { + private func reloadUIFromIconVerticalSpacing() { // Reload spacing only if value changed - let smallSpacing = self._smallSpacing.wrappedValue - if smallSpacing != self.contentStackViewTopConstraint?.constant { + let iconVerticalSpacing = self._iconVerticalSpacing.wrappedValue + if iconVerticalSpacing != self.topIconSpaceViewTopConstraint?.constant { // Subviews - self.contentStackView.spacing = smallSpacing + self.contentStackView.spacing = iconVerticalSpacing // Constraint - self.contentStackViewTopConstraint?.constant = smallSpacing - self.contentStackViewBottomConstraint?.constant = -smallSpacing - self.contentStackView.layoutIfNeeded() + self.topIconSpaceViewTopConstraint?.constant = iconVerticalSpacing + self.topIconSpaceViewTopConstraint?.isActive = true + self.updateConstraintsIfNeeded() } } - private func reloadUIFromMediumSpacing() { + private func reloadUIFromContentHorizontalSpacing() { // Reload spacing only if value changed - let mediumSpacing = self._mediumSpacing.wrappedValue - if mediumSpacing != self.contentStackViewLeadingConstraint?.constant { - self.contentStackViewLeadingConstraint?.constant = mediumSpacing - self.contentStackViewTrailingConstraint?.constant = -mediumSpacing + let contentHorizontalSpacing = self._contentHorizontalSpacing.wrappedValue + if contentHorizontalSpacing != self.contentStackViewLeadingConstraint?.constant { + self.contentStackViewLeadingConstraint?.constant = contentHorizontalSpacing + self.contentStackViewTrailingConstraint?.constant = -contentHorizontalSpacing self.contentStackView.layoutIfNeeded() } } @@ -311,6 +328,7 @@ public final class TagUIView: UIView { private func setupConstraints() { self.setupViewConstraints() self.setupContentStackViewConstraints() + self.setupIconSpaceViewsContraints() self.setupIconImageViewConstraints() } @@ -325,19 +343,27 @@ public final class TagUIView: UIView { self.contentStackView.translatesAutoresizingMaskIntoConstraints = false self.contentStackViewLeadingConstraint = self.contentStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor) - self.contentStackViewTopConstraint = self.contentStackView.topAnchor.constraint(equalTo: self.topAnchor) + self.contentStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true self.contentStackViewTrailingConstraint = self.contentStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor) - self.contentStackViewBottomConstraint = self.contentStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor) + self.contentStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true self.contentStackViewLeadingConstraint?.isActive = true - self.contentStackViewTopConstraint?.isActive = true self.contentStackViewTrailingConstraint?.isActive = true - self.contentStackViewBottomConstraint?.isActive = true + } + + private func setupIconSpaceViewsContraints() { + self.topIconSpaceView.translatesAutoresizingMaskIntoConstraints = false + self.topIconSpaceViewTopConstraint = self.topIconSpaceView.heightAnchor.constraint( + equalToConstant: self.iconVerticalSpacing + ) + + self.bottomIconSpaceView.translatesAutoresizingMaskIntoConstraints = false + self.bottomIconSpaceView.heightAnchor.constraint(equalTo: self.topIconSpaceView.heightAnchor).isActive = true } private func setupIconImageViewConstraints() { self.iconImageView.translatesAutoresizingMaskIntoConstraints = false - self.iconImageView.widthAnchor.constraint(equalTo: self.contentStackView.heightAnchor).isActive = true + self.iconImageView.widthAnchor.constraint(equalTo: self.iconImageView.heightAnchor).isActive = true } // MARK: - Getter @@ -361,13 +387,10 @@ public final class TagUIView: UIView { } // ** - // Update content size ? - self.height = UIFontMetrics.default.scaledValue( - for: TagConstants.height, - compatibleWith: self.traitCollection - ) - self._smallSpacing.update(traitCollection: self.traitCollection) - self._mediumSpacing.update(traitCollection: self.traitCollection) + // Update content size + self._height.update(traitCollection: self.traitCollection) + self._iconVerticalSpacing.update(traitCollection: self.traitCollection) + self._contentHorizontalSpacing.update(traitCollection: self.traitCollection) self.reloadUIFromSize() // ** }