pointfreeco / swift-snapshot-testing

📸 Delightful Swift snapshot testing.
https://www.pointfree.co/episodes/ep41-a-tour-of-snapshot-testing
MIT License
3.75k stars 577 forks source link

Generic parameter 'Format' could not be inferred #161

Closed yaakovgamliel closed 5 years ago

yaakovgamliel commented 5 years ago

Hi, I'm trying the following test in my project:

//
//  ManualEntriesCustomerFunnel.swift
//

import XCTest
import SnapshotTesting
@testable import pos

class AlertControllerMessages: XCTestCase {

    func testAlertController() {
        let vc = GeneralAlert.errorWith(body: "Hi there", buttonText: "Cancel", cancelAction: nil)
        assertSnapshot(matching: vc, as: .image)
    }

}

The compiler its giving me an error Generic parameter 'Format' could not be inferred for the .image parameter. Do I need to implement something else?

mbrandonw commented 5 years ago

Most likely this means that the .image snapshot strategy does not work with the value you are trying to match against. In this case, is vc really a UIViewController subclass? Could you share some more code with GeneralAlert's definitions?

yaakovgamliel commented 5 years ago

This is the implementation of my controller

import UIKit
import Core

typealias GeneralAlert = GeneralAlertViewController

class GeneralAlertViewController: UIViewController {

    var cancelAction: (() -> Void)?
    var alertTitle: String? { didSet { titleLabel?.text = alertTitle } }
    var body: String? { didSet { bodyLabel?.text = body } }
    var buttonText: String? { didSet { OKButton?.setTitle(buttonText, for: .normal) } }

    @IBOutlet private weak var titleLabel:UILabel!
    @IBOutlet private weak var bodyLabel:UILabel!
    @IBOutlet private weak var OKButton:UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        titleLabel.text = alertTitle
        bodyLabel.text = body
        OKButton.setTitle(buttonText, for: .normal)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        subscribeTimeoutHandler()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        unsubscribeTimeoutHandler()
    }

    override func timeoutNotificationReceived() {
        dismiss(animated: true, completion: nil)
    }

    func present(with vc: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            vc.present(self, animated: animated, completion: completion)
        }
    }

    static func errorWith(body:String?, buttonText: String = Messages.ok(), cancelAction:(() -> Void)? = nil) -> GeneralAlertViewController? {
        return with(title: Messages.defaultErrorTitle(), body: body, buttonText: buttonText, cancelAction: cancelAction)
    }

    static func with(title:String?, body:String?, buttonText: String = Messages.ok(), cancelAction:(() -> Void)? = nil) -> GeneralAlertViewController? {
        let identifier = String(describing: GeneralAlertViewController.self)
        let vc = UIStoryboard.generalAlert().instantiateViewController(withIdentifier: identifier)
        guard let alert = vc as? GeneralAlertViewController else {
            return nil
        }

        alert.alertTitle = title
        alert.body = body
        alert.cancelAction = cancelAction
        alert.buttonText = buttonText
        alert.modalPresentationStyle = .formSheet
        alert.modalTransitionStyle = .crossDissolve
        alert.preferredContentSize = CGSize(width: 600, height: 200)

        return alert
    }

    @IBAction func okButtonTouchUpInside(sender: UIButton) {
        guard let action = cancelAction else {
            dismiss(animated: true, completion: nil)
            return
        }

        dismiss(animated: true) { action() }
    }

    // MARK: - Helper functions

    @objc func forcedDismiss() {
        if self.view.window == nil { return }
        dismiss(animated: true, completion: nil)
    }
}
mbrandonw commented 5 years ago

Ah I see, the errorWith static method returns an optional view controller. You could force unwrap the controller in your test and then it should work:

-assertSnapshot(matching: vc, as: .image)
+assertSnapshot(matching: vc!, as: .image)

Too bad the Swift error messaging was really bad.

yaakovgamliel commented 5 years ago

Unwrapping the controller solved the issue :), thanks!!

mbrandonw commented 5 years ago

Great! Glad to hear. Will close out this issue.

stephencelis commented 5 years ago

(Filed a bug report with Swift here: https://bugs.swift.org/browse/SR-9598)

jeannustre commented 1 year ago

Getting the same error in 1.11.0 with a non-optional UIViewController.

Explicitly specifying the format works :

assertSnapshots(matching: vc, as: [Snapshotting<UIViewController, UIImage>.image])

Maybe a compiler cache issue since all my other tests compile fine using :

assertSnapshots(matching: vc, as: .image)