From bb789c10adb62626f0d98f8dc9b153035fb4b8c4 Mon Sep 17 00:00:00 2001 From: Marcin Iwanicki Date: Mon, 25 May 2020 11:15:40 +0100 Subject: [PATCH] More complete HTML renderer implementation --- .../Generated/html_format.2.e5ef416a.md | 114 +++++++ .../html_format_verbose.2.3ef640ed.md | 114 +++++++ CommandTests/manual_test_commands.json | 10 + .../TextProjectCompareResultRenderer.swift | 80 +---- .../Renderer/ConsoleRenderer.swift | 73 +---- .../HTMLRenderer.swift} | 190 ++++++----- .../Renderer/MarkdownRenderer.swift | 49 +-- .../Renderer/Renderer+Context.swift | 48 --- .../ResultRenderer/Renderer/Renderer.swift | 25 +- .../UniversalResultRenderer.swift | 10 +- .../Renderer/ConsoleRendererTests.swift | 81 ++--- .../Renderer/HTMLRendererTests.swift | 140 ++++++++ .../Renderer/MarkdownRendererTests.swift | 85 +++-- .../Renderer/Renderer+ContextTests.swift | 299 ------------------ 14 files changed, 603 insertions(+), 715 deletions(-) create mode 100644 CommandTests/Generated/html_format.2.e5ef416a.md create mode 100644 CommandTests/Generated/html_format_verbose.2.3ef640ed.md rename Sources/XCDiffCore/ResultRenderer/{ProjectResultRenderer/HTMLProjectCompareResultRenderer.swift => Renderer/HTMLRenderer.swift} (50%) delete mode 100644 Sources/XCDiffCore/ResultRenderer/Renderer/Renderer+Context.swift create mode 100644 Tests/XCDiffCoreTests/ResultRenderer/Renderer/HTMLRendererTests.swift delete mode 100644 Tests/XCDiffCoreTests/ResultRenderer/Renderer/Renderer+ContextTests.swift diff --git a/CommandTests/Generated/html_format.2.e5ef416a.md b/CommandTests/Generated/html_format.2.e5ef416a.md new file mode 100644 index 0000000..494c50b --- /dev/null +++ b/CommandTests/Generated/html_format.2.e5ef416a.md @@ -0,0 +1,114 @@ +# Command +```json +["-p1", "{ios_project_1}", "-p2", "{ios_project_2}", "-f", "html"] +``` + +# Expected exit code +2 + +# Expected output +``` + + + + + xcdiff results + + + + + + +
+
+

Diff Results

+

❌ FILE_REFERENCES

❌ BUILD_PHASES > "MismatchingLibrary" target

✅ BUILD_PHASES > "Project" target

✅ BUILD_PHASES > "ProjectFramework" target

✅ BUILD_PHASES > "ProjectTests" target

✅ BUILD_PHASES > "ProjectUITests" target

✅ COPY_FILES > "MismatchingLibrary" target

❌ COPY_FILES > "Project" target > Embed Frameworks

✅ COPY_FILES > "ProjectFramework" target

✅ COPY_FILES > "ProjectTests" target

✅ COPY_FILES > "ProjectUITests" target

❌ TARGETS > NATIVE targets

❌ TARGETS > AGGREGATE targets

❌ HEADERS > "MismatchingLibrary" target

✅ HEADERS > "Project" target

❌ HEADERS > "ProjectFramework" target

✅ HEADERS > "ProjectTests" target

✅ HEADERS > "ProjectUITests" target

✅ SOURCES > "MismatchingLibrary" target

❌ SOURCES > "Project" target

✅ SOURCES > "ProjectFramework" target

❌ SOURCES > "ProjectTests" target

❌ SOURCES > "ProjectUITests" target

✅ RESOURCES > "MismatchingLibrary" target

❌ RESOURCES > "Project" target

✅ RESOURCES > "ProjectFramework" target

❌ RESOURCES > "ProjectTests" target

❌ RESOURCES > "ProjectUITests" target

❌ CONFIGURATIONS > Root project

❌ SETTINGS > Root project > "Debug" configuration > Base configuration

❌ SETTINGS > Root project > "Debug" configuration > Values

✅ SETTINGS > Root project > "Release" configuration > Base configuration

❌ SETTINGS > Root project > "Release" configuration > Values

✅ SETTINGS > "MismatchingLibrary" target > "Debug" configuration > Base configuration

❌ SETTINGS > "MismatchingLibrary" target > "Debug" configuration > Values

✅ SETTINGS > "MismatchingLibrary" target > "Release" configuration > Base configuration

❌ SETTINGS > "MismatchingLibrary" target > "Release" configuration > Values

❌ SETTINGS > "Project" target > "Debug" configuration > Base configuration

❌ SETTINGS > "Project" target > "Debug" configuration > Values

✅ SETTINGS > "Project" target > "Release" configuration > Base configuration

❌ SETTINGS > "Project" target > "Release" configuration > Values

✅ SETTINGS > "ProjectFramework" target > "Debug" configuration > Base configuration

❌ SETTINGS > "ProjectFramework" target > "Debug" configuration > Values

✅ SETTINGS > "ProjectFramework" target > "Release" configuration > Base configuration

❌ SETTINGS > "ProjectFramework" target > "Release" configuration > Values

✅ SETTINGS > "ProjectTests" target > "Debug" configuration > Base configuration

✅ SETTINGS > "ProjectTests" target > "Debug" configuration > Values

✅ SETTINGS > "ProjectTests" target > "Release" configuration > Base configuration

✅ SETTINGS > "ProjectTests" target > "Release" configuration > Values

✅ SETTINGS > "ProjectUITests" target > "Debug" configuration > Base configuration

✅ SETTINGS > "ProjectUITests" target > "Debug" configuration > Values

✅ SETTINGS > "ProjectUITests" target > "Release" configuration > Base configuration

✅ SETTINGS > "ProjectUITests" target > "Release" configuration > Values

❌ SOURCE_TREES > Root project

✅ LINKED_DEPENDENCIES > "MismatchingLibrary" target

❌ LINKED_DEPENDENCIES > "Project" target

✅ LINKED_DEPENDENCIES > "ProjectFramework" target

✅ LINKED_DEPENDENCIES > "ProjectTests" target

✅ LINKED_DEPENDENCIES > "ProjectUITests" target

+
+ + + +``` diff --git a/CommandTests/Generated/html_format_verbose.2.3ef640ed.md b/CommandTests/Generated/html_format_verbose.2.3ef640ed.md new file mode 100644 index 0000000..2efbf06 --- /dev/null +++ b/CommandTests/Generated/html_format_verbose.2.3ef640ed.md @@ -0,0 +1,114 @@ +# Command +```json +["-p1", "{ios_project_1}", "-p2", "{ios_project_2}", "-f", "html", "-v"] +``` + +# Expected exit code +2 + +# Expected output +``` + + + + + xcdiff results + + + + + + +
+
+

Diff Results

+

❌ FILE_REFERENCES

⚠️ Only in first (8):

  • Project/Group B/AViewController.xib
  • Project/Group B/AnotherObjcClass.h
  • Project/Group B/AnotherObjcClass.m
  • Project/Resources/time.png
  • ProjectTests/BarTests.swift
  • ProjectUITests/LoginTests.swift
  • ProjectUITests/Screenshots/empty.png
  • libMismatchingLibrary.a

⚠️ Only in second (11):

  • MismatchingLibrary.framework
  • MismatchingLibrary/MismatchingLibrary-Info.plist
  • NewFramework.framework
  • NewFramework/Info.plist
  • NewFramework/NewFramework.h
  • Project/Project.xcconfig
  • Project/Target.xcconfig
  • ProjectFramework/Header4.h
  • ProjectTests/Responses/ListResponse.json
  • ProjectUITests/MetricsTests.swift
  • README.md

❌ BUILD_PHASES > "MismatchingLibrary" target

⚠️ Only in first (1):

  • CopyFiles

⚠️ Only in second (2):

  • Headers
  • Resources

✅ BUILD_PHASES > "Project" target

✅ BUILD_PHASES > "ProjectFramework" target

✅ BUILD_PHASES > "ProjectTests" target

✅ BUILD_PHASES > "ProjectUITests" target

✅ COPY_FILES > "MismatchingLibrary" target

❌ COPY_FILES > "Project" target > Embed Frameworks

⚠️ Only in second (2):

  • MismatchingLibrary.framework
  • NewFramework.framework

⚠️ Value mismatch (1):

  • ProjectFramework.framework

    • attributes = ["CodeSignOnCopy", "RemoveHeadersOnCopy"]
    • attributes = []

✅ COPY_FILES > "ProjectFramework" target

✅ COPY_FILES > "ProjectTests" target

✅ COPY_FILES > "ProjectUITests" target

❌ TARGETS > NATIVE targets

⚠️ Only in second (1):

  • NewFramework

⚠️ Value mismatch (1):

  • MismatchingLibrary product type

    • com.apple.product-type.library.static
    • com.apple.product-type.framework

❌ TARGETS > AGGREGATE targets

⚠️ Only in second (1):

  • NewAggregate

❌ HEADERS > "MismatchingLibrary" target

⚠️ Only in second (1):

  • MismatchingLibrary/MismatchingLibrary.h

✅ HEADERS > "Project" target

❌ HEADERS > "ProjectFramework" target

⚠️ Only in second (1):

  • ProjectFramework/Header4.h

⚠️ Value mismatch (2):

  • ProjectFramework/Header1.h attributes

    • Public
    • nil (Project)
  • ProjectFramework/Header2.h attributes

    • Private
    • nil (Project)

✅ HEADERS > "ProjectTests" target

✅ HEADERS > "ProjectUITests" target

✅ SOURCES > "MismatchingLibrary" target

❌ SOURCES > "Project" target

⚠️ Only in first (1):

  • Project/Group B/AnotherObjcClass.m

⚠️ Value mismatch (1):

  • Project/Group A/ObjcClass.m compiler flags

    • nil
    • -ObjC

✅ SOURCES > "ProjectFramework" target

❌ SOURCES > "ProjectTests" target

⚠️ Only in first (1):

  • ProjectTests/BarTests.swift

❌ SOURCES > "ProjectUITests" target

⚠️ Only in first (1):

  • ProjectUITests/LoginTests.swift

⚠️ Only in second (1):

  • ProjectUITests/MetricsTests.swift

✅ RESOURCES > "MismatchingLibrary" target

❌ RESOURCES > "Project" target

⚠️ Only in first (2):

  • Project/Group B/AViewController.xib
  • Project/Resources/time.png

✅ RESOURCES > "ProjectFramework" target

❌ RESOURCES > "ProjectTests" target

⚠️ Only in second (1):

  • ProjectTests/Responses/ListResponse.json

❌ RESOURCES > "ProjectUITests" target

⚠️ Only in first (1):

  • ProjectUITests/Screenshots/empty.png

❌ CONFIGURATIONS > Root project

⚠️ Only in second (1):

  • CUSTOM_NEW

❌ SETTINGS > Root project > "Debug" configuration > Base configuration

⚠️ Value mismatch (1):

  • Path to .xcconfig

    • nil
    • Project/Project.xcconfig

❌ SETTINGS > Root project > "Debug" configuration > Values

⚠️ Only in second (1):

  • CUSTOM_SETTGING_1

✅ SETTINGS > Root project > "Release" configuration > Base configuration

❌ SETTINGS > Root project > "Release" configuration > Values

⚠️ Only in second (1):

  • CUSTOM_SETTGING_1

✅ SETTINGS > "MismatchingLibrary" target > "Debug" configuration > Base configuration

❌ SETTINGS > "MismatchingLibrary" target > "Debug" configuration > Values

⚠️ Only in first (1):

  • OTHER_LDFLAGS

⚠️ Only in second (13):

  • CLANG_ENABLE_MODULES
  • CURRENT_PROJECT_VERSION
  • DEFINES_MODULE
  • DYLIB_COMPATIBILITY_VERSION
  • DYLIB_CURRENT_VERSION
  • DYLIB_INSTALL_NAME_BASE
  • INFOPLIST_FILE
  • INSTALL_PATH
  • LD_RUNPATH_SEARCH_PATHS
  • PRODUCT_BUNDLE_IDENTIFIER
  • SWIFT_OPTIMIZATION_LEVEL
  • VERSIONING_SYSTEM
  • VERSION_INFO_PREFIX

⚠️ Value mismatch (1):

  • PRODUCT_NAME

    • $(TARGET_NAME)
    • $(TARGET_NAME:c99extidentifier)

✅ SETTINGS > "MismatchingLibrary" target > "Release" configuration > Base configuration

❌ SETTINGS > "MismatchingLibrary" target > "Release" configuration > Values

⚠️ Only in first (1):

  • OTHER_LDFLAGS

⚠️ Only in second (12):

  • CLANG_ENABLE_MODULES
  • CURRENT_PROJECT_VERSION
  • DEFINES_MODULE
  • DYLIB_COMPATIBILITY_VERSION
  • DYLIB_CURRENT_VERSION
  • DYLIB_INSTALL_NAME_BASE
  • INFOPLIST_FILE
  • INSTALL_PATH
  • LD_RUNPATH_SEARCH_PATHS
  • PRODUCT_BUNDLE_IDENTIFIER
  • VERSIONING_SYSTEM
  • VERSION_INFO_PREFIX

⚠️ Value mismatch (1):

  • PRODUCT_NAME

    • $(TARGET_NAME)
    • $(TARGET_NAME:c99extidentifier)

❌ SETTINGS > "Project" target > "Debug" configuration > Base configuration

⚠️ Value mismatch (1):

  • Path to .xcconfig

    • nil
    • Project/Target.xcconfig

❌ SETTINGS > "Project" target > "Debug" configuration > Values

⚠️ Only in second (1):

  • ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES

⚠️ Value mismatch (1):

  • CUSTOM_SETTING_COMMON

    • VALUE_1
    • VALUE_2

✅ SETTINGS > "Project" target > "Release" configuration > Base configuration

❌ SETTINGS > "Project" target > "Release" configuration > Values

⚠️ Only in second (1):

  • ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES

⚠️ Value mismatch (1):

  • CUSTOM_SETTING_COMMON

    • VALUE_1
    • VALUE_2

✅ SETTINGS > "ProjectFramework" target > "Debug" configuration > Base configuration

❌ SETTINGS > "ProjectFramework" target > "Debug" configuration > Values

⚠️ Value mismatch (1):

  • PRODUCT_BUNDLE_IDENTIFIER

    • com.bloomberg.xcdiff.Project.testprovisioning.ProjectFramework
    • com.bloomberg.xcdiff.Project.ProjectFramework

✅ SETTINGS > "ProjectFramework" target > "Release" configuration > Base configuration

❌ SETTINGS > "ProjectFramework" target > "Release" configuration > Values

⚠️ Value mismatch (1):

  • PRODUCT_BUNDLE_IDENTIFIER

    • com.bloomberg.xcdiff.Project.testprovisioning.ProjectFramework
    • com.bloomberg.xcdiff.Project.ProjectFramework

✅ SETTINGS > "ProjectTests" target > "Debug" configuration > Base configuration

✅ SETTINGS > "ProjectTests" target > "Debug" configuration > Values

✅ SETTINGS > "ProjectTests" target > "Release" configuration > Base configuration

✅ SETTINGS > "ProjectTests" target > "Release" configuration > Values

✅ SETTINGS > "ProjectUITests" target > "Debug" configuration > Base configuration

✅ SETTINGS > "ProjectUITests" target > "Debug" configuration > Values

✅ SETTINGS > "ProjectUITests" target > "Release" configuration > Base configuration

✅ SETTINGS > "ProjectUITests" target > "Release" configuration > Values

❌ SOURCE_TREES > Root project

Output format: (<path>, <name>, <source_tree>)

⚠️ Only in first (8):

  • (AViewController.xib, nil, <group>) → (Group B, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)
  • (AnotherObjcClass.h, nil, <group>) → (Group B, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)
  • (AnotherObjcClass.m, nil, <group>) → (Group B, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)
  • (BarTests.swift, nil, <group>) → (ProjectTests, nil, <group>) → (nil, nil, <group>)
  • (LoginTests.swift, nil, <group>) → (ProjectUITests, nil, <group>) → (nil, nil, <group>)
  • (empty.png, nil, <group>) → (Screenshots, nil, <group>) → (ProjectUITests, nil, <group>) → (nil, nil, <group>)
  • (libMismatchingLibrary.a, nil, BUILT_PRODUCTS_DIR) → (nil, Products, <group>) → (nil, nil, <group>)
  • (time.png, nil, <group>) → (Resources, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)

⚠️ Only in second (11):

  • (Header4.h, nil, <group>) → (ProjectFramework, nil, <group>) → (nil, nil, <group>)
  • (Info.plist, nil, <group>) → (NewFramework, nil, <group>) → (nil, nil, <group>)
  • (ListResponse.json, nil, <group>) → (Responses, nil, <group>) → (ProjectTests, nil, <group>) → (nil, nil, <group>)
  • (MetricsTests.swift, nil, <group>) → (ProjectUITests, nil, <group>) → (nil, nil, <group>)
  • (MismatchingLibrary-Info.plist, nil, <group>) → (MismatchingLibrary, nil, <group>) → (nil, nil, <group>)
  • (MismatchingLibrary.framework, nil, BUILT_PRODUCTS_DIR) → (nil, Products, <group>) → (nil, nil, <group>)
  • (NewFramework.framework, nil, BUILT_PRODUCTS_DIR) → (nil, Products, <group>) → (nil, nil, <group>)
  • (NewFramework.h, nil, <group>) → (NewFramework, nil, <group>) → (nil, nil, <group>)
  • (Project.xcconfig, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)
  • (README.md, nil, <group>) → (nil, nil, <group>)
  • (Target.xcconfig, nil, <group>) → (Project, nil, <group>) → (nil, nil, <group>)

✅ LINKED_DEPENDENCIES > "MismatchingLibrary" target

❌ LINKED_DEPENDENCIES > "Project" target

⚠️ Only in second (2):

  • MismatchingLibrary.framework
  • NewFramework.framework

⚠️ Value mismatch (1):

  • ARKit.framework attributes

    • required
    • optional

✅ LINKED_DEPENDENCIES > "ProjectFramework" target

✅ LINKED_DEPENDENCIES > "ProjectTests" target

✅ LINKED_DEPENDENCIES > "ProjectUITests" target

+
+ + + +``` diff --git a/CommandTests/manual_test_commands.json b/CommandTests/manual_test_commands.json index cc517f8..4ca5ca3 100644 --- a/CommandTests/manual_test_commands.json +++ b/CommandTests/manual_test_commands.json @@ -48,5 +48,15 @@ "alias": "target_only_in_second_g_headers", "command": ["-p1", "{ios_project_1}", "-p2", "{ios_project_2}", "-g", "headers", "-t", "NewFramework", "-v"], "comment": "Target is only in second project when comparing headers" + }, + { + "alias": "html_format", + "command": ["-p1", "{ios_project_1}", "-p2", "{ios_project_2}", "-f", "html"], + "comment": "Multiple differences, valid HTML format expected" + }, + { + "alias": "html_format_verbose", + "command": ["-p1", "{ios_project_1}", "-p2", "{ios_project_2}", "-f", "html", "-v"], + "comment": "Multiple differences, valid HTML format expected" } ] \ No newline at end of file diff --git a/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/TextProjectCompareResultRenderer.swift b/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/TextProjectCompareResultRenderer.swift index 0210181..6ab6d9b 100644 --- a/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/TextProjectCompareResultRenderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/TextProjectCompareResultRenderer.swift @@ -25,80 +25,6 @@ final class TextProjectCompareResultRenderer: ProjectCompareResultRenderer { self.verbose = verbose } - func render(_ result: ProjectCompareResult) { - result.results.forEach(render) - } - - // MARK: - Private - - private func render(_ result: CompareResult) { - guard result.same() == false else { - renderer.successHeader(title(from: result)) - return - } - - renderer.errorHeader(title(from: result)) - - guard verbose else { - return - } - - if let description = result.description { - renderer.text(description) - } - - let onlyInFirst = result.onlyInFirst - if !onlyInFirst.isEmpty { - renderer.onlyInFirstHeader(count: onlyInFirst.count) - renderer.list(.begin) - onlyInFirst.forEach { - renderer.bullet($0, indent: .one) - } - renderer.list(.end) - } - - let onlyInSecond = result.onlyInSecond - if !onlyInSecond.isEmpty { - renderer.onlyInSecondHeader(count: onlyInSecond.count) - renderer.list(.begin) - onlyInSecond.forEach { - renderer.bullet($0, indent: .one) - } - renderer.list(.end) - } - - let differentValues = result.differentValues - if !differentValues.isEmpty { - renderer.differentValuesHeader(count: differentValues.count) - differentValues.forEach { - renderer.list(.begin) - renderer.bullet($0.context, indent: .one) - renderer.bullet("\($0.first)", indent: .two) - renderer.bullet("\($0.second)", indent: .two) - renderer.list(.end) - } - } - - renderer.newLine(1) - } - - private func title(from result: CompareResult) -> String { - let rootContext = result.tag.uppercased() - let subContext = !result.context.isEmpty ? " > " + result.context.joined(separator: " > ") : "" - return rootContext + subContext - } -} - - -final class TextProjectCompareResultRenderer2: ProjectCompareResultRenderer { - private let renderer: Renderer2 - private let verbose: Bool - - init(renderer: Renderer2, verbose: Bool) { - self.renderer = renderer - self.verbose = verbose - } - func render(_ result: ProjectCompareResult) { renderer.begin() result.results.forEach(render) @@ -160,7 +86,7 @@ final class TextProjectCompareResultRenderer2: ProjectCompareResultRenderer { renderer.list { differentValues.forEach { item in renderer.item { - renderer.text(item.context) + renderer.pre(item.context) renderer.list { renderer.item(item.first) renderer.item(item.second) @@ -172,7 +98,9 @@ final class TextProjectCompareResultRenderer2: ProjectCompareResultRenderer { } // added for compatibility with the old renderer - renderer.line(1) + if differentValues.isEmpty { + renderer.line(1) + } } } diff --git a/Sources/XCDiffCore/ResultRenderer/Renderer/ConsoleRenderer.swift b/Sources/XCDiffCore/ResultRenderer/Renderer/ConsoleRenderer.swift index 23f0fe8..718320c 100644 --- a/Sources/XCDiffCore/ResultRenderer/Renderer/ConsoleRenderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/Renderer/ConsoleRenderer.swift @@ -18,69 +18,6 @@ import Foundation final class ConsoleRenderer: Renderer { private let output: AnyOutput - - init(output: AnyOutput) { - self.output = output - } - - func text(_ text: String) { - write("\(text)\n") - } - - func list(_ element: RendererElement.List) { - switch element { - case .begin: - return - case .end: - newLine(1) - } - } - - func bullet(_ text: String, indent: RendererElement.Indent) { - let spacing = String(repeating: " ", count: 2 * indent.rawValue) - let symbol = bullet(indent: indent) - self.text("\(spacing)\(symbol) \(text)") - } - - func newLine(_ count: Int = 1) { - write(String(repeating: "\n", count: count)) - } - - func header(_ text: String, _ header: RendererElement.Header) { - switch header { - case .h1: - write("\n") - write("=\n") - write("= \(text)\n") - write("=\n") - write("\n") - case .h2: - write("\(text)\n") - case .h3: - write("\n\(text)\n\n") - } - } - - // MARK: - Private - - private func bullet(indent: RendererElement.Indent) -> String { - switch indent { - case .zero: - return "»" - case .one: - return "•" - case .two: - return "◦" - } - } - - private func write(_ string: String) { - output.write(string) - } -} - -final class ConsoleRenderer2: Renderer2 { - private let output: AnyOutput private var indent: Int = 0 init(output: AnyOutput) { @@ -95,7 +32,7 @@ final class ConsoleRenderer2: Renderer2 { // nothing } - func section(_ style: RendererElement.Style, _ content: () -> Void) { + func section(_: RendererElement.Style, _ content: () -> Void) { content() } @@ -118,6 +55,10 @@ final class ConsoleRenderer2: Renderer2 { write("\(text)\n") } + func pre(_ text: String) { + self.text(text) + } + func list(_ content: () -> Void) { indent += 1 content() @@ -128,6 +69,7 @@ final class ConsoleRenderer2: Renderer2 { func item(_ text: String) { item { write(text) + line(1) } } @@ -136,7 +78,6 @@ final class ConsoleRenderer2: Renderer2 { let symbol = bullet(indent: indent) write("\(spacing)\(symbol) ") content() - line(1) } func line(_ count: Int) { @@ -151,8 +92,6 @@ final class ConsoleRenderer2: Renderer2 { private func bullet(indent: Int) -> String { switch indent { - case 0: - return "»" case 1: return "•" default: diff --git a/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/HTMLProjectCompareResultRenderer.swift b/Sources/XCDiffCore/ResultRenderer/Renderer/HTMLRenderer.swift similarity index 50% rename from Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/HTMLProjectCompareResultRenderer.swift rename to Sources/XCDiffCore/ResultRenderer/Renderer/HTMLRenderer.swift index 0ff0f60..9978f16 100644 --- a/Sources/XCDiffCore/ResultRenderer/ProjectResultRenderer/HTMLProjectCompareResultRenderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/Renderer/HTMLRenderer.swift @@ -16,12 +16,10 @@ import Foundation -final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer { +final class HTMLRenderer: Renderer { private let output: AnyOutput - private let verbose: Bool private let header = """ - @@ -58,17 +56,39 @@ final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer { border-radius: 0.7em; } + ul { + margin-top: 0; + padding-left: 15px; + } + li { font-family: 'Courier New', Courier, monospace; } - h3 { + h1 { + padding: 0.2em 0.5em 0.2em 0.5em; + font-size: 24px; + font-weight: 300; + } + + h2 { padding: 0.2em 0.5em 0.2em 0.5em; border-radius: 0.2em; + font-size: 14px; + font-family: 'Courier New', Courier, monospace; + } + + h3 { + font-size: 12px; + padding-left: 0.2em; + } + + p { + margin: 0; } .container { - max-width: 980px; + max-width: 1200px; padding: 10px; margin-right: auto; margin-left: auto; @@ -78,12 +98,12 @@ final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer { background-color: #fffce3; } - .warning h3 { - background-color: #FFF2B8; + .warning h2 { + background-color: #fff2b8; } .success { - background-color: #F1FFE9; + background-color: #f1ffe9; } .content { @@ -94,7 +114,7 @@ final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer {
-

Diff

+

Diff Results

""" @@ -107,103 +127,69 @@ final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer { """ - init(output: AnyOutput, verbose: Bool) { + init(output: AnyOutput) { self.output = output - self.verbose = verbose } - func render(_ result: ProjectCompareResult) throws { + func begin() { output.write(header) - result.results.forEach(render(_:)) - output.write(footer) } - // MARK: - Private + func end() { + output.write(footer) + } - private func render(_ result: CompareResult) { - guard result.same() == false else { - section(.success) { - title(.success, result: result) - } - return + func section(_ style: RendererElement.Style, _ content: () -> Void) { + switch style { + case .success, .warning: + tag("section", cssClass(from: style), content) + case .default: + tag("div", .content, content) } + } - section(.warning) { - title(.warning, result: result) - guard verbose else { - return - } - tag("div", .content) { - let onlyInFirst = result.onlyInFirst - if !onlyInFirst.isEmpty { - tag("h4") { - output.write("Only in first (\(onlyInFirst.count))") - } - tag("ul") { - onlyInFirst.forEach { value in - tag("li") { - output.write(value) - } - } - } - } - - let onlyInSecond = result.onlyInSecond - if !onlyInSecond.isEmpty { - tag("h4") { - output.write("Only in second (\(onlyInSecond.count))") - } - tag("ul") { - onlyInSecond.forEach { value in - tag("li") { - output.write(value) - } - } - } - } - - let differentValues = result.differentValues - if !differentValues.isEmpty { - tag("h4") { - output.write("Different values (\(differentValues.count))") - } - tag("ul") { - differentValues.forEach { value in - tag("li") { - output.write(value.context) - tag("ul") { - tag("li") { - output.write(value.first) - } - tag("li") { - output.write(value.second) - } - } - } - } - } - } - } + func header(_ text: String, _ header: RendererElement.Header) { + tag(tag(from: header), nil, text) + } + + func text(_ text: String) { + tag("p", nil, escape(text)) + } + + func pre(_ text: String) { + self.text(escape(text)) + } + + func list(_ content: () -> Void) { + tag("ul", nil, content) + } + + func item(_ text: String) { + item { + output.write(escape(text)) } } - private func section(_ cssClass: CSSClass, _ content: () -> Void) { - tag("section", cssClass, content) + func item(_ content: () -> Void) { + tag("li", nil, content) + } + + func line(_: Int) { + // not needed } - private func title(_ cssClass: CSSClass, result: CompareResult) { - let rootContext = result.tag.uppercased() - let subContext = !result.context.isEmpty ? " > " + result.context.joined(separator: " > ") : "" - let title = rootContext + subContext - tag("h3") { - output.write(title) + // MARK: - Private + + private func tag(_ name: String, _ cssClass: CSSClass? = nil, _ content: String) { + tag(name, cssClass) { + output.write(content) } } private func tag(_ name: String, _ cssClass: CSSClass? = nil, _ content: () -> Void) { let cssClassString: String if let cssClass = cssClass { - cssClassString = " class=\(cssClass.rawValue)" + cssClassString = " class=\"\(cssClass.rawValue)\"" } else { cssClassString = "" } @@ -216,9 +202,41 @@ final class HTMLProjectCompareResultRenderer: ProjectCompareResultRenderer { """) } + // rather poor solution, we might need to find something more flexible and performant + private func escape(_ text: String) -> String { + return text + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "\"", with: """) + .replacingOccurrences(of: "'", with: "'") + } + private enum CSSClass: String { case success case warning case content } + + private func tag(from header: RendererElement.Header) -> String { + switch header { + case .h1: + return "h1" + case .h2: + return "h2" + case .h3: + return "h3" + } + } + + private func cssClass(from style: RendererElement.Style) -> CSSClass? { + switch style { + case .default: + return nil + case .success: + return .success + case .warning: + return .warning + } + } } diff --git a/Sources/XCDiffCore/ResultRenderer/Renderer/MarkdownRenderer.swift b/Sources/XCDiffCore/ResultRenderer/Renderer/MarkdownRenderer.swift index e1c5ec7..294812d 100644 --- a/Sources/XCDiffCore/ResultRenderer/Renderer/MarkdownRenderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/Renderer/MarkdownRenderer.swift @@ -18,45 +18,6 @@ import Foundation final class MarkdownRenderer: Renderer { private let output: AnyOutput - - init(output: AnyOutput) { - self.output = output - } - - func text(_ text: String) { - write("\(text)\n") - } - - func list(_ element: RendererElement.List) { - switch element { - case .begin: - return - case .end: - newLine(1) - } - } - - func bullet(_ text: String, indent: RendererElement.Indent) { - write("\(String(repeating: " ", count: 2 * indent.rawValue))- `\(text)`\n") - } - - func newLine(_ count: Int) { - write("\(String(repeating: "\n", count: count))") - } - - func header(_ text: String, _ header: RendererElement.Header) { - write("\n\(String(repeating: "#", count: header.rawValue)) \(text)\n\n") - } - - // MARK: - Private - - private func write(_ string: String) { - output.write(string) - } -} - -final class MarkdownRenderer2: Renderer2 { - private let output: AnyOutput private var indent: Int = 0 init(output: AnyOutput) { @@ -71,7 +32,7 @@ final class MarkdownRenderer2: Renderer2 { // nothing } - func section(_ style: RendererElement.Style, _ content: () -> Void) { + func section(_: RendererElement.Style, _ content: () -> Void) { content() } @@ -83,22 +44,26 @@ final class MarkdownRenderer2: Renderer2 { write("\(text)\n") } + func pre(_ text: String) { + write("`\(text)`\n") + } + func list(_ content: () -> Void) { indent += 1 content() indent -= 1 + line(1) } func item(_ text: String) { item { - write("`\(text)`") + pre(text) } } func item(_ content: () -> Void) { write("\(String(repeating: " ", count: 2 * indent))- ") content() - write("\n") } func line(_ count: Int) { diff --git a/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer+Context.swift b/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer+Context.swift deleted file mode 100644 index 08c5971..0000000 --- a/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer+Context.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright 2019 Bloomberg Finance L.P. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -extension Renderer { - func onlyInFirstHeader(count: Int? = nil) { - header("⚠️ Only in first\(string(from: count)):", .h3) - } - - func onlyInSecondHeader(count: Int? = nil) { - header("⚠️ Only in second\(string(from: count)):", .h3) - } - - func differentValuesHeader(count: Int? = nil) { - header("⚠️ Value mismatch\(string(from: count)):", .h3) - } - - func successHeader(_ text: String) { - header("✅ \(text)", .h2) - } - - func errorHeader(_ text: String) { - header("❌ \(text)", .h2) - } - - // MARK: - Private - - private func string(from count: Int?) -> String { - guard let count = count else { - return "" - } - return " (\(count))" - } -} diff --git a/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer.swift b/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer.swift index e7a33eb..512b8bd 100644 --- a/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/Renderer/Renderer.swift @@ -17,28 +17,12 @@ import Foundation struct RendererElement { - enum Indent: Int { - case zero = 0 - case one - case two - } - enum Header: Int { case h1 = 1 case h2 case h3 } - enum List { - case begin - case end - } - - enum Level { - case one - case two - } - enum Style { case `default` case success @@ -47,19 +31,12 @@ struct RendererElement { } protocol Renderer { - func text(_ text: String) - func list(_ element: RendererElement.List) - func bullet(_ text: String, indent: RendererElement.Indent) - func newLine(_ count: Int) - func header(_ text: String, _ header: RendererElement.Header) -} - -protocol Renderer2 { func begin() func end() func section(_ style: RendererElement.Style, _ content: () -> Void) func header(_ text: String, _ header: RendererElement.Header) func text(_ text: String) + func pre(_ text: String) func list(_ content: () -> Void) func item(_ text: String) func item(_ content: () -> Void) diff --git a/Sources/XCDiffCore/ResultRenderer/UniversalResultRenderer.swift b/Sources/XCDiffCore/ResultRenderer/UniversalResultRenderer.swift index 6e83a62..2acd01c 100644 --- a/Sources/XCDiffCore/ResultRenderer/UniversalResultRenderer.swift +++ b/Sources/XCDiffCore/ResultRenderer/UniversalResultRenderer.swift @@ -52,13 +52,13 @@ final class UniversalResultRenderer: ResultRenderer { } private func createConsoleRenderer(outputBuffer: AnyOutput) -> ProjectCompareResultRenderer { - return TextProjectCompareResultRenderer2(renderer: ConsoleRenderer2(output: outputBuffer), - verbose: verbose) + return TextProjectCompareResultRenderer(renderer: ConsoleRenderer(output: outputBuffer), + verbose: verbose) } private func createMarkdownRenderer(outputBuffer: AnyOutput) -> ProjectCompareResultRenderer { - return TextProjectCompareResultRenderer2(renderer: MarkdownRenderer2(output: outputBuffer), - verbose: verbose) + return TextProjectCompareResultRenderer(renderer: MarkdownRenderer(output: outputBuffer), + verbose: verbose) } private func createJSONRenderer(outputBuffer: AnyOutput) -> ProjectCompareResultRenderer { @@ -67,7 +67,7 @@ final class UniversalResultRenderer: ResultRenderer { } private func createHTMLRenderer(outputBuffer: AnyOutput) -> ProjectCompareResultRenderer { - return HTMLProjectCompareResultRenderer(output: outputBuffer, + return TextProjectCompareResultRenderer(renderer: HTMLRenderer(output: outputBuffer), verbose: verbose) } } diff --git a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/ConsoleRendererTests.swift b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/ConsoleRendererTests.swift index 5e6ade2..752bd5f 100644 --- a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/ConsoleRendererTests.swift +++ b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/ConsoleRendererTests.swift @@ -39,43 +39,46 @@ final class ConsoleRendererTests: XCTestCase { XCTAssertEqual(content, "1\n2\n3\n") } - func testList_whenBegin() { + func testList_whenEmpty() { // When - subject.list(.begin) - - // Then - XCTAssertEqual(content, "") - } - - func testList_whenEnd() { - // When - subject.list(.end) + subject.list {} // Then XCTAssertEqual(content, "\n") } - func testListWithBullets() { + func testList_whenItems() { // When - subject.list(.begin) - subject.bullet("b1", indent: .zero) - subject.bullet("b1.1", indent: .one) - subject.bullet("b1.2", indent: .one) - subject.bullet("b1.2.1", indent: .two) - subject.list(.end) + subject.list { + subject.item("b1.1") + subject.item { + subject.text("b1.2") + subject.list { + subject.item("b1.2.1") + } + } + subject.item { + subject.text("b2.2") + subject.list { + subject.item("b2.2.1") + } + } + } // Then XCTAssertEqual(content, """ - » b1 • b1.1 • b1.2 - ◦ b1.2.1\n\n + ◦ b1.2.1 + + • b2.2 + ◦ b2.2.1\n\n\n """) } - func testNewLine() { + func testLine() { // When - subject.newLine(3) + subject.line(3) // Then XCTAssertEqual(content, "\n\n\n") @@ -117,21 +120,26 @@ final class ConsoleRendererTests: XCTestCase { func testSample1() { // When subject.header("Header", .h3) - subject.list(.begin) - subject.bullet("Different Values 1", indent: .one) - subject.bullet("Value1", indent: .two) - subject.bullet("Value2", indent: .two) - subject.list(.end) - subject.list(.begin) - subject.bullet("Different Values 2", indent: .one) - subject.bullet("Value1", indent: .two) - subject.bullet("Value2", indent: .two) - subject.list(.end) - + subject.list { + subject.item { + subject.pre("Different Values 1") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + subject.item { + subject.pre("Different Values 2") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + } subject.header("Header 2", .h3) - subject.list(.begin) - subject.bullet("Test", indent: .zero) - subject.list(.end) + subject.list { + subject.item("Test") + } // Then XCTAssertEqual(content, """ @@ -146,9 +154,10 @@ final class ConsoleRendererTests: XCTestCase { ◦ Value1 ◦ Value2\n + Header 2 - » Test\n\n + • Test\n\n """) } diff --git a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/HTMLRendererTests.swift b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/HTMLRendererTests.swift new file mode 100644 index 0000000..564c04e --- /dev/null +++ b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/HTMLRendererTests.swift @@ -0,0 +1,140 @@ +// +// Copyright 2020 Bloomberg Finance L.P. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +@testable import XCDiffCore +import XCTest + +final class HTMLRendererTests: XCTestCase { + private var subject: HTMLRenderer! + private var outputBuffer: StringOutputBuffer! + + override func setUp() { + super.setUp() + + outputBuffer = StringOutputBuffer() + subject = HTMLRenderer(output: outputBuffer.any()) + } + + func testText() { + // When + subject.text("1") + subject.text("2") + subject.text("3") + + // Then + XCTAssertEqual(content, "

1

2

3

") + } + + func testList_whenEmpty() { + // When + subject.list {} + + // Then + XCTAssertEqual(content, "
    ") + } + + func testList_whenItems() { + // When + subject.list { + subject.item("b1.1") + subject.item { + subject.pre("b1.2") + subject.list { + subject.item("b1.2.1") + } + } + subject.item { + subject.pre("b2.2") + subject.list { + subject.item("b2.2.1") + } + } + } + + // Then + XCTAssertEqual(content, "
    • b1.1
    • b1.2

      • b1.2.1
    • " + + "
    • b2.2

      • b2.2.1
    ") + } + + func testLine() { + // When + subject.line(3) + + // Then + XCTAssertEqual(content, "") + } + + func testHeader_whenH1() { + // When + subject.header("H1", .h1) + + // Then + XCTAssertEqual(content, "

    H1

    ") + } + + func testHeader_whenH2() { + // When + subject.header("H2", .h2) + + // Then + XCTAssertEqual(content, "

    H2

    ") + } + + func testHeader_whenH3() { + // When + subject.header("H3", .h3) + + // Then + XCTAssertEqual(content, "

    H3

    ") + } + + func testSample1() { + // When + subject.header("Header", .h3) + subject.list { + subject.item { + subject.pre("Different Values 1") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + subject.item { + subject.pre("Different Values 2") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + } + subject.header("Header 2", .h3) + subject.list { + subject.item("Test") + } + + // Then + XCTAssertEqual(content, "

    Header

    • Different Values 1

      " + + "
      • Value1
      • Value2
    • Different Values 2

      " + + "
      • Value1
      • Value2

    Header 2

    • Test
    ") + } + + // MARK: - Private + + private var content: String { + return outputBuffer.flush() + } +} diff --git a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/MarkdownRendererTests.swift b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/MarkdownRendererTests.swift index b0a41a2..f46d5a5 100644 --- a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/MarkdownRendererTests.swift +++ b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/MarkdownRendererTests.swift @@ -39,43 +39,46 @@ final class MarkdownRendererTests: XCTestCase { XCTAssertEqual(content, "1\n2\n3\n") } - func testList_whenBegin() { + func testList_whenEmpty() { // When - subject.list(.begin) - - // Then - XCTAssertEqual(content, "") - } - - func testList_whenEnd() { - // When - subject.list(.end) + subject.list {} // Then XCTAssertEqual(content, "\n") } - func testListWithBullets() { + func testList_whenItems() { // When - subject.list(.begin) - subject.bullet("b1", indent: .zero) - subject.bullet("b1.1", indent: .one) - subject.bullet("b1.2", indent: .one) - subject.bullet("b1.2.1", indent: .two) - subject.list(.end) + subject.list { + subject.item("b1.1") + subject.item { + subject.pre("b1.2") + subject.list { + subject.item("b1.2.1") + } + } + subject.item { + subject.pre("b2.2") + subject.list { + subject.item("b2.2.1") + } + } + } // Then XCTAssertEqual(content, """ - - `b1` - `b1.1` - `b1.2` - - `b1.2.1`\n\n + - `b1.2.1` + + - `b2.2` + - `b2.2.1`\n\n\n """) } - func testNewLine() { + func testLine() { // When - subject.newLine(3) + subject.line(3) // Then XCTAssertEqual(content, "\n\n\n") @@ -114,16 +117,26 @@ final class MarkdownRendererTests: XCTestCase { func testSample1() { // When subject.header("Header", .h3) - subject.list(.begin) - subject.bullet("Different Values 1", indent: .one) - subject.bullet("Value1", indent: .two) - subject.bullet("Value2", indent: .two) - subject.list(.end) - subject.list(.begin) - subject.bullet("Different Values 2", indent: .one) - subject.bullet("Value1", indent: .two) - subject.bullet("Value2", indent: .two) - subject.list(.end) + subject.list { + subject.item { + subject.pre("Different Values 1") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + subject.item { + subject.pre("Different Values 2") + subject.list { + subject.item("Value1") + subject.item("Value2") + } + } + } + subject.header("Header 2", .h3) + subject.list { + subject.item("Test") + } // Then XCTAssertEqual(content, """ @@ -136,7 +149,15 @@ final class MarkdownRendererTests: XCTestCase { - `Different Values 2` - `Value1` - - `Value2`\n\n + - `Value2` + + + + ### Header 2 + + - `Test` + + """) } diff --git a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/Renderer+ContextTests.swift b/Tests/XCDiffCoreTests/ResultRenderer/Renderer/Renderer+ContextTests.swift deleted file mode 100644 index 6a6b043..0000000 --- a/Tests/XCDiffCoreTests/ResultRenderer/Renderer/Renderer+ContextTests.swift +++ /dev/null @@ -1,299 +0,0 @@ -// -// Copyright 2019 Bloomberg Finance L.P. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -@testable import XCDiffCore -import XCTest - -final class RendererContextTests: XCTestCase { - private var consoleStringOutputBuffer: StringOutputBuffer! - private var consoleRenderer: ConsoleRenderer! - - private var markdownOutputBuffer: StringOutputBuffer! - private var markdownRenderer: MarkdownRenderer! - - private var subject: ForwardRenderer! - - override func setUp() { - super.setUp() - - consoleStringOutputBuffer = StringOutputBuffer() - consoleRenderer = ConsoleRenderer(output: consoleStringOutputBuffer.any()) - - markdownOutputBuffer = StringOutputBuffer() - markdownRenderer = MarkdownRenderer(output: markdownOutputBuffer.any()) - - subject = ForwardRenderer(consoleRenderer, markdownRenderer) - } - - func testOnlyInFirstHeader_whenNoCount() { - // When - subject.onlyInFirstHeader() - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Only in first:\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Only in first:\n\n - """) - } - - func testOnlyInFirstHeader_whenCount5() { - // When - subject.onlyInFirstHeader(count: 5) - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Only in first (5):\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Only in first (5):\n\n - """) - } - - func testOnlyInSecondHeader_whenNoCount() { - // When - subject.onlyInSecondHeader() - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Only in second:\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Only in second:\n\n - """) - } - - func testOnlyInSecondHeader_whenCount5() { - // When - subject.onlyInSecondHeader(count: 5) - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Only in second (5):\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Only in second (5):\n\n - """) - } - - func testDifferentValuesHeader_whenNoCount() { - // When - subject.differentValuesHeader() - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Value mismatch:\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Value mismatch:\n\n - """) - } - - func testDifferentValuesHeader_whenCount5() { - // When - subject.differentValuesHeader(count: 5) - - // Then - XCTAssertEqual(consoleContent, """ - \n⚠️ Value mismatch (5):\n\n - """) - - XCTAssertEqual(markdownContent, """ - \n### ⚠️ Value mismatch (5):\n\n - """) - } - - func testSuccessHeader() { - // When - subject.successHeader("Header") - - // Then - XCTAssertEqual(consoleContent, """ - ✅ Header\n - """) - - XCTAssertEqual(markdownContent, """ - \n## ✅ Header\n\n - """) - } - - func testErrorHeader() { - // When - subject.errorHeader("Header") - - // Then - XCTAssertEqual(consoleContent, """ - ❌ Header\n - """) - - XCTAssertEqual(markdownContent, """ - \n## ❌ Header\n\n - """) - } - - func testConsoleRenderer_whenSample1() { - // When - setupSample1() - - // Then - XCTAssertEqual(consoleContent, """ - ✅ Success1 - ✅ Success2 - ❌ Error1 - - ⚠️ Only in first (2): - - • B1 - • B2\n - - ⚠️ Only in second (2): - - • C1 - • C2\n - - ⚠️ Value mismatch (2): - - • Context1 - ◦ V1.1 - ◦ V1.1 - - • Context2 - ◦ V2.1 - ◦ V2.1\n - - ✅ Success3\n - """) - } - - func testMarkdownRenderer_whenSample1() { - // When - setupSample1() - - // Then - XCTAssertEqual(markdownContent, """ - - ## ✅ Success1\n - - ## ✅ Success2\n - - ## ❌ Error1\n - - ### ⚠️ Only in first (2): - - - `B1` - - `B2`\n - - ### ⚠️ Only in second (2): - - - `C1` - - `C2`\n - - ### ⚠️ Value mismatch (2): - - - `Context1` - - `V1.1` - - `V1.1` - - - `Context2` - - `V2.1` - - `V2.1`\n\n - - ## ✅ Success3\n\n - """) - } - - // MARK: - Private - - private func setupSample1() { - subject.successHeader("Success1") - subject.successHeader("Success2") - subject.errorHeader("Error1") - subject.onlyInFirstHeader(count: 2) - subject.list(.begin) - subject.bullet("B1", indent: .one) - subject.bullet("B2", indent: .one) - subject.list(.end) - subject.onlyInSecondHeader(count: 2) - subject.list(.begin) - subject.bullet("C1", indent: .one) - subject.bullet("C2", indent: .one) - subject.list(.end) - subject.differentValuesHeader(count: 2) - subject.list(.begin) - subject.bullet("Context1", indent: .one) - subject.bullet("V1.1", indent: .two) - subject.bullet("V1.1", indent: .two) - subject.list(.end) - subject.list(.begin) - subject.bullet("Context2", indent: .one) - subject.bullet("V2.1", indent: .two) - subject.bullet("V2.1", indent: .two) - subject.list(.end) - subject.newLine(1) - subject.successHeader("Success3") - } - - private var consoleContent: String { - return consoleStringOutputBuffer.flush() - } - - private var markdownContent: String { - return markdownOutputBuffer.flush() - } -} - -final class ForwardRenderer: Renderer { - private let renderers: [Renderer] - - init(_ renderers: Renderer...) { - self.renderers = renderers - } - - // MARK: - Renderer - - func text(_ text: String) { - forEach { $0.text(text) } - } - - func list(_ element: RendererElement.List) { - forEach { $0.list(element) } - } - - func bullet(_ text: String, indent: RendererElement.Indent) { - forEach { $0.bullet(text, indent: indent) } - } - - func newLine(_ count: Int) { - forEach { $0.newLine(count) } - } - - func header(_ text: String, _ header: RendererElement.Header) { - forEach { $0.header(text, header) } - } - - // MARK: - Private - - private func forEach(_ closure: (Renderer) -> Void) { - renderers.forEach(closure) - } -}