From 2288108353bb429bca4422636416449ecf0de1ba Mon Sep 17 00:00:00 2001 From: PJ Fechner Date: Wed, 8 Jan 2025 21:59:31 -0600 Subject: [PATCH] Testing --- .github/workflows/multiplatform-tests.yml | 4 +- Package.swift | 9 +- .../CodingKeys/CodingKeyTypes.swift | 49 ++++++--- .../Convenience/ConvenienceExtensions.swift | 24 ++-- .../CodableWrapperMacros/ErrorHandling.swift | 7 +- .../TypeMacroContainers.swift | 26 +++-- .../StaticCoders/BoolCoding.swift | 1 - .../StaticCoders/DateCoding.swift | 2 +- .../StaticCoders/FloatingPointCoding.swift | 4 - .../CodingKeyMacroErrorTests.swift | 104 +++++++++++++++++- Tests/CodableWrapperMacrosTests/Helpers.swift | 14 ++- .../KeyConverterTests.swift | 19 ++++ .../Decoder/BoolDecodingTests.swift | 29 +++++ .../Decoder/DataDecodingTests.swift | 23 ++++ .../Decoder/DateDecodingTests.swift | 22 ++++ .../Decoder/DecodingTestSpec.swift | 22 +++- .../Decoder/OptionalDecodingTests.swift | 94 +++++++++++++++- .../Encoder/EncodingTestSpec.swift | 7 +- .../Encoder/OptionalEncodingTests.swift | 21 +++- .../PartialImplementationTests.swift | 49 ++++----- Tests/LinuxMain.swift | 62 +++++------ 21 files changed, 458 insertions(+), 134 deletions(-) diff --git a/.github/workflows/multiplatform-tests.yml b/.github/workflows/multiplatform-tests.yml index a40652d..782c835 100644 --- a/.github/workflows/multiplatform-tests.yml +++ b/.github/workflows/multiplatform-tests.yml @@ -48,6 +48,6 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build - run: swift build -v + run: swift build -v - name: Run swift tests - run: swift test -v + run: swift test --enable-experimental-swift-testing -v diff --git a/Package.swift b/Package.swift index 23a60e5..4537a8f 100644 --- a/Package.swift +++ b/Package.swift @@ -27,21 +27,24 @@ let package = Package( dependencies: [ .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "13.0.0")), .package(url: "https://github.com/Quick/Quick.git", .upToNextMajor(from: "7.6.0")), +// .package(url: "https://github.com/swiftlang/swift-testing.git", .upToNextMajor(from: "6.0.0")), // swiftlint is kinda big to pull in and build right now...maybe later // .package(url: "https://github.com/realm/SwiftLint.git", .upToNextMajor(from: "0.52.0")), .package(url: "https://github.com/swiftlang/swift-syntax.git", "508.0.0"..."600.0.1"), // For testing different versions of swift-syntax -// .package(url: "https://github.com/apple/swift-syntax.git", .upToNextMajor(from: "509.0.0")) +// .package(url: "https://github.com/apple/swift-syntax.git", .upToNextMajor(from: "510.0.0")) ], targets: [ .target( name: "CodableWrappers", - dependencies: ["CodableWrapperMacros"], + dependencies: ["CodableWrapperMacros", +// .product(name: "Testing", package: "swift-testing"), + ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") ]), -// plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]), +// plugins: [.plugin(name: "SwiftLintPlugin", package: "SwifutLint")]), .testTarget( name: "CodableWrappersTests", diff --git a/Sources/CodableWrapperMacros/CodingKeys/CodingKeyTypes.swift b/Sources/CodableWrapperMacros/CodingKeys/CodingKeyTypes.swift index c7c975c..db506dc 100644 --- a/Sources/CodableWrapperMacros/CodingKeys/CodingKeyTypes.swift +++ b/Sources/CodableWrapperMacros/CodingKeys/CodingKeyTypes.swift @@ -82,7 +82,7 @@ enum CodingKeyCase { /// custom casing case custom((String) -> (String)) - var separator: String { + var separator: String? { switch self { case .noChanges, .camelCase, .flatCase, .pascalCase, .upperCase: "" @@ -90,27 +90,35 @@ enum CodingKeyCase { "_" case .kebabCase, .camelKebabCase, .pascalKebabCase, .screamingKebabCase: "-" - case .custom(_): - "" + case .custom: + nil + } + } + + var caseVariant: CaseVariant? { + switch self { + case .flatCase, .snakeCase, .kebabCase: + .lowerCase + case .camelCase, .camelSnakeCase, .camelKebabCase: + .camelCase + case .pascalCase, .pascalSnakeCase, .pascalKebabCase: + .pascalCase + case .upperCase, .screamingSnakeCase, .screamingKebabCase: + .upperCase + case .custom(_), .noChanges: + nil } } func makeKeyValue(from value: String) -> String { switch self { - case .noChanges: value - case .camelCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .camelCase) - case .flatCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .lowerCase) - case .pascalCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .pascalCase) - case .upperCase: KeyConverter.plainCaseConverter.convert(value: value, variant: .upperCase) - case .snakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .lowerCase) - case .camelSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .camelCase) - case .pascalSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .pascalCase) - case .screamingSnakeCase: KeyConverter.snakeCaseConverter.convert(value: value, variant: .upperCase) - case .kebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .lowerCase) - case .camelKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .camelCase) - case .pascalKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .pascalCase) - case .screamingKebabCase: KeyConverter.kebabCaseConverter.convert(value: value, variant: .upperCase) - case .custom(let converter): converter(value) + case .noChanges: return value + case .custom(let converter): return converter(value) + default: + guard let keyConverter = KeyConverter(keyCase: self), let caseVariant else { + return value + } + return keyConverter.convert(value: value, variant: caseVariant) } } } @@ -131,7 +139,12 @@ struct KeyConverter: Sendable { init(separator: String) { self.separator = separator } - + + init?(keyCase: CodingKeyCase) { + guard let separator = keyCase.separator else { return nil } + self.init(separator: separator) + } + func convert(value: String, variant: CaseVariant) -> String { // Remove any special characters at the beginning/end let isAllCaps = value.isAllCaps diff --git a/Sources/CodableWrapperMacros/Convenience/ConvenienceExtensions.swift b/Sources/CodableWrapperMacros/Convenience/ConvenienceExtensions.swift index a60b650..d608d75 100644 --- a/Sources/CodableWrapperMacros/Convenience/ConvenienceExtensions.swift +++ b/Sources/CodableWrapperMacros/Convenience/ConvenienceExtensions.swift @@ -25,9 +25,10 @@ extension MemberBlockItemListSyntax.Element { decl.as(VariableDeclSyntax.self)?.attributes.matching(matching: T.self) ?? [] } - func attributeSyntax(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String { - decl.as(VariableDeclSyntax.self)?.attributes.matchingSyntax(matching: T.self) ?? [] - } + // Not currently used +// func attributeSyntax(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String { +// decl.as(VariableDeclSyntax.self)?.attributes.matchingSyntax(matching: T.self) ?? [] +// } func attributeSyntax(named name: String) -> AttributeSyntax? { attribute(named: name)?.as(AttributeSyntax.self) @@ -57,14 +58,15 @@ extension AttributeListSyntax { } } - func matchingSyntax(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String { - compactMap { - guard let attributeName = $0.identifierName?.trimmingCharacters(in: .whitespacesAndNewlines), let syntax = $0.as(AttributeSyntax.self), let type = T(rawValue: attributeName) else { - return nil - } - return (type, syntax) - } - } + // Not currently used +// func matchingSyntax(matching rawType: T.Type) -> [(T, AttributeSyntax)] where T.RawValue == String { +// compactMap { +// guard let attributeName = $0.identifierName?.trimmingCharacters(in: .whitespacesAndNewlines), let syntax = $0.as(AttributeSyntax.self), let type = T(rawValue: attributeName) else { +// return nil +// } +// return (type, syntax) +// } +// } } extension AttributeListSyntax.Element { var identifierName: String? { diff --git a/Sources/CodableWrapperMacros/ErrorHandling.swift b/Sources/CodableWrapperMacros/ErrorHandling.swift index 5731a67..577b3ad 100644 --- a/Sources/CodableWrapperMacros/ErrorHandling.swift +++ b/Sources/CodableWrapperMacros/ErrorHandling.swift @@ -52,12 +52,9 @@ extension AttributeSyntax { throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .codingKeyValueRequired)]) } let argument = argumentList[argumentList.index(argumentList.startIndex, offsetBy: index)] - guard argument.expression.is(StringLiteralExprSyntax.self) else { - throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .mustBeStringLiteral)]) - } - // Uses the value in the Macro + // Get the value of the macro guard let customKeyValue = argument.expression.as(StringLiteralExprSyntax.self) else { - throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .codingKeyValueRequired)]) + throw DiagnosticsError(diagnostics: [.init(node: self, syntaxError: .mustBeStringLiteral)]) } return ExprSyntax(customKeyValue) diff --git a/Sources/CodableWrapperMacros/TypeMacroContainers.swift b/Sources/CodableWrapperMacros/TypeMacroContainers.swift index 52eba48..7f8e3b6 100644 --- a/Sources/CodableWrapperMacros/TypeMacroContainers.swift +++ b/Sources/CodableWrapperMacros/TypeMacroContainers.swift @@ -28,18 +28,20 @@ struct CodableMacroStructContainer { } func attributeNameCanGenerate(name: String?) -> Bool { - switch name { - case .none: false - case CustomCodable.macroName: - true - case CodingKeyPrefix.macroName: - !codableDefined - case CodingKeySuffix.macroName: - attributeNameCanGenerate(name: CodingKeyPrefix.macroName) && codingKeyPrefix == nil - default: - attributeNameCanGenerate(name: CodingKeySuffix.macroName) - && name == codableAttributes.first?.attributeType.rawValue - } + name == CustomCodable.macroName + // Future work for other attributres being able to generate +// switch name { +// case .none: false +// case CustomCodable.macroName: +// true +// case CodingKeyPrefix.macroName: +// !codableDefined +// case CodingKeySuffix.macroName: +// attributeNameCanGenerate(name: CodingKeyPrefix.macroName) && codingKeyPrefix == nil +// default: +// attributeNameCanGenerate(name: CodingKeySuffix.macroName) +// && name == codableAttributes.first?.attributeType.rawValue +// } } } diff --git a/Sources/CodableWrappers/StaticCoders/BoolCoding.swift b/Sources/CodableWrappers/StaticCoders/BoolCoding.swift index 842e19d..ab500e5 100644 --- a/Sources/CodableWrappers/StaticCoders/BoolCoding.swift +++ b/Sources/CodableWrappers/StaticCoders/BoolCoding.swift @@ -31,7 +31,6 @@ public struct BoolAsStringValueProvider: NonConformingBoolValueProvider { case "true": return true case "false": return false default: - print("Failed to convert \(typeValue) to Boolean return nil") return nil } } diff --git a/Sources/CodableWrappers/StaticCoders/DateCoding.swift b/Sources/CodableWrappers/StaticCoders/DateCoding.swift index 4c2c04a..8e751cd 100644 --- a/Sources/CodableWrappers/StaticCoders/DateCoding.swift +++ b/Sources/CodableWrappers/StaticCoders/DateCoding.swift @@ -63,7 +63,7 @@ extension DateFormatterStaticDecoder { let stringValue = try String(from: decoder) guard let value = dateFormatter.date(from: stringValue) else { - let description = "Expected \(Data.self) but could not convert \(stringValue) to Data" + let description = "Expected \(Date.self) but could not convert \(stringValue) to Date" throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: description)) } diff --git a/Sources/CodableWrappers/StaticCoders/FloatingPointCoding.swift b/Sources/CodableWrappers/StaticCoders/FloatingPointCoding.swift index 8821e5d..26588ad 100644 --- a/Sources/CodableWrappers/StaticCoders/FloatingPointCoding.swift +++ b/Sources/CodableWrappers/StaticCoders/FloatingPointCoding.swift @@ -19,8 +19,6 @@ public protocol NonConformingDecimalValueProvider { /// Uses the `ValueProvider` for (de)serialization of a non-conforming `Float` public struct NonConformingFloatStaticCoder: StaticCoder { - private init() { } - public static func decode(from decoder: Decoder) throws -> Float { guard let stringValue = try? String(from: decoder) else { return try Float(from: decoder) @@ -55,8 +53,6 @@ public struct NonConformingFloatStaticCoder: StaticCoder { - private init() { } - public static func decode(from decoder: Decoder) throws -> Double { guard let stringValue = try? String(from: decoder) else { return try Double(from: decoder) diff --git a/Tests/CodableWrapperMacrosTests/CodingKeyMacroErrorTests.swift b/Tests/CodableWrapperMacrosTests/CodingKeyMacroErrorTests.swift index ba4f523..713e112 100644 --- a/Tests/CodableWrapperMacrosTests/CodingKeyMacroErrorTests.swift +++ b/Tests/CodableWrapperMacrosTests/CodingKeyMacroErrorTests.swift @@ -174,7 +174,7 @@ final class CodingKeyMacroErrorTests: XCTestCase { // macros: testMacros) } - func testThrowsErrorWhenCodingKeyImplemented() throws { + func testThrowsErrorWhenCodingKeysImplemented() throws { assertMacroExpansion( """ @CustomCodable struct TestCodable: Codable { @@ -215,6 +215,21 @@ final class CodingKeyMacroErrorTests: XCTestCase { """, diagnostics: [.init(error: .mustBeStringLiteral, line: 3, column: 5)], macros: testMacros) + + assertMacroExpansion( + """ + @CustomCodable struct TestCodable: Codable { + @CustomCodingKey(1) + let originalKey: String + } + """, + expandedSource: """ + struct TestCodable: Codable { + let originalKey: String + } + """, + diagnostics: [.init(error: .mustBeStringLiteral, line: 2, column: 5)], + macros: testMacros) } func testThrowsDueToEmptyStringCodingKey() throws { @@ -234,7 +249,7 @@ final class CodingKeyMacroErrorTests: XCTestCase { macros: testMacros) } - func testThrowsDueToNoeCodableMacro() throws { + func testThrowsDueToNoCodableMacro() throws { assertMacroExpansion( """ @SnakeCase struct TestCodable: Codable { @@ -250,6 +265,21 @@ final class CodingKeyMacroErrorTests: XCTestCase { diagnostics: [.init(error: .requiresCodableMacro(macroName: "SnakeCase"), line: 1, column: 1)], macros: testMacros) + assertMacroExpansion( + """ + @CodingKeySuffix struct TestCodable: Codable { + @CustomCodingKey("") + let originalKey: String + } + """, + expandedSource: """ + struct TestCodable: Codable { + let originalKey: String + } + """, + diagnostics: [.init(error: .requiresCodableMacro(macroName: "CodingKeySuffix"), line: 1, column: 1)], + macros: testMacros) + assertMacroExpansion( """ @CodingKeyPrefix struct TestCodable: Codable { @@ -265,6 +295,76 @@ final class CodingKeyMacroErrorTests: XCTestCase { diagnostics: [.init(error: .requiresCodableMacro(macroName: "CodingKeyPrefix"), line: 1, column: 1)], macros: testMacros) } + + func testThrowsWhenNotStruct() throws { + assertMacroExpansion( + """ + @CustomCodable class TestCodable: Codable { + let originalKey: String + } + """, + expandedSource: """ + class TestCodable: Codable { + let originalKey: String + } + """, + diagnostics: [.init(error: .canOnlyBeAttachedToStruct(name: "@CustomCodable"), line: 1, column: 1)], + macros: testMacros) + + assertMacroExpansion( + """ + @SnakeCase enum TestCodable: Codable { + case test + } + """, + expandedSource: """ + enum TestCodable: Codable { + case test + } + """, + diagnostics: [.init(error: .canOnlyBeAttachedToPropertiesAndStructs(name: "@SnakeCase"), line: 1, column: 1)], + macros: testMacros) + } + + func testThrowsWithEmptyCodingKey() throws { + assertMacroExpansion( + """ + @CustomCodable @SnakeCase struct TestCodable: Codable { + @CustomCodingKey() + let originalKey: String + } + """, + expandedSource: """ + struct TestCodable: Codable { + let originalKey: String + } + """, + diagnostics: [.init(error: .codingKeyValueRequired, line: 2, column: 5)], + macros: testMacros) + } + + func testThrowsWhenAttachedToFunction() throws { + assertMacroExpansion( + """ + @CustomCodable struct TestCodable: Codable { + let originalKey: String + @CustomCodingKey("test") + func test() -> String { "" } + } + """, + expandedSource: """ + struct TestCodable: Codable { + let originalKey: String + func test() -> String { "" } + + private enum CodingKeys: String, CodingKey { + case originalKey = "originalKey" + } + } + """, + diagnostics: [.init(error: .canOnlyBeAttachedToProperty(name: "@CustomCodingKey"), line: 3, column: 5)], + macros: testMacros) + } } #endif diff --git a/Tests/CodableWrapperMacrosTests/Helpers.swift b/Tests/CodableWrapperMacrosTests/Helpers.swift index 917abf6..cdbe4b4 100644 --- a/Tests/CodableWrapperMacrosTests/Helpers.swift +++ b/Tests/CodableWrapperMacrosTests/Helpers.swift @@ -10,12 +10,18 @@ import XCTest @testable import CodableWrapperMacros extension DiagnosticSpec { - init(warning: SyntaxWarning, line: Int, column: Int) { - self.init(message: warning.localizedDescription, line: line, column: column, severity: .warning) + init(warning: SyntaxWarning, line: Int, column: Int, + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line) { + self.init(message: warning.localizedDescription, line: line, column: column, severity: .warning, + originatorFile: originatorFile, originatorLine: originatorLine) } - init(error: SyntaxError, line: Int, column: Int) { - self.init(message: error.localizedDescription, line: line, column: column, severity: .error) + init(error: SyntaxError, line: Int, column: Int, + originatorFile: StaticString = #filePath, + originatorLine: UInt = #line) { + self.init(message: error.localizedDescription, line: line, column: column, severity: .error, + originatorFile: originatorFile, originatorLine: originatorLine) } } diff --git a/Tests/CodableWrapperMacrosTests/KeyConverterTests.swift b/Tests/CodableWrapperMacrosTests/KeyConverterTests.swift index c952227..a699db8 100644 --- a/Tests/CodableWrapperMacrosTests/KeyConverterTests.swift +++ b/Tests/CodableWrapperMacrosTests/KeyConverterTests.swift @@ -13,6 +13,18 @@ import Nimble final class KeyConverterTests: QuickSpec { override class func spec() { describe("KeyConverter") { + describe("GeneralTests") { + context("CustomCase") { + it("ConvertsCorrectly") { + expect(CodingKeyCase.custom({ $0 + "Test" }).makeKeyValue(from: "Key")) == "KeyTest" + } + } + context("IsNumeric") { + it("ConvertsCorrectly") { + expect(KeyConverter.snakeCaseConverter.convert(value: "12345", variant: .camelCase)) == "12345" + } + } + } describe("SnakeCase") { let converter = KeyConverter.snakeCaseConverter context("WithLowerCase") { @@ -82,6 +94,13 @@ final class KeyConverterTests: QuickSpec { } } } + describe("Invalid Cases") { + context("WithNoCaseSeparator") { + it("ReturnsNil") { + expect(KeyConverter(keyCase: .custom({ $0 }))) == nil + } + } + } } } } diff --git a/Tests/CodableWrappersTests/Decoder/BoolDecodingTests.swift b/Tests/CodableWrappersTests/Decoder/BoolDecodingTests.swift index 0fdbae3..53e7d2c 100644 --- a/Tests/CodableWrappersTests/Decoder/BoolDecodingTests.swift +++ b/Tests/CodableWrappersTests/Decoder/BoolDecodingTests.swift @@ -70,6 +70,11 @@ class BoolDecodingTests: QuickSpec, DecodingTestSpec { } } } + context("WhenInvald") { + it("ThrowsError") { + expect {_ = try self.jsonDecoder.decode(BoolAsStringTextModel.self, from: boolAsStringInvliadTestJSON.data(using: .utf8)!)}.to(throwError()) + } + } } } // MARK: - PListDecoder @@ -129,6 +134,11 @@ class BoolDecodingTests: QuickSpec, DecodingTestSpec { } } } + context("WhenInvald") { + it("ThrowsError") { + expect {_ = try self.plistDecoder.decode(BoolAsStringTextModel.self, from: boolAsStringInvalidTestXML.data(using: .utf8)!)}.to(throwError()) + } + } } } } @@ -190,3 +200,22 @@ private func boolAsStringTestXML(stringValue: String) -> String { """ """ } + +private var boolAsStringInvliadTestJSON: String { """ +{ + "boolValue" : "blabla" +} +""" +} + +private var boolAsStringInvalidTestXML: String { """ + + + + + boolValue + blabla + + +""" +} diff --git a/Tests/CodableWrappersTests/Decoder/DataDecodingTests.swift b/Tests/CodableWrappersTests/Decoder/DataDecodingTests.swift index 2b129fe..7f6e100 100644 --- a/Tests/CodableWrappersTests/Decoder/DataDecodingTests.swift +++ b/Tests/CodableWrappersTests/Decoder/DataDecodingTests.swift @@ -33,6 +33,9 @@ class DataDecodingTests: QuickSpec, DecodingTestSpec { expect(actualModel) == customDataTestModel } } + it("InvalidBase64") { + expect {_ = try self.jsonDecoder.decode(TestBase64Model.self, from: base64InvalidTestJSON.data(using: .utf8)!)}.to(throwError()) + } } // MARK: - PListDecoder context("PListDecoder") { @@ -54,6 +57,9 @@ class DataDecodingTests: QuickSpec, DecodingTestSpec { expect(actualModel) == customDataTestModel } } + it("InvalidBase64") { + expect {_ = try self.plistDecoder.decode(TestBase64Model.self, from: base64InvalidTestXML.data(using: .utf8)!)}.to(throwError()) + } } } } @@ -84,6 +90,23 @@ private let base64TestXML = """ """ +private let base64InvalidTestJSON = """ +{ + "customData" : 123 +} +""" + +private let base64InvalidTestXML = """ + + + + + customData + 123 + + +""" + // MARK: - Custom Mock Data private let customDataTestModel = TestCustomDataModel(customData: "Oh, Hi Mark!".data(using: .utf8)!) diff --git a/Tests/CodableWrappersTests/Decoder/DateDecodingTests.swift b/Tests/CodableWrappersTests/Decoder/DateDecodingTests.swift index 550d6b6..c0d0f0d 100644 --- a/Tests/CodableWrappersTests/Decoder/DateDecodingTests.swift +++ b/Tests/CodableWrappersTests/Decoder/DateDecodingTests.swift @@ -61,6 +61,9 @@ class DateDecodingTests: QuickSpec, DecodingTestSpec { expect(actualModel) == customFormatterTestInstance } } + it("CustomFormatterWithInvalidJSON") { + expect {_ = try self.jsonDecoder.decode(CustomFormatterTestModel.self, from: customFormatterInvalidJSON.data(using: .utf8)!)}.to(throwError()) + } } // MARK: - PListDecoder describe("PListDecoder") { @@ -110,6 +113,9 @@ class DateDecodingTests: QuickSpec, DecodingTestSpec { expect(actualModel) == customFormatterTestInstance } } + it("CustomFormatterWithInvalidJSON") { + expect {_ = try self.plistDecoder.decode(CustomFormatterTestModel.self, from: customFormatterInvalidXML.data(using: .utf8)!)}.to(throwError()) + } } } } @@ -217,6 +223,22 @@ private let customFormatterXML = """ """ +private let customFormatterInvalidJSON = """ +{ + "customFormatDate" : "juf2q9hru4;afiejrsz" +} +""" +private let customFormatterInvalidXML = """ + + + + + customFormatDate + juf2q9hru4;afiejrsz + + +""" + // MARK: - Custom Formatter private struct TestCustomDateFormatter: DateFormatterStaticCoder { static let dateFormatter: DateFormatter = { diff --git a/Tests/CodableWrappersTests/Decoder/DecodingTestSpec.swift b/Tests/CodableWrappersTests/Decoder/DecodingTestSpec.swift index 2bd85ea..d4e6fc8 100644 --- a/Tests/CodableWrappersTests/Decoder/DecodingTestSpec.swift +++ b/Tests/CodableWrappersTests/Decoder/DecodingTestSpec.swift @@ -10,6 +10,20 @@ import Foundation import Quick import Nimble +protocol CodingTests: DecodingTests, EncodingTests { } +extension CodingTests { + + static var emptyJSON: String { "{\n\n}" } + static var emptyPList: String { """ + + + + + + """ + } +} + protocol CodingTestSpec { } @@ -26,12 +40,16 @@ extension CodingTestSpec { } } -protocol DecodingTestSpec: CodingTestSpec { +protocol DecodingTests { static var jsonDecoder: JSONDecoder { get } static var plistDecoder: PropertyListDecoder { get } } -extension DecodingTestSpec { +protocol DecodingTestSpec: DecodingTests, CodingTestSpec { + +} + +extension DecodingTests { static var jsonDecoder: JSONDecoder { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 diff --git a/Tests/CodableWrappersTests/Decoder/OptionalDecodingTests.swift b/Tests/CodableWrappersTests/Decoder/OptionalDecodingTests.swift index 83461f8..84ca258 100644 --- a/Tests/CodableWrappersTests/Decoder/OptionalDecodingTests.swift +++ b/Tests/CodableWrappersTests/Decoder/OptionalDecodingTests.swift @@ -42,6 +42,27 @@ class OptionalDecodingTests: QuickSpec, DecodingTestSpec, EncodingTestSpec { } } } + describe("OptionalStaticCoder") { + // MARK: SecondsSince1970 + describe("SecondsSince1970DateOptionalCoding") { + it("HasNoValue") { + expect {_ = try self.jsonDecoder.decode(SecondsSince1970TestModel.self, from: emptyJSON.data(using: .utf8)!) }.toNot(throwError()) + let decodedModel = try? self.jsonDecoder.decode(SecondsSince1970TestModel.self, from: emptyJSON.data(using: .utf8)!) + expect(decodedModel).toNot(beNil()) + if let actualModel = decodedModel { + expect(actualModel) == secondsSince1970TestEmptyInstance + } + } + it("HasAValue") { + expect {_ = try self.jsonDecoder.decode(SecondsSince1970TestModel.self, from: secondsSince1970JSON.data(using: .utf8)!) }.toNot(throwError()) + let decodedModel = try? self.jsonDecoder.decode(SecondsSince1970TestModel.self, from: secondsSince1970JSON.data(using: .utf8)!) + expect(decodedModel).toNot(beNil()) + if let actualModel = decodedModel { + expect(actualModel) == secondsSince1970TestInstance + } + } + } + } describe("OmitCoding") { it("HasNoValue") { expect {_ = try self.jsonDecoder.decode(OmitCodingTestModel.self, from: Self.emptyJSON.data(using: .utf8)!)}.toNot(throwError()) @@ -135,6 +156,24 @@ class OptionalDecodingTests: QuickSpec, DecodingTestSpec, EncodingTestSpec { } } } + describe("OptionalStaticCoder") { + it("HasNoValue") { + expect {_ = try self.plistDecoder.decode(SecondsSince1970TestModel.self, from: emptyPList.data(using: .utf8)!)}.toNot(throwError()) + let decodedModel = try? self.plistDecoder.decode(SecondsSince1970TestModel.self, from: emptyPList.data(using: .utf8)!) + expect(decodedModel).toNot(beNil()) + if let actualModel = decodedModel { + expect(actualModel) == secondsSince1970TestEmptyInstance + } + } + it("HasAValue") { + expect {_ = try self.plistDecoder.decode(SecondsSince1970TestModel.self, from: secondsSince1970XML.data(using: .utf8)!)}.toNot(throwError()) + let decodedModel = try? self.plistDecoder.decode(SecondsSince1970TestModel.self, from: secondsSince1970XML.data(using: .utf8)!) + expect(decodedModel).toNot(beNil()) + if let actualModel = decodedModel { + expect(actualModel) == secondsSince1970TestInstance + } + } + } describe("OmitCoding") { it("HasNoValue") { expect {_ = try self.plistDecoder.decode(OmitCodingTestModel.self, from: Self.emptyPList.data(using: .utf8)!)}.toNot(throwError()) @@ -209,12 +248,15 @@ class OptionalDecodingTests: QuickSpec, DecodingTestSpec, EncodingTestSpec { private struct TransientModel: Codable, Equatable { @TransientCoding var value: String? + @TransientDecoding + var otherValue: String? } -private let transientTestInstance = TransientModel(value: nil) -private let transientTestWithDataInstance = TransientModel(value: "hi") +private let transientTestInstance = TransientModel(value: nil, otherValue: nil) +private let transientTestWithDataInstance = TransientModel(value: "hi", otherValue: "There") private let emptyTestWithNullJSON = """ { + "otherValue" : null, "value" : null } """ @@ -224,6 +266,8 @@ private let emptyTestWithNullXML = """ + otherValue + $null value $null @@ -232,6 +276,7 @@ private let emptyTestWithNullXML = """ private let emptyTestWithDataJSON = """ { + "otherValue" : "There", "value" : "hi" } """ @@ -241,12 +286,57 @@ private let emptyTestWithDataXML = """ + otherValue + There value hi """ +// MARK: - Seconds Since 1970 Mock Data +private struct SecondsSince1970TestModel: Codable, Equatable { + @OptionalCoding + var secondsSince1970Date: Date? + + @OptionalDecoding + var otherSecondsSince1970Date: Date? +} +private let secondsSince1970TestEmptyInstance = SecondsSince1970TestModel(secondsSince1970Date: nil, otherSecondsSince1970Date: nil) +private let secondsSince1970TestInstance = SecondsSince1970TestModel(secondsSince1970Date: Date(timeIntervalSince1970: 590277534.0), otherSecondsSince1970Date: Date(timeIntervalSince1970: 590277534.0)) +private let secondsSince1970JSON = """ +{ + "otherSecondsSince1970Date" : 590277534, + "secondsSince1970Date" : 590277534 +} +""" + +private let secondsSince1970XML = """ + + + + + otherSecondsSince1970Date + 590277534.0 + secondsSince1970Date + 590277534.0 + + +""" + +private let secondsSince1970XML2 = """ + + + + + otherSecondsSince1970Date + 590277534 + secondsSince1970Date + 590277534 + + +""" + // MARK: - OmitCoding Mock Data private struct OmitCodingTestModel: Codable, Equatable { diff --git a/Tests/CodableWrappersTests/Encoder/EncodingTestSpec.swift b/Tests/CodableWrappersTests/Encoder/EncodingTestSpec.swift index 5c8577f..db4e2a8 100644 --- a/Tests/CodableWrappersTests/Encoder/EncodingTestSpec.swift +++ b/Tests/CodableWrappersTests/Encoder/EncodingTestSpec.swift @@ -9,13 +9,14 @@ import Foundation import Quick import Nimble - -protocol EncodingTestSpec: CodingTestSpec { +protocol EncodingTests { static var jsonEncoder: JSONEncoder { get } static var plistEncoder: PropertyListEncoder { get } } -extension EncodingTestSpec { +protocol EncodingTestSpec: EncodingTests, CodingTestSpec { } + +extension EncodingTests { static var jsonEncoder: JSONEncoder { let encoder = JSONEncoder() // if #available(OSX 10.13, *) { diff --git a/Tests/CodableWrappersTests/Encoder/OptionalEncodingTests.swift b/Tests/CodableWrappersTests/Encoder/OptionalEncodingTests.swift index f56fcda..45a03cd 100644 --- a/Tests/CodableWrappersTests/Encoder/OptionalEncodingTests.swift +++ b/Tests/CodableWrappersTests/Encoder/OptionalEncodingTests.swift @@ -288,11 +288,14 @@ class OptionalEncodingTests: QuickSpec, EncodingTestSpec, DecodingTestSpec { private struct EmptyModel: Codable, Equatable { @TransientCoding var value: String? + @TransientEncoding + var otherValue: String? } -private let emptyTestInstance = EmptyModel(value: nil) -private let emptyTestWithDataInstance = EmptyModel(value: "hi") +private let emptyTestInstance = EmptyModel(value: nil, otherValue: nil) +private let emptyTestWithDataInstance = EmptyModel(value: "hi", otherValue: "there") private let emptyTestWithDataJSON = """ { + "otherValue" : "there", "value" : "hi" } """ @@ -302,6 +305,8 @@ private let emptyTestWithDataXML = """ + otherValue + there value hi @@ -318,11 +323,15 @@ private let noWrapperTestInstance = NoWrapperModel(value: nil) private struct SecondsSince1970TestModel: Codable, Equatable { @OptionalCoding var secondsSince1970Date: Date? + + @OptionalEncoding + var otherSecondsSince1970Date: Date? } -private let secondsSince1970TestEmptyInstance = SecondsSince1970TestModel(secondsSince1970Date: nil) -private let secondsSince1970TestInstance = SecondsSince1970TestModel(secondsSince1970Date: Date(timeIntervalSince1970: 590277534.0)) +private let secondsSince1970TestEmptyInstance = SecondsSince1970TestModel(secondsSince1970Date: nil, otherSecondsSince1970Date: nil) +private let secondsSince1970TestInstance = SecondsSince1970TestModel(secondsSince1970Date: Date(timeIntervalSince1970: 590277534.0), otherSecondsSince1970Date: Date(timeIntervalSince1970: 590277534.0)) private let secondsSince1970JSON = """ { + "otherSecondsSince1970Date" : 590277534, "secondsSince1970Date" : 590277534 } """ @@ -332,6 +341,8 @@ private let secondsSince1970XML = """ + otherSecondsSince1970Date + 590277534.0 secondsSince1970Date 590277534.0 @@ -343,6 +354,8 @@ private let secondsSince1970XML2 = """ + otherSecondsSince1970Date + 590277534 secondsSince1970Date 590277534 diff --git a/Tests/CodableWrappersTests/PartialImplementationTests.swift b/Tests/CodableWrappersTests/PartialImplementationTests.swift index e9b3421..5aef6fc 100644 --- a/Tests/CodableWrappersTests/PartialImplementationTests.swift +++ b/Tests/CodableWrappersTests/PartialImplementationTests.swift @@ -7,36 +7,27 @@ import CodableWrappers import Foundation -import Quick -import Nimble +//import Quick +//import Nimble -class CompositionTests: QuickSpec, DecodingTestSpec, EncodingTestSpec { - override class func spec() { - describe("StaticCoder") { - context("OnlyCustomDecoding") { - it("EncodesWithDefault") { - let currentDate = Date() - let encodingModel = DecodingModel(time: currentDate) - let encoded = try! self.jsonEncoder.encode(encodingModel) - expect {_ = try self.jsonDecoder.decode(DecodingModel.self, from: encoded)}.toNot(throwError()) - let decoded = try? self.jsonDecoder.decode(DecodingModel.self, from: encoded) - expect(decoded).toNot(beNil()) - // This means it was decoded using the SecondsSince1970DateDecoding, but encoded using the default - expect(decoded?.time.timeIntervalSince1970) == currentDate.timeIntervalSinceReferenceDate - } - } - context("OnlyCustomEncoding") { - it("DecodesWithDefault") { - let currentDate = Date() - let encodingModel = EncodingModel(time: currentDate) - let encoded = try! self.jsonEncoder.encode(encodingModel) - expect {_ = try self.jsonDecoder.decode(EncodingModel.self, from: encoded)}.toNot(throwError()) - let decoded = try? self.jsonDecoder.decode(EncodingModel.self, from: encoded) - expect(decoded).toNot(beNil()) - // This means it was decoded using the SecondsSince1970DateDecoding, but encoded using the default - expect(decoded?.time.timeIntervalSinceReferenceDate) == currentDate.timeIntervalSince1970 - } - } +import Testing + +struct CompositionTests { + struct StaticCoder: CodingTests { + @Test func customDecodingEncodesWithDefaults() async throws { + let currentDate = Date() + let encodingModel = DecodingModel(time: currentDate) + let encoded = try Self.jsonEncoder.encode(encodingModel) + let decoded = try Self.jsonDecoder.decode(DecodingModel.self, from: encoded) + #expect(decoded.time.timeIntervalSince1970 == currentDate.timeIntervalSinceReferenceDate) + } + + @Test func customEncodingDecodesWithDefaults() async throws { + let currentDate = Date() + let encodingModel = EncodingModel(time: currentDate) + let encoded = try Self.jsonEncoder.encode(encodingModel) + let decoded = try Self.jsonDecoder.decode(EncodingModel.self, from: encoded) + #expect(decoded.time.timeIntervalSinceReferenceDate == currentDate.timeIntervalSince1970) } } } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 92c9f47..d28d9a4 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -4,34 +4,34 @@ import Quick @testable import CodableWrappersTests //@testable import CodableWrapperMacrosTests -let allTestClasses = [ -// CodingKeyMacroErrorTests.self, -// CodingKeyMacroTests.self, -// CodingKeyPrefixSuffixTests.self, -// KeyConverterTests.self, - - BoolDecodingTests.self, - CustomFloatingPointDecoderTests.self, - DataDecodingTests.self, - DateDecodingTests.self, - EmptyDefaultsDecodingTests.self, - LossyCollectionDecodingTests.self, - OptionalDecodingTests.self, - - BoolEncodingTests.self, - CustomFloatingPointEncoderTests.self, - DataEncodingTests.self, - DateEncodingTests.self, - EmptyDefaultsEncodingTests.self, - NullEncodingTests.self, - OptionalEncodingTests.self, - CompositionTests.self, - PartialImplementationTests.self, -] -#if os(Linux) -@main struct Main { - static func main() { - QCKMain(allTestClasses) - } -} -#endif +//let allTestClasses = [ +//// CodingKeyMacroErrorTests.self, +//// CodingKeyMacroTests.self, +//// CodingKeyPrefixSuffixTests.self, +//// KeyConverterTests.self, +// +// BoolDecodingTests.self, +// CustomFloatingPointDecoderTests.self, +// DataDecodingTests.self, +// DateDecodingTests.self, +// EmptyDefaultsDecodingTests.self, +// LossyCollectionDecodingTests.self, +// OptionalDecodingTests.self, +// +// BoolEncodingTests.self, +// CustomFloatingPointEncoderTests.self, +// DataEncodingTests.self, +// DateEncodingTests.self, +// EmptyDefaultsEncodingTests.self, +// NullEncodingTests.self, +// OptionalEncodingTests.self, +// CompositionTests.self, +// PartialImplementationTests.self, +//] +//#if os(Linux) +//@main struct Main { +// static func main() { +// QCKMain(allTestClasses) +// } +//} +//#endif