Test Tags generation
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
This commit is contained in:
parent
6aef8bc2de
commit
2a144fc00e
@ -126,13 +126,13 @@ class TagsGenerator {
|
|||||||
let footer = " }"
|
let footer = " }"
|
||||||
|
|
||||||
if targets.contains(Tags.TargetType.matomo) {
|
if targets.contains(Tags.TargetType.matomo) {
|
||||||
header = "func configure(sideId: String, url: String) {"
|
header = "func configure(siteId: String, url: String) {"
|
||||||
} else if targets.contains(Tags.TargetType.firebase) {
|
} else if targets.contains(Tags.TargetType.firebase) {
|
||||||
header = "func configure() {"
|
header = "func configure() {"
|
||||||
}
|
}
|
||||||
|
|
||||||
if targets.contains(Tags.TargetType.matomo) {
|
if targets.contains(Tags.TargetType.matomo) {
|
||||||
content.append(" managers.append(MatomoAnalyticsManager(siteId: sideId, url: url))\n")
|
content.append(" managers.append(MatomoAnalyticsManager(siteId: siteId, url: url))")
|
||||||
}
|
}
|
||||||
if targets.contains(Tags.TargetType.firebase) {
|
if targets.contains(Tags.TargetType.firebase) {
|
||||||
content.append(" managers.append(FirebaseAnalyticsManager())")
|
content.append(" managers.append(FirebaseAnalyticsManager())")
|
||||||
|
@ -144,6 +144,22 @@ final class StringsFileGeneratorTests: XCTestCase {
|
|||||||
|
|
||||||
extension GenStrings {
|
extension GenStrings {
|
||||||
|
|
||||||
|
enum KeyStrings: String {
|
||||||
|
case s1_def_one = "s1_def_one"
|
||||||
|
case s1_def_two = "s1_def_two"
|
||||||
|
case s2_def_one = "s2_def_one"
|
||||||
|
case s2_def_two = "s2_def_two"
|
||||||
|
|
||||||
|
var keyPath: KeyPath<GenStrings, String> {
|
||||||
|
switch self {
|
||||||
|
case .s1_def_one: return \\GenStrings.s1_def_one
|
||||||
|
case .s1_def_two: return \\GenStrings.s1_def_two
|
||||||
|
case .s2_def_one: return \\GenStrings.s2_def_one
|
||||||
|
case .s2_def_two: return \\GenStrings.s2_def_two
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - section_one
|
// MARK: - section_one
|
||||||
|
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
@ -174,6 +190,9 @@ final class StringsFileGeneratorTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,6 +240,22 @@ final class StringsFileGeneratorTests: XCTestCase {
|
|||||||
|
|
||||||
extension GenStrings {
|
extension GenStrings {
|
||||||
|
|
||||||
|
enum KeyStrings: String {
|
||||||
|
case s1_def_one = "s1_def_one"
|
||||||
|
case s1_def_two = "s1_def_two"
|
||||||
|
case s2_def_one = "s2_def_one"
|
||||||
|
case s2_def_two = "s2_def_two"
|
||||||
|
|
||||||
|
var keyPath: KeyPath<GenStrings, String> {
|
||||||
|
switch self {
|
||||||
|
case .s1_def_one: return \\GenStrings.s1_def_one
|
||||||
|
case .s1_def_two: return \\GenStrings.s1_def_two
|
||||||
|
case .s2_def_one: return \\GenStrings.s2_def_one
|
||||||
|
case .s2_def_two: return \\GenStrings.s2_def_two
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - section_one
|
// MARK: - section_one
|
||||||
|
|
||||||
/// Translation in fr :
|
/// Translation in fr :
|
||||||
@ -251,6 +286,9 @@ final class StringsFileGeneratorTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
//
|
|
||||||
// TagsGeneratorTests.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Thibaut Schmitt on 06/09/2022.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import XCTest
|
|
||||||
import ToolCore
|
|
||||||
|
|
||||||
@testable import ResgenSwift
|
|
||||||
|
|
||||||
final class TagsGeneratorTests: XCTestCase {
|
|
||||||
|
|
||||||
private func getDefinition(name: String, lang: String, tags: [String]) -> TagDefinition {
|
|
||||||
let definition = TagDefinition(title: name)
|
|
||||||
definition.tags = tags
|
|
||||||
// definition.translations = [lang: "Some translation"]
|
|
||||||
return definition
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGeneratedExtensionContent() {
|
|
||||||
// Given
|
|
||||||
let sectionOne = TagSection(name: "section_one")
|
|
||||||
sectionOne.definitions = [
|
|
||||||
getDefinition(name: "s1_def_one", lang: "ium", tags: ["ios","iosonly"]),
|
|
||||||
getDefinition(name: "s1_def_two", lang: "ium", tags: ["ios","iosonly"]),
|
|
||||||
]
|
|
||||||
|
|
||||||
let sectionTwo = TagSection(name: "section_two")
|
|
||||||
sectionTwo.definitions = [
|
|
||||||
getDefinition(name: "s2_def_one", lang: "ium", tags: ["ios","iosonly"]),
|
|
||||||
getDefinition(name: "s2_def_two", lang: "ium", tags: ["droid","droidonly"])
|
|
||||||
]
|
|
||||||
|
|
||||||
let sectionThree = TagSection(name: "section_three")
|
|
||||||
sectionThree.definitions = [
|
|
||||||
getDefinition(name: "s3_def_one", lang: "ium", tags: ["droid","droidonly"]),
|
|
||||||
getDefinition(name: "s3_def_two", lang: "ium", tags: ["droid","droidonly"])
|
|
||||||
]
|
|
||||||
|
|
||||||
// When
|
|
||||||
let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
|
||||||
targets: [Tags.TargetType.firebase],
|
|
||||||
tags: ["ios", "iosonly"],
|
|
||||||
staticVar: false,
|
|
||||||
extensionName: "GenTags")
|
|
||||||
// Expect Tags
|
|
||||||
let expect = """
|
|
||||||
// Generated by ResgenSwift.Strings.Tags \(ResgenSwiftVersion)
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
extension GenTags {
|
|
||||||
// MARK: - section_one
|
|
||||||
|
|
||||||
/// Translation in ium :
|
|
||||||
/// Some translation
|
|
||||||
var s1_def_one: String {
|
|
||||||
"Some translation"
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Translation in ium :
|
|
||||||
/// Some translation
|
|
||||||
var s1_def_two: String {
|
|
||||||
"Some translation"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - section_two
|
|
||||||
|
|
||||||
/// Translation in ium :
|
|
||||||
/// Some translation
|
|
||||||
var s2_def_one: String {
|
|
||||||
"Some translation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
|
||||||
}
|
|
||||||
}
|
|
135
Tests/ResgenSwiftTests/Tags/DiffString.swift
Normal file
135
Tests/ResgenSwiftTests/Tags/DiffString.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
/// Find first differing character between two strings
|
||||||
|
///
|
||||||
|
/// :param: s1 First String
|
||||||
|
/// :param: s2 Second String
|
||||||
|
///
|
||||||
|
/// :returns: .DifferenceAtIndex(i) or .NoDifference
|
||||||
|
public func firstDifferenceBetweenStrings(s1: NSString, s2: NSString) -> FirstDifferenceResult {
|
||||||
|
let len1 = s1.length
|
||||||
|
let len2 = s2.length
|
||||||
|
|
||||||
|
let lenMin = min(len1, len2)
|
||||||
|
|
||||||
|
for i in 0..<lenMin {
|
||||||
|
if s1.character(at: i) != s2.character(at: i) {
|
||||||
|
return .DifferenceAtIndex(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len1 < len2 {
|
||||||
|
return .DifferenceAtIndex(len1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len2 < len1 {
|
||||||
|
return .DifferenceAtIndex(len2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .NoDifference
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a formatted String representation of difference between strings
|
||||||
|
///
|
||||||
|
/// :param: s1 First string
|
||||||
|
/// :param: s2 Second string
|
||||||
|
///
|
||||||
|
/// :returns: a string, possibly containing significant whitespace and newlines
|
||||||
|
public func prettyFirstDifferenceBetweenStrings(s1: String, s2: String) -> String {
|
||||||
|
let firstDifferenceResult = firstDifferenceBetweenStrings(s1: s1 as NSString, s2: s2 as NSString)
|
||||||
|
return prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: firstDifferenceResult, s1: s1 as NSString, s2: s2 as NSString) as String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a formatted String representation of a FirstDifferenceResult for two strings
|
||||||
|
///
|
||||||
|
/// :param: firstDifferenceResult FirstDifferenceResult
|
||||||
|
/// :param: s1 First string used in generation of firstDifferenceResult
|
||||||
|
/// :param: s2 Second string used in generation of firstDifferenceResult
|
||||||
|
///
|
||||||
|
/// :returns: a printable string, possibly containing significant whitespace and newlines
|
||||||
|
public func prettyDescriptionOfFirstDifferenceResult(firstDifferenceResult: FirstDifferenceResult, s1: NSString, s2: NSString) -> NSString {
|
||||||
|
|
||||||
|
func diffString(index: Int, s1: NSString, s2: NSString) -> NSString {
|
||||||
|
let markerArrow = "\u{2b06}" // "⬆"
|
||||||
|
let ellipsis = "\u{2026}" // "…"
|
||||||
|
/// Given a string and a range, return a string representing that substring.
|
||||||
|
///
|
||||||
|
/// If the range starts at a position other than 0, an ellipsis
|
||||||
|
/// will be included at the beginning.
|
||||||
|
///
|
||||||
|
/// If the range ends before the actual end of the string,
|
||||||
|
/// an ellipsis is added at the end.
|
||||||
|
func windowSubstring(s: NSString, range: NSRange) -> String {
|
||||||
|
let validRange = NSMakeRange(range.location, min(range.length, s.length - range.location))
|
||||||
|
let substring = s.substring(with: validRange)
|
||||||
|
|
||||||
|
let prefix = range.location > 0 ? ellipsis : ""
|
||||||
|
let suffix = (s.length - range.location > range.length) ? ellipsis : ""
|
||||||
|
|
||||||
|
return "\(prefix)\(substring)\(suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show this many characters before and after the first difference
|
||||||
|
let windowPrefixLength = 10
|
||||||
|
let windowSuffixLength = 10
|
||||||
|
let windowLength = windowPrefixLength + 1 + windowSuffixLength
|
||||||
|
|
||||||
|
let windowIndex = max(index - windowPrefixLength, 0)
|
||||||
|
let windowRange = NSMakeRange(windowIndex, windowLength)
|
||||||
|
|
||||||
|
let sub1 = windowSubstring(s: s1, range: windowRange)
|
||||||
|
let sub2 = windowSubstring(s: s2, range: windowRange)
|
||||||
|
|
||||||
|
let markerPosition = min(windowSuffixLength, index) + (windowIndex > 0 ? 1 : 0)
|
||||||
|
|
||||||
|
let markerPrefix = String(repeating: " " as Character, count: markerPosition)
|
||||||
|
let markerLine = "\(markerPrefix)\(markerArrow)"
|
||||||
|
|
||||||
|
return "Difference at index \(index):\n\(sub1)\n\(sub2)\n\(markerLine)" as NSString
|
||||||
|
}
|
||||||
|
|
||||||
|
switch firstDifferenceResult {
|
||||||
|
case .NoDifference: return "No difference"
|
||||||
|
case .DifferenceAtIndex(let index): return diffString(index: index, s1: s1, s2: s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Result type for firstDifferenceBetweenStrings()
|
||||||
|
public enum FirstDifferenceResult {
|
||||||
|
/// Strings are identical
|
||||||
|
case NoDifference
|
||||||
|
|
||||||
|
/// Strings differ at the specified index.
|
||||||
|
///
|
||||||
|
/// This could mean that characters at the specified index are different,
|
||||||
|
/// or that one string is longer than the other
|
||||||
|
case DifferenceAtIndex(Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FirstDifferenceResult {
|
||||||
|
/// Textual representation of a FirstDifferenceResult
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .NoDifference:
|
||||||
|
return "NoDifference"
|
||||||
|
case .DifferenceAtIndex(let index):
|
||||||
|
return "DifferenceAtIndex(\(index))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Textual representation of a FirstDifferenceResult for debugging purposes
|
||||||
|
public var debugDescription: String {
|
||||||
|
return self.description
|
||||||
|
}
|
||||||
|
}
|
162
Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift
Normal file
162
Tests/ResgenSwiftTests/Tags/TagDefinitionTests.swift
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
//
|
||||||
|
// TagDefinitionTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class TagDefinitionTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Match line
|
||||||
|
|
||||||
|
func testMatchingTagDefinition() {
|
||||||
|
// Given
|
||||||
|
let line = "[definition_name]"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let definition = TagDefinition.match(line)
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertNotNil(definition)
|
||||||
|
XCTAssertEqual(definition?.title, "definition_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingTagDefinition() {
|
||||||
|
// Given
|
||||||
|
let line1 = "definition_name"
|
||||||
|
let line2 = "[definition_name"
|
||||||
|
let line3 = "definition_name]"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let definition1 = TagDefinition.match(line1)
|
||||||
|
let definition2 = TagDefinition.match(line2)
|
||||||
|
let definition3 = TagDefinition.match(line3)
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertNil(definition1)
|
||||||
|
XCTAssertNil(definition2)
|
||||||
|
XCTAssertNil(definition3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matching tags
|
||||||
|
|
||||||
|
func testMatchingTags() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["ios"])
|
||||||
|
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["iosonly"])
|
||||||
|
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["notranslation"])
|
||||||
|
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertTrue(match1)
|
||||||
|
XCTAssertTrue(match2)
|
||||||
|
XCTAssertTrue(match3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingTags() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.tags = ["ios","iosonly","notranslation"]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = definition.hasOneOrMoreMatchingTags(inputTags: ["droid"])
|
||||||
|
let match2 = definition.hasOneOrMoreMatchingTags(inputTags: ["droidonly"])
|
||||||
|
let match3 = definition.hasOneOrMoreMatchingTags(inputTags: ["azerty"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertFalse(match1)
|
||||||
|
XCTAssertFalse(match2)
|
||||||
|
XCTAssertFalse(match3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Raw properties
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyScreen() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
definition.name = "Ecran un"
|
||||||
|
definition.type = "screen"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyScreen = definition.getProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectScreen = """
|
||||||
|
func logScreenEcranUn() {
|
||||||
|
logScreen(name: "Ecran un", path: "ecran_un/")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawPropertyEvent() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
definition.name = "Ecran un"
|
||||||
|
definition.type = "event"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyEvent = definition.getProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectEvent = """
|
||||||
|
func logEventEcranUn() {
|
||||||
|
logEvent(name: "Ecran un")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyScreen() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
definition.name = "Ecran un"
|
||||||
|
definition.type = "screen"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyScreen = definition.getStaticProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectScreen = """
|
||||||
|
static func logScreenEcranUn() {
|
||||||
|
logScreen(name: "Ecran un", path: "ecran_un/")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyScreen.adaptForXCTest(), expectScreen.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedRawStaticPropertyEvent() {
|
||||||
|
// Given
|
||||||
|
let definition = TagDefinition(title: "definition_name")
|
||||||
|
definition.path = "ecran_un/"
|
||||||
|
definition.name = "Ecran un"
|
||||||
|
definition.type = "event"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let propertyEvent = definition.getStaticProperty()
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
let expectEvent = """
|
||||||
|
static func logEventEcranUn() {
|
||||||
|
logEvent(name: "Ecran un")
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
XCTAssertEqual(propertyEvent.adaptForXCTest(), expectEvent.adaptForXCTest())
|
||||||
|
}
|
||||||
|
}
|
104
Tests/ResgenSwiftTests/Tags/TagSectionTests.swift
Normal file
104
Tests/ResgenSwiftTests/Tags/TagSectionTests.swift
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// TagSectionTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Loris Perret on 06/12/2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class TagSectionTests: XCTestCase {
|
||||||
|
|
||||||
|
// MARK: - Match line
|
||||||
|
|
||||||
|
func testMatchingTagSection() {
|
||||||
|
// Given
|
||||||
|
let line = "[[section_name]]"
|
||||||
|
|
||||||
|
// When
|
||||||
|
let section = TagSection.match(line)
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertNotNil(section)
|
||||||
|
XCTAssertEqual(section?.name, "section_name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingTagSection() {
|
||||||
|
// Given
|
||||||
|
let lines = ["section_name",
|
||||||
|
"[section_name]",
|
||||||
|
"[section_name",
|
||||||
|
"[[section_name",
|
||||||
|
"[[section_name]",
|
||||||
|
"section_name]",
|
||||||
|
"section_name]]",
|
||||||
|
"[section_name]]"]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let matches = lines.compactMap { TagSection.match($0) }
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertEqual(matches.isEmpty, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matching tags
|
||||||
|
|
||||||
|
func testMatchingTags() {
|
||||||
|
// Given
|
||||||
|
let section = TagSection(name: "section_name")
|
||||||
|
section.definitions = [
|
||||||
|
{
|
||||||
|
let def = TagDefinition(title: "definition_name")
|
||||||
|
def.tags = ["ios","iosonly"]
|
||||||
|
return def
|
||||||
|
}(),
|
||||||
|
{
|
||||||
|
let def = TagDefinition(title: "definition_name_two")
|
||||||
|
def.tags = ["droid","droidonly"]
|
||||||
|
return def
|
||||||
|
}()
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = section.hasOneOrMoreMatchingTags(tags: ["ios"])
|
||||||
|
let match2 = section.hasOneOrMoreMatchingTags(tags: ["iosonly"])
|
||||||
|
let match3 = section.hasOneOrMoreMatchingTags(tags: ["droid"])
|
||||||
|
let match4 = section.hasOneOrMoreMatchingTags(tags: ["droidonly"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertTrue(match1)
|
||||||
|
XCTAssertTrue(match2)
|
||||||
|
XCTAssertTrue(match3)
|
||||||
|
XCTAssertTrue(match4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNotMatchingTags() {
|
||||||
|
// Given
|
||||||
|
let section = TagSection(name: "section_name")
|
||||||
|
section.definitions = [
|
||||||
|
{
|
||||||
|
let def = TagDefinition(title: "definition_name")
|
||||||
|
def.tags = ["ios","iosonly"]
|
||||||
|
return def
|
||||||
|
}(),
|
||||||
|
{
|
||||||
|
let def = TagDefinition(title: "definition_name_two")
|
||||||
|
def.tags = ["droid","droidonly"]
|
||||||
|
return def
|
||||||
|
}()
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
let match1 = section.hasOneOrMoreMatchingTags(tags: ["web"])
|
||||||
|
let match2 = section.hasOneOrMoreMatchingTags(tags: ["webonly"])
|
||||||
|
let match3 = section.hasOneOrMoreMatchingTags(tags: ["azerty"])
|
||||||
|
|
||||||
|
// Expect
|
||||||
|
XCTAssertFalse(match1)
|
||||||
|
XCTAssertFalse(match2)
|
||||||
|
XCTAssertFalse(match3)
|
||||||
|
}
|
||||||
|
}
|
454
Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift
Normal file
454
Tests/ResgenSwiftTests/Tags/TagsGeneratorTests.swift
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
//
|
||||||
|
// TagsGeneratorTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Thibaut Schmitt on 06/09/2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
import ToolCore
|
||||||
|
|
||||||
|
@testable import ResgenSwift
|
||||||
|
|
||||||
|
final class TagsGeneratorTests: XCTestCase {
|
||||||
|
|
||||||
|
private func getTagDefinition(title: String, path: String, name: String, type: String, tags: [String]) -> TagDefinition {
|
||||||
|
let definition = TagDefinition(title: title)
|
||||||
|
definition.path = path
|
||||||
|
definition.name = name
|
||||||
|
definition.type = type
|
||||||
|
definition.tags = tags
|
||||||
|
return definition
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentFirebase() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = TagSection(name: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]),
|
||||||
|
getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = TagSection(name: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]),
|
||||||
|
getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = TagSection(name: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]),
|
||||||
|
getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
TagsGenerator.targets = [Tags.TargetType.firebase]
|
||||||
|
let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenTags")
|
||||||
|
// Expect Tags
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Tags 1.2
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Firebase
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(name: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name])
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(name: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterValue: name
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable }
|
||||||
|
|
||||||
|
func configure() {
|
||||||
|
managers.append(FirebaseAnalyticsManager())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(name: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(name: name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(name: "s1 def one", path: "s1_def_one/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(name: "s1 def two")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(name: "s2 def one", path: "s2_def_one/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentMatomo() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = TagSection(name: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]),
|
||||||
|
getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = TagSection(name: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]),
|
||||||
|
getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = TagSection(name: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]),
|
||||||
|
getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
TagsGenerator.targets = [Tags.TargetType.matomo]
|
||||||
|
let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenTags")
|
||||||
|
// Expect Tags
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Tags 1.2
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(name: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \\(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \\(siteId)")
|
||||||
|
tracker = MatomoTracker(siteId: siteId, baseURL: URL(string: url)!)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(name: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: "category",
|
||||||
|
action: "action",
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable }
|
||||||
|
|
||||||
|
func configure(siteId: String, url: String) {
|
||||||
|
managers.append(MatomoAnalyticsManager(siteId: siteId, url: url))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(name: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(name: name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(name: "s1 def one", path: "s1_def_one/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(name: "s1 def two")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(name: "s2 def one", path: "s2_def_one/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratedExtensionContentMatomoAndFirebase() {
|
||||||
|
// Given
|
||||||
|
let sectionOne = TagSection(name: "section_one")
|
||||||
|
sectionOne.definitions = [
|
||||||
|
getTagDefinition(title: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: "screen", tags: ["ios", "iosonly"]),
|
||||||
|
getTagDefinition(title: "s1_def_two", path: "s1_def_two/", name: "s1 def two", type: "event", tags: ["ios", "iosonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionTwo = TagSection(name: "section_two")
|
||||||
|
sectionTwo.definitions = [
|
||||||
|
getTagDefinition(title: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: "screen", tags: ["ios","iosonly"]),
|
||||||
|
getTagDefinition(title: "s2_def_two", path: "s2_def_two/", name: "s2 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
let sectionThree = TagSection(name: "section_three")
|
||||||
|
sectionThree.definitions = [
|
||||||
|
getTagDefinition(title: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: "screen", tags: ["droid","droidonly"]),
|
||||||
|
getTagDefinition(title: "s3_def_two", path: "s3_def_two/", name: "s3 def two", type: "event", tags: ["droid","droidonly"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
// When
|
||||||
|
TagsGenerator.targets = [Tags.TargetType.matomo, Tags.TargetType.firebase]
|
||||||
|
let extensionContent = TagsGenerator.getExtensionContent(sections: [sectionOne, sectionTwo, sectionThree],
|
||||||
|
tags: ["ios", "iosonly"],
|
||||||
|
staticVar: false,
|
||||||
|
extensionName: "GenTags")
|
||||||
|
// Expect Tags
|
||||||
|
let expect = """
|
||||||
|
// Generated by ResgenSwift.Tags 1.2
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MatomoTracker
|
||||||
|
import Firebase
|
||||||
|
|
||||||
|
// MARK: - Protocol
|
||||||
|
|
||||||
|
protocol AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String)
|
||||||
|
func logEvent(name: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Matomo
|
||||||
|
|
||||||
|
class MatomoAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var tracker: MatomoTracker
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
init(siteId: String, url: String) {
|
||||||
|
debugPrint("[Matomo service] Server URL: \\(url)")
|
||||||
|
debugPrint("[Matomo service] Site ID: \\(siteId)")
|
||||||
|
tracker = MatomoTracker(siteId: siteId, baseURL: URL(string: url)!)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.dispatchInterval = 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tracker.logger = DefaultLogger(minLevel: .verbose)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")")
|
||||||
|
debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
guard let trackerUrl = tracker.contentBase?.absoluteString else { return }
|
||||||
|
let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS")
|
||||||
|
tracker.track(
|
||||||
|
view: [name],
|
||||||
|
url: urlString
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(name: String) {
|
||||||
|
guard !tracker.isOptedOut else { return }
|
||||||
|
tracker.track(
|
||||||
|
eventWithCategory: "category",
|
||||||
|
action: "action",
|
||||||
|
name: name,
|
||||||
|
number: nil,
|
||||||
|
url: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Firebase
|
||||||
|
|
||||||
|
class FirebaseAnalyticsManager: AnalyticsManagerProtocol {
|
||||||
|
func logScreen(name: String, path: String) {
|
||||||
|
Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: name])
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEvent(name: String) {
|
||||||
|
var parameters = [
|
||||||
|
AnalyticsParameterValue: name
|
||||||
|
]
|
||||||
|
|
||||||
|
Analytics.logEvent(AnalyticsEventSelectContent, parameters: parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Manager
|
||||||
|
|
||||||
|
class AnalyticsManager {
|
||||||
|
static var shared = AnalyticsManager()
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
var managers: [AnalyticsManagerProtocol] = []
|
||||||
|
|
||||||
|
private var isEnabled: Bool = true
|
||||||
|
|
||||||
|
// MARK: - Methods
|
||||||
|
|
||||||
|
func setAnalyticsEnabled(_ enable: Bool) { isEnabled = enable }
|
||||||
|
|
||||||
|
func configure(siteId: String, url: String) {
|
||||||
|
managers.append(MatomoAnalyticsManager(siteId: siteId, url: url))
|
||||||
|
managers.append(FirebaseAnalyticsManager())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logScreen(name: String, path: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logScreen(name: name, path: path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logEvent(name: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
managers.forEach { manager in
|
||||||
|
manager.logEvent(name: name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_one
|
||||||
|
|
||||||
|
func logScreenS1DefOne() {
|
||||||
|
logScreen(name: "s1 def one", path: "s1_def_one/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func logEventS1DefTwo() {
|
||||||
|
logEvent(name: "s1 def two")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - section_two
|
||||||
|
|
||||||
|
func logScreenS2DefOne() {
|
||||||
|
logScreen(name: "s2 def one", path: "s2_def_one/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extensionContent != expect {
|
||||||
|
print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect))
|
||||||
|
}
|
||||||
|
XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user