diff --git a/README.md b/README.md index 096688e..25820ab 100644 --- a/README.md +++ b/README.md @@ -39,27 +39,29 @@ A danger-swift plug-in to manage/post danger checking results with markdown styl ## Usage -- First of all create a result data structure with `CheckResult` initializer +Basically just use `.shoki` property from a `DangerDSL` instance to access all features provided by DangerSwiftShoki + +Examples below assume you have initialized a `danger` using `Danger()` in your `Dangerfile.swift` + +- First of all create a report data structure with `makeInitialReport` method ```swift - var checkResult = CheckResult(title: "My Check") + var report = danger.shoki.makeInitialReport(title: "My Report") ``` -- Then you can perform any check with `check` method, by returning your check result in the trailing `execution` closure +- Then you can perform any checks with `check` method, by returning your check result in the trailing `execution` closure ```swift - checkResult.check("Test Result Check") { + danger.shoki.check("Test Result Check", into: &report) { if testPassed { return .good } else { if isAcceptable { - warn("Encouraged to make a change but OK at this time") - return .acceptable + return .acceptable(warningMessage: "Encouraged to make a change but OK at this time") } else { - fail("Must fix") - return .rejected + return .rejected(failureMessage: "Must fix") } } } @@ -68,20 +70,20 @@ A danger-swift plug-in to manage/post danger checking results with markdown styl - You can also ask reviewers not to forget to do some manual checks with `askReviewer` method if needed ```swift - checkResult.askReviewer(to: "Check whether commit messages are correctly formatted or not") + danger.shoki.askReviewer(to: "Check whether commit messages are correctly formatted or not", into: $report) ``` -- At last post the whole check result with `shoki.report` method which is available for `DangerDSL` instances +- At last post the whole check result with `report` method ```swift - danger.shoki.report(checkResult) // Assume you have initialized `danger` by code like `let danger = Danger()` + danger.shoki.report(report) ``` ## Preview Code above will make danger producing markdown messages like below -> ## My Check +> ## My Report > > Checking Item | Result > | ---| --- | diff --git a/Sources/DangerSwiftShoki/CheckResult.swift b/Sources/DangerSwiftShoki/CheckResult.swift deleted file mode 100644 index d53f1bb..0000000 --- a/Sources/DangerSwiftShoki/CheckResult.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// CheckResult.swift -// -// -// Created by 史 翔新 on 2020/07/11. -// - -public struct CheckResult { - - public enum Result { - - case good - case acceptable - case rejected - - var markdownSymbol: String { - switch self { - case .good: - return ":tada:" - - case .acceptable: - return ":thinking:" - - case .rejected: - return ":no_good:" - } - } - - } - - typealias Message = (content: String, result: Result) - - public let title: String - - private var messages: [Message] = [] - - private var todos: [String] = [] - - public var warningsCount: Int { - messages.filter({ $0.result == .acceptable }).count - } - - public var errorsCount: Int { - messages.filter({ $0.result == .rejected }).count - } - - public init(title: String) { - self.title = title - } - - public mutating func askReviewer(to taskToDo: String) { - - todos.append(taskToDo) - - } - - public mutating func check(_ item: String, execution: () -> Result) { - - let result = execution() - messages.append((item, result)) - - } - - public var markdownTitle: String { - - "## " + title - - } - - public var markdownMessage: String { - - guard !messages.isEmpty else { - return "" - } - - let chartHeader = """ - Checking Item | Result - | ---| --- | - - """ - let chartContent = messages.map { - "\($0.content) | \($0.result.markdownSymbol)" - } .joined(separator: "\n") - - return chartHeader + chartContent - - } - - public var markdownTodos: String { - - guard !todos.isEmpty else { - return "" - } - - let todoContent = todos.map { - "- [ ] \($0)" - } - - return todoContent.joined(separator: "\n") - - } - -} diff --git a/Sources/DangerSwiftShoki/DangerDSL+.swift b/Sources/DangerSwiftShoki/DangerDSL+.swift index 6c174f3..870488f 100644 --- a/Sources/DangerSwiftShoki/DangerDSL+.swift +++ b/Sources/DangerSwiftShoki/DangerDSL+.swift @@ -10,8 +10,12 @@ import Danger extension DangerDSL { public var shoki: Shoki { - return .init(markdownExecutor: { markdown($0) }, - messageExecutor: { message($0) }) + return .init( + markdownExecutor: { markdown($0) }, + messageExecutor: { message($0) }, + warningExecutor: { warn($0) }, + failureExecutor: { fail($0) } + ) } } diff --git a/Sources/DangerSwiftShoki/MarkdownConfiguration.swift b/Sources/DangerSwiftShoki/MarkdownConfiguration.swift new file mode 100644 index 0000000..9b779d2 --- /dev/null +++ b/Sources/DangerSwiftShoki/MarkdownConfiguration.swift @@ -0,0 +1,113 @@ +// +// MarkdownConfiguration.swift +// +// +// Created by 史 翔新 on 2021/12/02. +// + +public struct MarkdownConfiguration { + + var titleMarkdownFormatter: (String) -> String + var messageMarkdownFormatter: ([Report.CheckItem]) -> String + var warningMarkdownFormatter: (Report.WarningMessage) -> String + var failureMarkdownFormatter: (Report.FailureMessage) -> String + var todosMarkdownFormatter: ([String]) -> String + var congratulationsMessage: String + + public static func defaultTitleMarkdown(title: String) -> String { + + "## " + title + + } + + public static func defaultMessageMarkdown(checkItems: [Report.CheckItem]) -> String { + + guard !checkItems.isEmpty else { + return "" + } + + let chartHeader = """ + Checking Item | Result + | ---| --- | + + """ + let chartContent = checkItems.map { + "\($0.title) | \($0.result.markdownSymbol)" + } .joined(separator: "\n") + + return chartHeader + chartContent + + } + + public static func defaultWarningMarkdown(warning: Report.WarningMessage) -> String { + + let messageSuffix = warning.message.map { ": \($0)" } ?? " has a warning." + return warning.title + messageSuffix + + } + + public static func defaultFailureMarkdown(failure: Report.FailureMessage) -> String { + + let messageSuffix = failure.message.map { ": \($0)" } ?? " failed." + return failure.title + messageSuffix + + } + + public static func defaultTodosMarkdown(todos: [String]) -> String { + + guard !todos.isEmpty else { + return "" + } + + let todoContent = todos.map { + "- [ ] \($0)" + } + + return todoContent.joined(separator: "\n") + + } + + public static var defaultCongratulationsMessage: String { + + "Good Job :white_flower:" + + } + + public init( + titleMarkdownFormatter: @escaping (String) -> String = defaultTitleMarkdown(title:), + messageMarkdownFormatter: @escaping ([Report.CheckItem]) -> String = defaultMessageMarkdown(checkItems:), + warningMarkdownFormatter: @escaping (Report.WarningMessage) -> String = defaultWarningMarkdown(warning:), + failureMarkdownFormatter: @escaping (Report.FailureMessage) -> String = defaultFailureMarkdown(failure:), + todosMarkdownFormatter: @escaping ([String]) -> String = defaultTodosMarkdown(todos:), + congratulationsMessage: String = defaultCongratulationsMessage + ) { + self.titleMarkdownFormatter = titleMarkdownFormatter + self.messageMarkdownFormatter = messageMarkdownFormatter + self.warningMarkdownFormatter = warningMarkdownFormatter + self.failureMarkdownFormatter = failureMarkdownFormatter + self.todosMarkdownFormatter = todosMarkdownFormatter + self.congratulationsMessage = congratulationsMessage + } + + public static var `default`: MarkdownConfiguration { + .init() + } + +} + +private extension Report.CheckItem.Result { + + var markdownSymbol: String { + switch self { + case .good: + return ":tada:" + + case .acceptable: + return ":thinking:" + + case .rejected: + return ":no_good:" + } + } + +} diff --git a/Sources/DangerSwiftShoki/Report.swift b/Sources/DangerSwiftShoki/Report.swift new file mode 100644 index 0000000..e3ed3d8 --- /dev/null +++ b/Sources/DangerSwiftShoki/Report.swift @@ -0,0 +1,147 @@ +// +// Report.swift +// +// +// Created by 史 翔新 on 2020/07/11. +// + +public struct Report: Equatable { + + public struct CheckItem: Equatable { + + public enum Result: Equatable { + case good + case acceptable(warningMessage: String?) + case rejected(failureMessage: String?) + } + + let title: String + let result: Result + + } + + public typealias WarningMessage = (title: String, message: String?) + public typealias FailureMessage = (title: String, message: String?) + + public let title: String + + internal(set) public var checkItems: [CheckItem] = [] + + internal(set) public var todos: [String] = [] + + public var warnings: AnyCollection { + + checkItems.lazy.compactMap { item in + switch item.result { + case .acceptable(warningMessage: let warning): + return (item.title, warning) + + case .good, .rejected: + return nil + } + } + .eraseToAnyCollection() + + } + + public var failures: AnyCollection { + + checkItems.lazy.compactMap { item in + switch item.result { + case .rejected(failureMessage: let failure): + return (item.title, failure) + + case .good, .acceptable: + return nil + } + } + .eraseToAnyCollection() + + } + + @available(*, deprecated, renamed: "warnings.count") + public var warningsCount: Int { + warnings.count + } + + @available(*, deprecated, renamed: "failures.count") + public var errorsCount: Int { + failures.count + } + + init(title: String) { + self.title = title + } + + @available(*, deprecated, message: "It's `Shoki`'s responsibility to format a message, not `CheckResult`'s, so stop using this property to get the formatted title, which you shouldn't have to care about at first place.") + public var markdownTitle: String { + + "## " + title + + } + + @available(*, deprecated, message: "It's `Shoki`'s responsibility to format a message, not `CheckResult`'s, so stop using this property to get the formatted message, which you shouldn't have to care about at first place.") + public var markdownMessage: String { + + guard !checkItems.isEmpty else { + return "" + } + + let chartHeader = """ + Checking Item | Result + | ---| --- | + + """ + let chartContent = checkItems.map { + "\($0.title) | \($0.result.markdownSymbol)" + } .joined(separator: "\n") + + return chartHeader + chartContent + + } + + @available(*, deprecated, message: "It's `Shoki`'s responsibility to format a message, not `CheckResult`'s, so stop using this property to get the formatted todos, which you shouldn't have to care about at first place.") + public var markdownTodos: String { + + guard !todos.isEmpty else { + return "" + } + + let todoContent = todos.map { + "- [ ] \($0)" + } + + return todoContent.joined(separator: "\n") + + } + +} + + +@available(*, deprecated, renamed: "Report") +public typealias CheckResult = Report + +private extension Report.CheckItem.Result { + + var markdownSymbol: String { + switch self { + case .good: + return ":tada:" + + case .acceptable: + return ":thinking:" + + case .rejected: + return ":no_good:" + } + } + +} + +private extension Collection { + + func eraseToAnyCollection() -> AnyCollection { + .init(self) + } + +} diff --git a/Sources/DangerSwiftShoki/Shoki.swift b/Sources/DangerSwiftShoki/Shoki.swift index a771d32..634453b 100644 --- a/Sources/DangerSwiftShoki/Shoki.swift +++ b/Sources/DangerSwiftShoki/Shoki.swift @@ -11,13 +11,19 @@ public struct Shoki { private let markdownExecutor: (String) -> Void private let messageExecutor: (String) -> Void + private let warningExecutor: (String) -> Void + private let failureExecutor: (String) -> Void init( markdownExecutor: @escaping (String) -> Void, - messageExecutor: @escaping (String) -> Void + messageExecutor: @escaping (String) -> Void, + warningExecutor: @escaping (String) -> Void, + failureExecutor: @escaping (String) -> Void ) { self.markdownExecutor = markdownExecutor self.messageExecutor = messageExecutor + self.warningExecutor = warningExecutor + self.failureExecutor = failureExecutor } } @@ -32,25 +38,66 @@ extension Shoki { messageExecutor(message) } + func warn(_ warning: String) { + warningExecutor(warning) + } + + func fail(_ failure: String) { + failureExecutor(failure) + } + } extension Shoki { - public func report(_ result: CheckResult) { + public func makeInitialReport(title: String) -> Report { - markdown(result.markdownTitle) + .init(title: title) - if !result.markdownMessage.isEmpty { - markdown(result.markdownMessage) - + } + + public func check(_ title: String, into report: inout Report, execution: () -> Report.CheckItem.Result) { + + let executionResult = execution() + report.checkItems.append(.init(title: title, result: executionResult)) + + } + + public func askReviewer(to taskToDo: String, into report: inout Report) { + + report.todos.append(taskToDo) + + } + + public func report(_ report: Report, using configuration: MarkdownConfiguration = .default) { + + let markdownTitle = configuration.titleMarkdownFormatter(report.title) + if !markdownTitle.isEmpty { + markdown(markdownTitle) } - if !result.markdownTodos.isEmpty { - markdown(result.markdownTodos) + let markdownMessage = configuration.messageMarkdownFormatter(report.checkItems) + if !markdownMessage.isEmpty { + markdown(markdownMessage) } - if result.warningsCount == 0 && result.errorsCount == 0 { - message("Good Job :white_flower:") + for warning in report.warnings { + let markdownWarning = configuration.warningMarkdownFormatter(warning) + warn(markdownWarning) + } + + for failure in report.failures { + let markdownFailure = configuration.failureMarkdownFormatter(failure) + fail(markdownFailure) + } + + let markdownTodos = configuration.todosMarkdownFormatter(report.todos) + if !markdownTodos.isEmpty { + markdown(markdownTodos) + } + + if report.warnings.isEmpty && report.failures.isEmpty { + message(configuration.congratulationsMessage) } } diff --git a/Tests/DangerSwiftShokiTests/CheckResultTests.swift b/Tests/DangerSwiftShokiTests/CheckResultTests.swift deleted file mode 100644 index 57a7c79..0000000 --- a/Tests/DangerSwiftShokiTests/CheckResultTests.swift +++ /dev/null @@ -1,108 +0,0 @@ -import XCTest -@testable import DangerSwiftShoki - -final class CheckResultTests: XCTestCase { - - func test_checkResultLife() { - - var checkResult = CheckResult(title: "Test Check") - - XCTContext.runActivity(named: "Initialize CheckResult") { _ in - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 0) - XCTAssertEqual(checkResult.errorsCount, 0) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - """) - XCTAssertEqual(checkResult.markdownTodos, """ - """) - } - - XCTContext.runActivity(named: "Add Check Item with Good Result") { _ in - checkResult.check("Good Check", execution: { .good }) - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 0) - XCTAssertEqual(checkResult.errorsCount, 0) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - Checking Item | Result - | ---| --- | - Good Check | :tada: - """) - XCTAssertEqual(checkResult.markdownTodos, """ - """) - } - - XCTContext.runActivity(named: "Add Check Item with Acceptable Result") { _ in - checkResult.check("Acceptable Check", execution: { .acceptable }) - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 1) - XCTAssertEqual(checkResult.errorsCount, 0) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - Checking Item | Result - | ---| --- | - Good Check | :tada: - Acceptable Check | :thinking: - """) - XCTAssertEqual(checkResult.markdownTodos, """ - """) - } - - XCTContext.runActivity(named: "Add Check Item with Rejected Result") { _ in - checkResult.check("Rejected Check", execution: { .rejected }) - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 1) - XCTAssertEqual(checkResult.errorsCount, 1) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - Checking Item | Result - | ---| --- | - Good Check | :tada: - Acceptable Check | :thinking: - Rejected Check | :no_good: - """) - XCTAssertEqual(checkResult.markdownTodos, """ - """) - } - - XCTContext.runActivity(named: "Add A Todo Item") { _ in - checkResult.askReviewer(to: "Do Something") - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 1) - XCTAssertEqual(checkResult.errorsCount, 1) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - Checking Item | Result - | ---| --- | - Good Check | :tada: - Acceptable Check | :thinking: - Rejected Check | :no_good: - """) - XCTAssertEqual(checkResult.markdownTodos, """ - - [ ] Do Something - """) - } - - XCTContext.runActivity(named: "Add Another Todo Item") { _ in - checkResult.askReviewer(to: "Do Another Thing") - XCTAssertEqual(checkResult.title, "Test Check") - XCTAssertEqual(checkResult.warningsCount, 1) - XCTAssertEqual(checkResult.errorsCount, 1) - XCTAssertEqual(checkResult.markdownTitle, "## Test Check") - XCTAssertEqual(checkResult.markdownMessage, """ - Checking Item | Result - | ---| --- | - Good Check | :tada: - Acceptable Check | :thinking: - Rejected Check | :no_good: - """) - XCTAssertEqual(checkResult.markdownTodos, """ - - [ ] Do Something - - [ ] Do Another Thing - """) - } - - } - -} diff --git a/Tests/DangerSwiftShokiTests/MarkdownConfigurationTests.swift b/Tests/DangerSwiftShokiTests/MarkdownConfigurationTests.swift new file mode 100644 index 0000000..75ef926 --- /dev/null +++ b/Tests/DangerSwiftShokiTests/MarkdownConfigurationTests.swift @@ -0,0 +1,120 @@ +// +// MarkdownConfigurationTests.swift +// +// +// Created by 史 翔新 on 2021/12/02. +// + +import XCTest +@testable import DangerSwiftShoki + +final class MarkdownConfigurationTests: XCTestCase { + + func test_defaultConfigurations() { + + typealias MC = MarkdownConfiguration + let mc = MC.default + + XCTContext.runActivity(named: "Check Title Markdown") { _ in + + let title = "Test Title" + let expected = "## Test Title" + XCTAssertEqual(MC.defaultTitleMarkdown(title: title), expected) + XCTAssertEqual(mc.titleMarkdownFormatter(title), expected) + + } + + XCTContext.runActivity(named: "Check Message Markdown") { _ in + + let emptyCheckItems: [Report.CheckItem] = [] + let expectedEmptyOutput = "" + XCTAssertEqual(MC.defaultMessageMarkdown(checkItems: emptyCheckItems), expectedEmptyOutput) + XCTAssertEqual(mc.messageMarkdownFormatter(emptyCheckItems), expectedEmptyOutput) + + let singleElementCheckItems: [Report.CheckItem] = [ + .init(title: "Test 1", result: .good) + ] + let expectedSingleElementOutput = """ + Checking Item | Result + | ---| --- | + Test 1 | :tada: + """ + XCTAssertEqual(MC.defaultMessageMarkdown(checkItems: singleElementCheckItems), expectedSingleElementOutput) + XCTAssertEqual(mc.messageMarkdownFormatter(singleElementCheckItems), expectedSingleElementOutput) + + let multipleElementCheckItems: [Report.CheckItem] = [ + .init(title: "Test 2", result: .acceptable(warningMessage: nil)), + .init(title: "Test 3", result: .rejected(failureMessage: "NG")) + ] + let expectedMultipleElementOutput = """ + Checking Item | Result + | ---| --- | + Test 2 | :thinking: + Test 3 | :no_good: + """ + XCTAssertEqual(MC.defaultMessageMarkdown(checkItems: multipleElementCheckItems), expectedMultipleElementOutput) + XCTAssertEqual(mc.messageMarkdownFormatter(multipleElementCheckItems), expectedMultipleElementOutput) + + } + + XCTContext.runActivity(named: "Check Warning Markdown") { _ in + + let warningTitle = "Warning Title" + let nonNilMessage = "Needs Further Check" + let expectedNilMessageOutput = "Warning Title has a warning." + let expectedNonNilMessageOutput = "Warning Title: Needs Further Check" + XCTAssertEqual(MC.defaultWarningMarkdown(warning: (warningTitle, nil)), expectedNilMessageOutput) + XCTAssertEqual(mc.warningMarkdownFormatter((warningTitle, nil)), expectedNilMessageOutput) + XCTAssertEqual(MC.defaultWarningMarkdown(warning: (warningTitle, nonNilMessage)), expectedNonNilMessageOutput) + XCTAssertEqual(mc.warningMarkdownFormatter((warningTitle, nonNilMessage)), expectedNonNilMessageOutput) + + } + + XCTContext.runActivity(named: "Check Failure Markdown") { _ in + + let failureTitle = "Failure Title" + let nonNilMessage = "NO GOOD" + let expectedNilMessageOutput = "Failure Title failed." + let expectedNonNilMessageOutput = "Failure Title: NO GOOD" + XCTAssertEqual(MC.defaultFailureMarkdown(failure: (failureTitle, nil)), expectedNilMessageOutput) + XCTAssertEqual(mc.failureMarkdownFormatter((failureTitle, nil)), expectedNilMessageOutput) + XCTAssertEqual(MC.defaultFailureMarkdown(failure: (failureTitle, nonNilMessage)), expectedNonNilMessageOutput) + XCTAssertEqual(mc.failureMarkdownFormatter((failureTitle, nonNilMessage)), expectedNonNilMessageOutput) + + } + + XCTContext.runActivity(named: "Check Todo Markdown") { _ in + + let emptyTodos: [String] = [] + let expectedEmptyOutput = "" + XCTAssertEqual(MC.defaultTodosMarkdown(todos: emptyTodos), expectedEmptyOutput) + XCTAssertEqual(mc.todosMarkdownFormatter(emptyTodos), expectedEmptyOutput) + + let singleElementTodos = ["Todo 1"] + let expectedSingleElementOutput = """ + - [ ] Todo 1 + """ + XCTAssertEqual(MC.defaultTodosMarkdown(todos: singleElementTodos), expectedSingleElementOutput) + XCTAssertEqual(mc.todosMarkdownFormatter(singleElementTodos), expectedSingleElementOutput) + + let multipleElementTodos = ["Todo 1", "Todo 2"] + let expectedMultipleElementOutput = """ + - [ ] Todo 1 + - [ ] Todo 2 + """ + XCTAssertEqual(MC.defaultTodosMarkdown(todos: multipleElementTodos), expectedMultipleElementOutput) + XCTAssertEqual(mc.todosMarkdownFormatter(multipleElementTodos), expectedMultipleElementOutput) + + } + + XCTContext.runActivity(named: "Check Congratulations Message") { _ in + + let expected = "Good Job :white_flower:" + XCTAssertEqual(MC.defaultCongratulationsMessage, expected) + XCTAssertEqual(mc.congratulationsMessage, expected) + + } + + } + +} diff --git a/Tests/DangerSwiftShokiTests/ReportTests.swift b/Tests/DangerSwiftShokiTests/ReportTests.swift new file mode 100644 index 0000000..cbc5ad7 --- /dev/null +++ b/Tests/DangerSwiftShokiTests/ReportTests.swift @@ -0,0 +1,45 @@ +import XCTest +@testable import DangerSwiftShoki + +final class ReportTests: XCTestCase { + + private func dummyReport() -> Report { + var report = Report(title: "") + report.checkItems = [ + .init(title: "GOOD", result: .good), + .init(title: "WARNING 1", result: .acceptable(warningMessage: "Warning Message")), + .init(title: "WARNING 2", result: .acceptable(warningMessage: nil)), + .init(title: "FAILURE 1", result: .rejected(failureMessage: "Failure Message")), + .init(title: "FAILURE 2", result: .rejected(failureMessage: nil)), + ] + return report + } + + func test_warnings() { + + let inputReport = dummyReport() + XCTAssertEqual(inputReport.warnings, [("WARNING 1", "Warning Message"), ("WARNING 2", nil)]) + + } + + func test_failures() { + + let inputReport = dummyReport() + XCTAssertEqual(inputReport.failures, [("FAILURE 1", "Failure Message"), ("FAILURE 2", nil)]) + + } + +} + +private func XCTAssertEqual(_ anyCollection: AnyCollection, _ array: Array, line: UInt = #line) { + + guard anyCollection.count == array.count else { + return XCTFail(line: line) + } + + for tuple in zip(anyCollection, array) { + XCTAssertEqual(tuple.0.title, tuple.1.title, line: line) + XCTAssertEqual(tuple.0.message, tuple.1.message, line: line) + } + +} diff --git a/Tests/DangerSwiftShokiTests/ShokiTests.swift b/Tests/DangerSwiftShokiTests/ShokiTests.swift index 7f31e6b..5290ccf 100644 --- a/Tests/DangerSwiftShokiTests/ShokiTests.swift +++ b/Tests/DangerSwiftShokiTests/ShokiTests.swift @@ -23,20 +23,156 @@ final class ShokiTests: XCTestCase { currentExpectation.expectation.fulfill() case let invalid: - XCTFail("Invalid call from: \(invalid)", line: currentExpectation.line) + XCTFail(#"Invalid message. Expected: "\#(currentExpectation.input)"; Received: "\#(invalid)."#, line: currentExpectation.line) } expectations.removeFirst() } } - func test_report() { + private func makeUnexpectedExecutor(line: UInt = #line) -> (String) -> Void { + return { _ in XCTFail(line: line) } + } + + private func dummyShoki(line: UInt = #line) -> Shoki { + .init( + markdownExecutor: makeUnexpectedExecutor(line: line), + messageExecutor: makeUnexpectedExecutor(line: line), + warningExecutor: makeUnexpectedExecutor(line: line), + failureExecutor: makeUnexpectedExecutor(line: line) + ) + } + + func test_markdown() { + + let expectedInput = "My Markdown" + let executionExpectation = expectation(description: "Markdown") + let executor = makeExecutor([ + (#line, expectedInput, executionExpectation), + ]) + let shoki = Shoki( + markdownExecutor: executor, + messageExecutor: makeUnexpectedExecutor(), + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: makeUnexpectedExecutor() + ) + + shoki.markdown(expectedInput) + wait(for: [executionExpectation], timeout: 0) + + } + + func test_message() { + + let expectedInput = "My Message" + let executionExpectation = expectation(description: "Message") + let executor = makeExecutor([ + (#line, expectedInput, executionExpectation), + ]) + let shoki = Shoki( + markdownExecutor: makeUnexpectedExecutor(), + messageExecutor: executor, + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: makeUnexpectedExecutor() + ) + + shoki.message(expectedInput) + wait(for: [executionExpectation], timeout: 0) + + } + + func test_warn() { + + let expectedInput = "My Warn" + let executionExpectation = expectation(description: "Warn") + let executor = makeExecutor([ + (#line, expectedInput, executionExpectation), + ]) + let shoki = Shoki( + markdownExecutor: makeUnexpectedExecutor(), + messageExecutor: makeUnexpectedExecutor(), + warningExecutor: executor, + failureExecutor: makeUnexpectedExecutor() + ) + + shoki.warn(expectedInput) + wait(for: [executionExpectation], timeout: 0) + + } + + func test_fail() { + + let expectedInput = "My Fail" + let executionExpectation = expectation(description: "Fail") + let executor = makeExecutor([ + (#line, expectedInput, executionExpectation), + ]) + let shoki = Shoki( + markdownExecutor: makeUnexpectedExecutor(), + messageExecutor: makeUnexpectedExecutor(), + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: executor + ) + + shoki.fail(expectedInput) + wait(for: [executionExpectation], timeout: 0) + + } + + func test_makeInitialReport() { + + let shoki = dummyShoki() + let report = shoki.makeInitialReport(title: "Title") + let expected = Report(title: "Title") + XCTAssertEqual(report, expected) + + } + + func test_check() { + + let shoki = dummyShoki() + var report = Report(title: "Title") + shoki.check("Check 1", into: &report, execution: { .good }) + shoki.check("Check 2", into: &report, execution: { .acceptable(warningMessage: nil) }) + shoki.check("Check 3", into: &report, execution: { .rejected(failureMessage: nil) }) + let expected: Report = { + var report = Report(title: "Title") + report.checkItems = [ + .init(title: "Check 1", result: .good), + .init(title: "Check 2", result: .acceptable(warningMessage: nil)), + .init(title: "Check 3", result: .rejected(failureMessage: nil)), + ] + return report + }() + XCTAssertEqual(report, expected) + + } + + func test_askReviewer() { + + let shoki = dummyShoki() + var report = Report(title: "Title") + shoki.askReviewer(to: "TODO 1", into: &report) + shoki.askReviewer(to: "TODO 2", into: &report) + let expected: Report = { + var report = Report(title: "Title") + report.todos = [ + "TODO 1", + "TODO 2", + ] + return report + }() + XCTAssertEqual(report, expected) + + } + + func test_realWorldUsage() { XCTContext.runActivity(named: "Good CheckResult") { _ in - let inputResult = { () -> CheckResult in - var result = CheckResult(title: "Good Result") - result.check("Good Check", execution: { .good }) - result.askReviewer(to: "Good Todo") + let inputReport = { () -> Report in + var result = Report(title: "Good Result") + result.checkItems.append(.init(title: "Good Check", result: .good)) + result.todos.append("Good Todo") return result }() @@ -62,16 +198,21 @@ final class ShokiTests: XCTestCase { let messageExecutor = makeExecutor([ (#line, expectedReward, rewardExpectation) ]) - let shoki = Shoki(markdownExecutor: markdownExecutor, messageExecutor: messageExecutor) + let shoki = Shoki( + markdownExecutor: markdownExecutor, + messageExecutor: messageExecutor, + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: makeUnexpectedExecutor() + ) - shoki.report(inputResult) + shoki.report(inputReport) wait(for: [titleExpectation, messageExpectation, todosExpectation, rewardExpectation], timeout: 0, enforceOrder: true) } XCTContext.runActivity(named: "Empty CheckResult") { _ in - let inputResult = CheckResult(title: "Empty Result") + let inputReport = Report(title: "Empty Result") let titleExpectation = expectation(description: "Title") let rewardExpectation = expectation(description: "Reward") @@ -85,23 +226,29 @@ final class ShokiTests: XCTestCase { let messageExecutor = makeExecutor([ (#line, expectedReward, rewardExpectation), ]) - let shoki = Shoki(markdownExecutor: markdownExecutor, messageExecutor: messageExecutor) + let shoki = Shoki( + markdownExecutor: markdownExecutor, + messageExecutor: messageExecutor, + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: makeUnexpectedExecutor() + ) - shoki.report(inputResult) + shoki.report(inputReport) wait(for: [titleExpectation, rewardExpectation], timeout: 0, enforceOrder: true) } XCTContext.runActivity(named: "Rejected Result") { _ in - let inputResult = { () -> CheckResult in - var result = CheckResult(title: "Rejected Result") - result.check("Rejected Check", execution: { .rejected }) + let inputReport = { () -> Report in + var result = Report(title: "Rejected Result") + result.checkItems.append(.init(title: "Rejected Check", result: .rejected(failureMessage: nil))) return result }() let titleExpectation = expectation(description: "Title") let messageExpectation = expectation(description: "Message") + let failureExpectation = expectation(description: "Failure") let expectedTitle = "## Rejected Result" let expectedMessage = """ @@ -109,16 +256,25 @@ final class ShokiTests: XCTestCase { | ---| --- | Rejected Check | :no_good: """ + let expectedFailure = "Rejected Check failed." let markdownExecutor = makeExecutor([ (#line, expectedTitle, titleExpectation), (#line, expectedMessage, messageExpectation), ]) let messageExecutor = makeExecutor([]) - let shoki = Shoki(markdownExecutor: markdownExecutor, messageExecutor: messageExecutor) + let failureExecutor = makeExecutor([ + (#line, expectedFailure, failureExpectation), + ]) + let shoki = Shoki( + markdownExecutor: markdownExecutor, + messageExecutor: messageExecutor, + warningExecutor: makeUnexpectedExecutor(), + failureExecutor: failureExecutor + ) - shoki.report(inputResult) - wait(for: [titleExpectation, messageExpectation], timeout: 0, enforceOrder: true) + shoki.report(inputReport) + wait(for: [titleExpectation, messageExpectation, failureExpectation], timeout: 0, enforceOrder: true) }