diff --git a/.github/workflows/syndikit.yml b/.github/workflows/syndikit.yml index f585f03..3f5fcec 100644 --- a/.github/workflows/syndikit.yml +++ b/.github/workflows/syndikit.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: runs-on: [ubuntu-20.04, ubuntu-22.04] - swift-version: [5.7.3, 5.8] + swift-version: [5.7.3, 5.8.1, 5.9] include: - runs-on: ubuntu-20.04 swift-version: 5.5.3 @@ -135,7 +135,7 @@ jobs: ${{ runner.os }}-build- ${{ runner.os }}- - name: Cache mint - if: ${{ github.event_name == 'pull_request' && github.base_ref == 'main' && matrix.xcode == '/Applications/Xcode_14.3.1.app' }} + if: ${{ github.event_name == 'pull_request' && ( github.base_ref == 'main' || endsWith( github.ref_name , 'Prep') ) && matrix.xcode == '/Applications/Xcode_14.3.1.app' }} id: cache-mint uses: actions/cache@v3 env: @@ -152,7 +152,7 @@ jobs: - name: Setup Xcode run: sudo xcode-select -s ${{ matrix.xcode }}/Contents/Developer - name: Install mint - if: ${{ github.event_name == 'pull_request' && github.base_ref == 'main' && matrix.xcode == '/Applications/Xcode_14.3.1.app' }} + if: ${{ github.event_name == 'pull_request' && ( github.base_ref == 'main' || endsWith( github.ref_name , 'Prep') ) && matrix.xcode == '/Applications/Xcode_14.3.1.app' }} run: | brew update brew install mint @@ -168,7 +168,7 @@ jobs: uses: github/codeql-action/analyze@v2 - name: Run Swift Package tests run: swift test -v --enable-code-coverage - - uses: sersoft-gmbh/swift-coverage-action@v2 + - uses: sersoft-gmbh/swift-coverage-action@v4 - name: Upload SPM to CodeCov.io run: bash <(curl https://codecov.io/bash) -F spm -F macOS -F ${XCODE_NAME} env: @@ -177,7 +177,7 @@ jobs: run: rm -rf .build - name: Lint run: ./scripts/lint.sh - if: ${{ github.event_name == 'pull_request' && github.base_ref == 'main' && matrix.xcode == '/Applications/Xcode_14.3.app' }} + if: ${{ github.event_name == 'pull_request' && ( github.base_ref == 'main' || endsWith( github.ref_name , 'Prep') ) && matrix.xcode == '/Applications/Xcode_14.3.1.app' }} - name: Dump PIF if: startsWith(matrix.xcode,'/Applications/Xcode_14') run: | @@ -190,14 +190,14 @@ jobs: done - name: Run iOS target tests run: xcodebuild test -scheme SyndiKit -sdk iphonesimulator -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test - - uses: sersoft-gmbh/swift-coverage-action@v2 + - uses: sersoft-gmbh/swift-coverage-action@v4 - name: Upload iOS Coverage to CodeCov.io run: bash <(curl https://codecov.io/bash) -F iOS -F iOS${{ matrix.iOSVersion }} -F macOS -F ${XCODE_NAME} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Run watchOS target tests run: xcodebuild test -scheme SyndiKit -sdk watchsimulator -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' -enableCodeCoverage YES build test - - uses: sersoft-gmbh/swift-coverage-action@v2 + - uses: sersoft-gmbh/swift-coverage-action@v4 - name: Upload watchOS Coverage to CodeCov.io run: bash <(curl https://codecov.io/bash) -F watchOS -F watchOS${{ matrix.watchOSVersion }} -F macOS -F ${XCODE_NAME} env: diff --git a/.swiftlint.yml b/.swiftlint.yml index 9b8e635..db96f2b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -114,9 +114,8 @@ identifier_name: excluded: - id excluded: - - Tests/*/XCTestManifests.swift + - Tests - DerivedData - .build - - Tests/LinuxMain.swift indentation_width: indentation_width: 2 diff --git a/Sources/SyndiKit/Collection.swift b/Sources/SyndiKit/Collection.swift index 90b8c4f..a0beab0 100644 --- a/Sources/SyndiKit/Collection.swift +++ b/Sources/SyndiKit/Collection.swift @@ -1,7 +1,7 @@ import Foundation extension Collection { - subscript(safe index: Index) -> Element? { - return indices.contains(index) ? self[index] : nil - } + subscript(safe index: Index) -> Element? { + indices.contains(index) ? self[index] : nil + } } diff --git a/Sources/SyndiKit/Common/Feedable.swift b/Sources/SyndiKit/Common/Feedable.swift index 2ed0cc0..21ddcba 100644 --- a/Sources/SyndiKit/Common/Feedable.swift +++ b/Sources/SyndiKit/Common/Feedable.swift @@ -9,7 +9,7 @@ import Foundation /// - ``siteURL`` /// - ``summary`` /// - ``updated`` -/// - ``author`` +/// - ``authors`` /// - ``copyright`` /// - ``image`` /// - ``children`` diff --git a/Sources/SyndiKit/Common/Primitives/ListString.swift b/Sources/SyndiKit/Common/Primitives/ListString.swift new file mode 100644 index 0000000..58b66db --- /dev/null +++ b/Sources/SyndiKit/Common/Primitives/ListString.swift @@ -0,0 +1,39 @@ +import Foundation + +public struct ListString< + Value: LosslessStringConvertible & Equatable +>: Codable, Equatable { + public let values: [Value] + + internal init(values: [Value]) { + self.values = values + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let listString = try container.decode(String.self) + let strings = listString.components(separatedBy: ",") + let values = try strings + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + .map(Self.createValueFrom) + self.init(values: values) + } + + private static func createValueFrom(_ string: String) throws -> Value { + guard let value: Value = .init(string) else { + throw DecodingError.typeMismatch( + Value.self, + .init(codingPath: [], debugDescription: "Invalid value: \(string)") + ) + } + return value + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + let strings = values.map(String.init) + let listString = strings.joined(separator: ",") + try container.encode(listString) + } +} diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift index 66c29d4..4b57a7e 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift @@ -26,7 +26,10 @@ extension PodcastChapters { } else if case let .unknown(string) = self { return string } else { - fatalError("Type attribute of should either be `KnownMimeType`, or unknown!") + fatalError( + // swiftlint:disable:next line_length + "Type attribute of with value: \(self) should either be `KnownMimeType`, or unknown!" + ) } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift index 6b52f2b..a5bbe92 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift @@ -1,62 +1,106 @@ import Foundation -extension PodcastLocation { - public struct GeoURI: Codable, Equatable { - let latitude: Double - let longitude: Double - let altitude: Double? - let accuracy: Double? - - public init(latitude: Double, longitude: Double, altitude: Double? = nil, accuracy: Double? = nil) { +public extension PodcastLocation { + struct GeoURI: Codable, Equatable, LosslessStringConvertible { + public let latitude: Double + public let longitude: Double + public let altitude: Double? + public let accuracy: Double? + + public var description: String { + var description = "geo:\(latitude),\(longitude)" + + if let altitude = altitude { + description += ",\(altitude)" + } + + if let accuracy = accuracy { + description += ";u=\(accuracy)" + } + + return description + } + + public init( + latitude: Double, + longitude: Double, + altitude: Double? = nil, + accuracy: Double? = nil + ) { self.latitude = latitude self.longitude = longitude self.altitude = altitude self.accuracy = accuracy } - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let geoStr = try container.decode(String.self) + public init?(_ description: String) { + try? self.init(singleValue: description) + } + + public init(singleValue: String) throws { + let pathComponents = try Self.pathComponents(from: singleValue) guard - let geoScheme = geoStr.split(separator: ":")[safe: 0], - geoScheme == "geo" else { + let geoCoords = pathComponents[safe: 0]?.split(separator: ","), + let latitude = geoCoords[safe: 0]?.asDouble(), + let longitude = geoCoords[safe: 1]?.asDouble() + else { throw DecodingError.dataCorrupted( .init( codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid prefix for geo attribute: \(geoStr)" + debugDescription: "Invalid coordinates for geo attribute: \(singleValue)" ) ) } - guard let geoPath = geoStr.split(separator: ":")[safe: 1] else { + + let altitude = geoCoords[safe: 2]?.asDouble() + + let accuracy = pathComponents[safe: 1]? + .split(separator: "=")[safe: 1]? + .asDouble() + + self.init( + latitude: latitude, + longitude: longitude, + altitude: altitude, + accuracy: accuracy + ) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let singleValue = try container.decode(String.self) + + try self.init(singleValue: singleValue) + } + + private static func pathComponents(from string: String) throws -> [Substring] { + let components = string.split(separator: ":") + + guard + components[safe: 0] == "geo" else { throw DecodingError.dataCorrupted( .init( codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid path for geo attribute: \(geoStr)" + debugDescription: "Invalid prefix for geo attribute: \(string)" ) ) } - guard - let geoCoords = geoPath.split(separator: ";")[safe: 0], - let latitude = geoCoords.split(separator: ",")[safe: 0]?.asDouble(), - let longitude = geoCoords.split(separator: ",")[safe: 1]?.asDouble() - else { + guard let geoPath = components[safe: 1] else { throw DecodingError.dataCorrupted( .init( codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid coordinates for geo attribute: \(geoStr)" + debugDescription: "Invalid path for geo attribute: \(string)" ) ) } - let altitude = geoCoords.split(separator: ",")[safe: 2]?.asDouble() - let accuracy = geoPath.split(separator: ";")[safe: 1]? - .split(separator: "=")[safe: 1]? - .asDouble() - self.latitude = latitude - self.longitude = longitude - self.altitude = altitude - self.accuracy = accuracy + return geoPath.split(separator: ";") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) } } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift index 44a48e1..6eaa0de 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift @@ -1,7 +1,7 @@ import Foundation -extension PodcastLocation { - public struct OsmQuery: Codable, Equatable { +public extension PodcastLocation { + struct OsmQuery: Codable, Equatable { enum OsmType: String, Codable, CaseIterable { case node = "N" case way = "W" @@ -39,9 +39,9 @@ extension PodcastLocation { } let osmRevision = osmStr.split(separator: "#")[safe: 1]?.asInt() - self.id = osmID - self.type = osmType - self.revision = osmRevision + id = osmID + type = osmType + revision = osmRevision } } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift index cefbba6..d3359d1 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift @@ -44,7 +44,10 @@ extension PodcastPerson { } else if case let .unknown(string) = self { return string } else { - fatalError("Role attribute of should either be a `KnownRole`, or unknown!") + fatalError( + // swiftlint:disable:next line_length + "Role attribute of with value: \(self) should either be a `KnownRole`, or unknown!" + ) } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift index 0ed23f3..8ee3322 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift @@ -18,14 +18,14 @@ public struct PodcastPerson: Codable, Equatable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.role = try container.decodeIfPresent(Role.self, forKey: .role) - self.group = try container.decodeIfPresent(String.self, forKey: .group) - self.fullname = try container.decode(String.self, forKey: .fullname) + role = try container.decodeIfPresent(Role.self, forKey: .role) + group = try container.decodeIfPresent(String.self, forKey: .group) + fullname = try container.decode(String.self, forKey: .fullname) let hrefUrl = try container.decodeIfPresent(String.self, forKey: .href) ?? "" - self.href = hrefUrl.isEmpty ? nil : URL(string: hrefUrl) + href = hrefUrl.isEmpty ? nil : URL(string: hrefUrl) let imgUrl = try container.decodeIfPresent(String.self, forKey: .img) ?? "" - self.img = imgUrl.isEmpty ? nil : URL(string: imgUrl) + img = imgUrl.isEmpty ? nil : URL(string: imgUrl) } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift index 3e6263d..654c9d6 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift @@ -41,7 +41,10 @@ extension PodcastTranscript { } else if case let .unknown(string) = self { return string } else { - fatalError("Type attribute of should either be a `KnownMimeType`, or unknown!") + fatalError( + // swiftlint:disable:next line_length + "Type attribute of with value: \(self) should either be a `KnownMimeType`, or unknown!" + ) } } diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift index e516bc9..3c395a8 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift @@ -24,7 +24,10 @@ public extension WordPressElements { } extension WordPressElements.Category: Equatable { - public static func == (lhs: WordPressElements.Category, rhs: WordPressElements.Category) -> Bool { + public static func == ( + lhs: WordPressElements.Category, + rhs: WordPressElements.Category + ) -> Bool { lhs.termID == rhs.termID && lhs.niceName == rhs.niceName && lhs.parent == rhs.parent diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift index c553381..fd56724 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift @@ -18,7 +18,10 @@ public extension WordPressElements { } extension WordPressElements.PostMeta: Equatable { - public static func == (lhs: WordPressElements.PostMeta, rhs: WordPressElements.PostMeta) -> Bool { + public static func == ( + lhs: WordPressElements.PostMeta, + rhs: WordPressElements.PostMeta + ) -> Bool { lhs.key == rhs.key && lhs.value == rhs.value } diff --git a/Sources/SyndiKit/Formats/OPML/OPMLHead.swift b/Sources/SyndiKit/Formats/OPML/OPMLHead.swift index b5d995d..7d62c8f 100644 --- a/Sources/SyndiKit/Formats/OPML/OPMLHead.swift +++ b/Sources/SyndiKit/Formats/OPML/OPMLHead.swift @@ -9,7 +9,7 @@ public extension OPML { public let ownerEmail: String? public let ownerId: String? public let docs: String? - public let expansionStates: [Int]? + public let expansionStates: ListString? public let vertScrollState: Int? public let windowTop: Int? public let windowLeft: Int? @@ -31,38 +31,5 @@ public extension OPML { case windowBottom case windowRight } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - title = try container.decodeIfPresent(String.self, forKey: .title) - dateCreated = try container.decodeIfPresent(String.self, forKey: .dateCreated) - dateModified = try container.decodeIfPresent(String.self, forKey: .dateModified) - ownerName = try container.decodeIfPresent(String.self, forKey: .ownerName) - ownerEmail = try container.decodeIfPresent(String.self, forKey: .ownerEmail) - ownerId = try container.decodeIfPresent(String.self, forKey: .ownerId) - docs = try container.decodeIfPresent(String.self, forKey: .docs) - vertScrollState = try container.decodeIfPresent(Int.self, forKey: .vertScrollState) - windowTop = try container.decodeIfPresent(Int.self, forKey: .windowTop) - windowLeft = try container.decodeIfPresent(Int.self, forKey: .windowLeft) - windowBottom = try container.decodeIfPresent(Int.self, forKey: .windowBottom) - windowRight = try container.decodeIfPresent(Int.self, forKey: .windowRight) - - expansionStates = try container - .decodeIfPresent(String.self, forKey: .expansionStates)? - .components(separatedBy: ", ") - .filter { $0.isEmpty == false } - .map { - guard let value = Int($0) else { - let context = DecodingError.Context( - codingPath: [CodingKeys.expansionStates], - debugDescription: "Invalid expansionState type '\($0)'" - ) - - throw DecodingError.typeMismatch(Int.self, context) - } - - return value - } - } } } diff --git a/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift b/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift index 1021fb4..ec098d5 100644 --- a/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift +++ b/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift @@ -11,7 +11,7 @@ public extension OPML { public let xmlUrl: URL? public let language: String? public let created: String? - public let categories: [String]? + public let categories: ListString? public let isComment: Bool? public let isBreakpoint: Bool? public let version: String? @@ -35,28 +35,5 @@ public extension OPML { case outlines = "outline" } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - text = try container.decode(String.self, forKey: .text) - title = try container.decodeIfPresent(String.self, forKey: .title) - description = try container.decodeIfPresent(String.self, forKey: .description) - type = try container.decodeIfPresent(OutlineType.self, forKey: .type) - url = try container.decodeIfPresent(URL.self, forKey: .url) - htmlUrl = try container.decodeIfPresent(URL.self, forKey: .htmlUrl) - xmlUrl = try container.decodeIfPresent(URL.self, forKey: .xmlUrl) - language = try container.decodeIfPresent(String.self, forKey: .language) - created = try container.decodeIfPresent(String.self, forKey: .created) - isComment = try container.decodeIfPresent(Bool.self, forKey: .isComment) - isBreakpoint = try container.decodeIfPresent(Bool.self, forKey: .isBreakpoint) - version = try container.decodeIfPresent(String.self, forKey: .version) - - outlines = try container.decodeIfPresent([Outline].self, forKey: .outlines) - - categories = try container - .decodeIfPresent(String.self, forKey: .categories)? - .components(separatedBy: ",") - } } } diff --git a/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md b/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md index c0f4d3b..e2c3281 100644 --- a/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md +++ b/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md @@ -182,13 +182,13 @@ Abstract media types which can be pulled for the various ``Entryable`` objects. - ``MediaContent`` - ``Video`` - ### XML Primitive Types In many cases, types are encoded in non-matching types but are intended to strong-typed for various formats. These primitives are setup to make XML decoding easier while retaining their intended strong-type. - ``CData`` - ``XMLStringInt`` +- ``ListString`` ### Syndication Updates @@ -206,6 +206,7 @@ Specific properties related to the Atom format. - ``AtomEntry`` - ``AtomCategory`` - ``AtomMedia`` +- ``AtomMediaGroup`` - ``Link`` ### JSON Feed Format @@ -215,6 +216,11 @@ Specific properties related to the JSON Feed format. - ``JSONFeed`` - ``JSONItem`` +### OPML Feed Formate + +- ``OPML`` +- ``OutlineType`` + ### RSS Feed Format Specific properties related to the RSS Feed format. @@ -226,6 +232,19 @@ Specific properties related to the RSS Feed format. - ``RSSItemCategory`` - ``Enclosure`` +### Podcast Extensions + +Specific properties related to [podcasts](https://github.com/Podcastindex-org/podcast-namespace). + +- ``PodcastPerson`` +- ``PodcastSeason`` +- ``PodcastChapters`` +- ``PodcastLocation`` +- ``PodcastSoundbite`` +- ``PodcastTranscript`` +- ``PodcastFunding`` +- ``PodcastLocked`` + ### WordPress Extensions Specific extension properties provided by WordPress. diff --git a/Tests/SyndiKitTests/OPMLTests.swift b/Tests/SyndiKitTests/OPMLTests.swift index 70fff2c..22da4ab 100644 --- a/Tests/SyndiKitTests/OPMLTests.swift +++ b/Tests/SyndiKitTests/OPMLTests.swift @@ -52,9 +52,9 @@ internal final class OPMLTests: XCTestCase { let outline = opml?.body.outlines.first XCTAssertEqual(outline?.text, "The Mets are the best team in baseball.") - XCTAssertEqual(outline?.categories?.count, 2) - XCTAssertEqual(outline?.categories?[0], "/Philosophy/Baseball/Mets") - XCTAssertEqual(outline?.categories?[1], "/Tourism/New York") + XCTAssertEqual(outline?.categories?.values.count, 2) + XCTAssertEqual(outline?.categories?.values[0], "/Philosophy/Baseball/Mets") + XCTAssertEqual(outline?.categories?.values[1], "/Tourism/New York") } internal func testPlacesLived() throws { @@ -62,18 +62,18 @@ internal final class OPMLTests: XCTestCase { XCTAssertEqual(opml?.head.title, "placesLived.opml") XCTAssertEqual(opml?.head.ownerId, "http://www.opml.org/profiles/sendMail?usernum=1") - XCTAssertEqual(opml?.head.expansionStates?.count, 6) - XCTAssertEqual(opml?.head.expansionStates?[0], 1) - XCTAssertEqual(opml?.head.expansionStates?[3], 10) + XCTAssertEqual(opml?.head.expansionStates?.values.count, 6) + XCTAssertEqual(opml?.head.expansionStates?.values[0], 1) + XCTAssertEqual(opml?.head.expansionStates?.values[3], 10) } internal func testSimpleScript() throws { let opml = try Content.opml["simpleScript"]?.get() XCTAssertEqual(opml?.head.title, "workspace.userlandsamples.doSomeUpstreaming") - XCTAssertEqual(opml?.head.expansionStates?.count, 3) - XCTAssertEqual(opml?.head.expansionStates?[0], 1) - XCTAssertEqual(opml?.head.expansionStates?[2], 4) + XCTAssertEqual(opml?.head.expansionStates?.values.count, 3) + XCTAssertEqual(opml?.head.expansionStates?.values[0], 1) + XCTAssertEqual(opml?.head.expansionStates?.values[2], 4) XCTAssertEqual(opml?.body.outlines.count, 4) @@ -94,12 +94,9 @@ internal final class OPMLTests: XCTestCase { } XCTAssertTrue(type is Int.Type) - XCTAssertTrue( - context.codingPath.contains( - where: { - $0.stringValue == OPML.Head.CodingKeys.expansionStates.stringValue - } - ) + XCTAssertEqual( + context.debugDescription, + "Invalid value: one" ) } } diff --git a/Tests/SyndiKitTests/RSSCodedTests.swift b/Tests/SyndiKitTests/RSSCodedTests.swift index 8e6f70b..56f9dcc 100644 --- a/Tests/SyndiKitTests/RSSCodedTests.swift +++ b/Tests/SyndiKitTests/RSSCodedTests.swift @@ -307,10 +307,10 @@ public final class SyndiKitTests: XCTestCase { } func testPodcastLocationOfTypeRelation() throws { - let expectedLatitude = 30.2672 - let expectedLongitude = 97.7431 + let expectedLatitude = 30.267_2 + let expectedLongitude = 97.743_1 let expectedOsmType = "R" - let expectedOsmID = 113314 + let expectedOsmID = 113_314 let xmlStr = """