Skip to content

Commit

Permalink
Merge pull request #6 from alexey1312/feature/addHEICformat
Browse files Browse the repository at this point in the history
Add HEIC format
  • Loading branch information
Sherlouk authored Jan 18, 2022
2 parents 0fa401e + 9e60ac2 commit 8e5e510
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 42 deletions.
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
{
"object": {
"pins": [
{
"package": "SnapshotTestingHEIC",
"repositoryURL": "https://github.com/alexey1312/SnapshotTestingHEIC.git",
"state": {
"branch": null,
"revision": "a8e6abbdde6969f0a8fb80f1ca62640824905387",
"version": "1.0.1"
}
},
{
"package": "SnapshotTesting",
"repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git",
Expand Down
7 changes: 6 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ let package = Package(
],
dependencies: [
.package(name: "SnapshotTesting",
url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.0"),
url: "https://github.com/pointfreeco/swift-snapshot-testing.git",
from: "1.8.0"),
.package(name: "SnapshotTestingHEIC",
url: "https://github.com/alexey1312/SnapshotTestingHEIC.git",
from: "1.0.0"),
],
targets: [
.target(
name: "SnapshotTestingStitch",
dependencies: [
.product(name: "SnapshotTesting", package: "SnapshotTesting"),
.product(name: "SnapshotTestingHEIC", package: "SnapshotTestingHEIC"),
]
),

Expand Down
7 changes: 7 additions & 0 deletions Sources/SnapshotTestingStitch/ImageFormat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation
import SnapshotTestingHEIC

public enum ImageFormat {
case png
case heic(_ compression: CompressionQuality = .lossless)
}
115 changes: 74 additions & 41 deletions Sources/SnapshotTestingStitch/Snapshotting.swift
Original file line number Diff line number Diff line change
@@ -1,57 +1,89 @@
import UIKit
import SnapshotTesting
import SnapshotTestingHEIC

extension Snapshotting where Format == UIImage {

public extension Snapshotting where Format == UIImage {
/// Stitches multiple visual snapshot strategies into a single image asset.
///
/// - Parameters:
/// - strategies: The unnamed tasks which should be carried out, in the order that they should be displayed.
/// Any strategy can be used as long as the output format is UIImage.
/// - style: The style configuration which allows for you to customise the appearance of the output image, including but not limited to the item
/// spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison in order for the test to successfully pass.
public static func stitch(
/// - tasks: The unnamed tasks which should be carried out, in the order that they should be displayed.
/// Any strategy can be used as long as the output format is UIImage.
/// - style: The style configuration which allows for you to customise the appearance of the output image,
/// including but not limited to the item spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison
/// in order for the test to successfully pass.
/// - format: The desired image format to use when writing to an image destination.
/// It would default to PNG for backwards compatibility.
static func stitch(
strategies tasks: [Snapshotting<Value, Format>],
style: StitchStyle = .init(),
precision: Float = 1
precision: Float = 1,
format: ImageFormat = .png
) -> Snapshotting {
// Default to an empty string, if they choose not to provide one.
stitch(strategies: tasks.map { .init(name: nil, strategy: $0, configure: nil) }, style: style, precision: precision)
stitch(
strategies: tasks.map { .init(name: nil, strategy: $0, configure: nil) },
style: style,
precision: precision,
format: format
)
}

/// Stitches multiple visual snapshot strategies into a single image asset.
///
/// - Parameters:
/// - strategies: The named tasks which should be carried out, in the order that they should be displayed. Titles will be displayed above their
/// respectful image, allowing for easier identification. Any strategy can be used as long as the output format is UIImage.
/// - style: The style configuration which allows for you to customise the appearance of the output image, including but not limited to the item
/// spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison in order for the test to successfully pass.
public static func stitch(
/// - tasks: The named tasks which should be carried out, in the order that they should be displayed.
/// Titles will be displayed above their respectful image, allowing for easier identification.
/// Any strategy can be used as long as the output format is UIImage.
/// - style: The style configuration which allows for you to customise the appearance of the output image,
/// including but not limited to the item spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison
/// in order for the test to successfully pass.
/// - format: The desired image format to use when writing to an image destination.
/// It would default to PNG for backwards compatibility.
static func stitch(
strategies tasks: [(name: String, strategy: Snapshotting<Value, Format>)],
style: StitchStyle = .init(),
precision: Float = 1
precision: Float = 1,
format: ImageFormat = .png
) -> Snapshotting {
stitch(strategies: tasks.map { .init(name: $0.name, strategy: $0.strategy, configure: nil) }, style: style, precision: precision)
stitch(
strategies: tasks.map { .init(name: $0.name, strategy: $0.strategy, configure: nil) },
style: style,
precision: precision,
format: format
)
}

/// Stitches multiple visual snapshot strategies into a single image asset.
///
/// - Parameters:
/// - strategies: The tasks which should be carried out, in the order that they should be displayed. Tasks can include a title which will be displayed above
/// their respectful image, allowing for easier identification. Any strategy can be used as long as the output format is UIImage. Tasks can
/// can also contain a configuration block which allows for you to modify the value just before it's snapshot is taken.
/// - style: The style configuration which allows for you to customise the appearance of the output image, including but not limited to the item
/// spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison in order for the test to successfully pass.
public static func stitch(
/// - tasks: The tasks which should be carried out, in the order that they should be displayed.
/// Tasks can include a title which will be displayed above their respectful image,
/// allowing for easier identification. Any strategy can be used as long as the output format is UIImage.
/// Tasks can can also contain a configuration block which allows for you to modify
/// the value just before it's snapshot is taken.
/// - style: The style configuration which allows for you to customise the appearance of the output image,
/// including but not limited to the item spacing, and optional image borders.
/// - precision: The percentage of pixels that must match in the final comparison
/// in order for the test to successfully pass.
/// - format: The desired image format to use when writing to an image destination.
/// It would default to PNG for backwards compatibility.
static func stitch(
strategies tasks: [StitchTask<Value, Format>],
style: StitchStyle = .init(),
precision: Float = 1
precision: Float = 1,
format: ImageFormat = .png
) -> Snapshotting {
let internalStrategy: Snapshotting<UIViewController, UIImage> = .image(precision: precision)

let internalStrategy: Snapshotting<UIViewController, UIImage>

switch format {
case .png:
internalStrategy = .image(precision: precision)
case .heic(let compressionQuality):
internalStrategy = .imageHEIC(precision: precision, compressionQuality: compressionQuality)
}

return Snapshotting(
pathExtension: internalStrategy.pathExtension,
diffing: internalStrategy.diffing
Expand All @@ -64,44 +96,45 @@ extension Snapshotting where Format == UIImage {
callback(UIImage())
return
}

// Create a dispatch group to keep track of the remaining tasks
let dispatchGroup = DispatchGroup()

// Create an array to store the final outputs to be stitched
var values = [(index: Int, title: String?, output: UIImage)]()

// Loop over each of the user-provided strategies, snapshot them,
// store the output, and update the dispatch group.
tasks.enumerated().forEach { index, task in
dispatchGroup.enter()

var mutableValue = value
task.configure?(&mutableValue)

task.strategy.snapshot(mutableValue).run { output in
values.append((index, task.name, output))
dispatchGroup.leave()
}
}

// Once all strategies have been completed...
dispatchGroup.notify(queue: .main) {
// Sort values based on input order
let sortedValues: [(String?, UIImage)] = values
.sorted(by: { lhs, rhs in lhs.index < rhs.index })
.map { result in (result.title, result.output) }

// Check to ensure all tasks have been returned
assert(sortedValues.count == tasks.count,
"Inconsistant number of outputted values in comparison to inputted strategies")

assert(
sortedValues.count == tasks.count,
"Inconsistant number of outputted values in comparison to inputted strategies"
)

// Stitch them together, and callback to the snapshot testing library.
let image = ImageStitcher(inputs: sortedValues).stitch(style: style)
callback(image)
}
}
}
}

}
130 changes: 130 additions & 0 deletions Tests/SnapshotTestingStitchTests/SnapshotTestingStitchHEIC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import XCTest
import SnapshotTesting
import SnapshotTestingHEIC
@testable import SnapshotTestingStitch

final class SnapshotTestingStitchHEICTests: XCTestCase {

let isRecording: Bool = false

func test_withTitles() {
assertSnapshot(
matching: createTestViewController(),
as: .stitch(
strategies: [
("iPhone 8", .image(on: .iPhone8)),
("iPhone 8 Plus", .image(on: .iPhone8Plus)),
],
format: .heic()
),
record: isRecording
)
}

func test_withoutTitles() {
assertSnapshot(
matching: createTestViewController(),
as: .stitch(
strategies: [
.image(on: .iPhone8),
.image(on: .iPhone8Plus),
],
format: .heic()
),
record: isRecording
)
}

func test_withoutBorder() {
assertSnapshot(
matching: createTestViewController(),
as: .stitch(
strategies: [
("iPhone 8", .image(on: .iPhone8)),
("iPhone 8 Plus", .image(on: .iPhone8Plus)),
],
style: .init(borderWidth: 0),
format: .heic()
),
record: isRecording
)
}

func test_withManyDevices() {
assertSnapshot(
matching: createTestViewController(),
as: .stitch(
strategies: [
("iPhone 8", .image(on: .iPhone8)),
("iPhone 8 Plus", .image(on: .iPhone8Plus)),
("iPhone X", .image(on: .iPhoneX)),
("iPhone SE", .image(on: .iPhoneSe)),
("iPhone Xr", .image(on: .iPhoneXr)),
("iPhone Xs Max", .image(on: .iPhoneXsMax)),
("iPhone Xs Max (Landscape)", .image(on: .iPhoneXsMax(.landscape))),
],
format: .heic()
),
record: isRecording
)
}

func test_withView() {
assertSnapshot(
matching: createTestView(),
as: .stitch(
strategies: [
("100x", .image(size: CGSize(width: 100, height: 100))),
("250x", .image(size: CGSize(width: 250, height: 250))),
],
format: .heic()
),
record: isRecording
)
}

func test_withNoStrategies() {
// You actually get a compiler warning for ambiguity by default, so you have to go through some loops to pass
// literally nothing through.
let tasks: [Snapshotting<UIView, UIImage>] = []

assertSnapshot(
matching: createTestView(),
as: .stitch(strategies: tasks, format: .heic()),
record: isRecording
)
}

func test_withConfigure() {
assertSnapshot(
matching: createTestViewController(),
as: .stitch(strategies: [
.init(name: "Green", strategy: .image, configure: { $0.view.backgroundColor = .green }),
.init(name: "Pink", strategy: .image, configure: { $0.view.backgroundColor = .systemPink }),
// The input value is being manipulated, which means if you don't reconfigure it then it will be the
// same as the previous test.
.init(name: "Pink (No Configure)", strategy: .image, configure: nil),
],
format: .heic()
),
record: isRecording
)
}

// MARK: - Helpers

func createTestViewController() -> UIViewController {
let viewController = UIViewController()
viewController.view.backgroundColor = .blue

return viewController
}

func createTestView() -> UIView {
let view = UIView(frame: .zero)
view.backgroundColor = .green

return view
}

}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 8e5e510

Please sign in to comment.