diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..613fe71 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,88 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.4.0] - 2026-03-30 + +### Enhancement + +- **Variant utility** — read variant aliases and emit `data-csvariants` JSON (single or many entries). +- **`VariantUtilityError`** on invalid input. + +## [1.3.4] - 2025-01-17 + +### Changed + +- Deployment targets updated. +- General enhancements and latest platform support. + +## [1.3.3] - 2024-05-17 + +### Added + +- Privacy manifest file. + +### Changed + +- Updated tests. + +## [1.3.2] - 2024-03-28 + +### Added + +- Support for the **fragment** tag in JSON RTE. + +## [1.3.1] - 2023-11-20 + +### Fixed + +- Image linking issue. + +## [1.3.0] - 2023-05-26 + +### Added + +- Nested asset support. +- Break tag support. + +## [1.2.1] - 2022-09-09 + +### Fixed + +- Swift Package warning (exclude warning from package removed). + +## [1.2.0] - 2021-08-10 + +### Added + +- JSON RTE to HTML support for the **GQL API**. + +## [1.1.2] - 2021-07-16 + +### Added + +- JSON RTE content to HTML parsing support. + +## [1.1.1] - 2021-04-09 + +### Changed + +- Deployment target updates. + +### Removed + +- XC Framework (issue resolved). + +## [1.1.0] - 2021-04-06 + +### Fixed + +- Swift Package duplicate naming for ContentstackUtils. + +## [1.0.0] - 2021-04-06 + +### Added + +- Embedded items feature support. +- `includeEmbeddedItems` in Entry and Query modules. +- Utils SDK support in the SDK. diff --git a/ContentstackUtils.xcodeproj/project.pbxproj b/ContentstackUtils.xcodeproj/project.pbxproj index d51ac16..46b7d86 100644 --- a/ContentstackUtils.xcodeproj/project.pbxproj +++ b/ContentstackUtils.xcodeproj/project.pbxproj @@ -65,6 +65,9 @@ 0FFF2F2A2668FC54003E9DBF /* NodeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F292668FC54003E9DBF /* NodeType.swift */; }; 0FFF2F382668FE85003E9DBF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F372668FE85003E9DBF /* Node.swift */; }; 64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */; }; + 6749AC902F714E26007282C5 /* variantsEntries.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC8F2F714E26007282C5 /* variantsEntries.json */; }; + 6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC912F714E2F007282C5 /* variantsSingleEntry.json */; }; + 6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6749AC932F714E36007282C5 /* VariantUtilityTests.swift */; }; OBJ_22 /* ContentstackUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* ContentstackUtils.swift */; }; OBJ_29 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; OBJ_40 /* ContentstackUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* ContentstackUtilsTests.swift */; }; @@ -138,6 +141,10 @@ 0FFF2F292668FC54003E9DBF /* NodeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeType.swift; sourceTree = ""; }; 0FFF2F372668FE85003E9DBF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; 64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 6749AC8F2F714E26007282C5 /* variantsEntries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsEntries.json; sourceTree = ""; }; + 6749AC912F714E2F007282C5 /* variantsSingleEntry.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsSingleEntry.json; sourceTree = ""; }; + 6749AC932F714E36007282C5 /* VariantUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantUtilityTests.swift; sourceTree = ""; }; + 6749AC952F715507007282C5 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; "ContentstackUtils::ContentstackUtils::Product" /* ContentstackUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ContentstackUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; "ContentstackUtils::ContentstackUtilsTests::Product" /* ContentstackUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ContentstackUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; OBJ_12 /* ContentstackUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentstackUtilsTests.swift; sourceTree = ""; }; @@ -321,6 +328,9 @@ 0F07E62E25244DB5003E0BD1 /* StringExtensionTests.swift */, 0F5E484B2525DB600038C16B /* TestClient.swift */, 0FEC0B3A254FEC60008D4E66 /* MetadataTests.swift */, + 6749AC8F2F714E26007282C5 /* variantsEntries.json */, + 6749AC912F714E2F007282C5 /* variantsSingleEntry.json */, + 6749AC932F714E36007282C5 /* VariantUtilityTests.swift */, 0F7142C325514A6F00C18A61 /* ContentstackUtilsArrayTest.swift */, 0F7142C52551684600C18A61 /* ContentstackUtilsCustomRendertest.swift */, 0F579540266A50D40082815C /* MarkTypeTest.swift */, @@ -347,6 +357,7 @@ 64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */, 0FAA3EBD26A1C65B00173FA9 /* ContentstackUtils.podspec */, OBJ_6 /* Package.swift */, + 6749AC952F715507007282C5 /* CHANGELOG.md */, 0F7142C725517A4900C18A61 /* README.md */, 0FA3D58E252228E300E58179 /* Scripts */, OBJ_7 /* Sources */, @@ -471,7 +482,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */, 64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */, + 6749AC902F714E26007282C5 /* variantsEntries.json in Resources */, 0F5E484E2525DDD70038C16B /* EntryEmbedded.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -556,6 +569,7 @@ 0FFD88D7266DDD1900BA5919 /* ContentstackUtilsJsonToHtmlTest.swift in Sources */, 0F07E62F25244DB5003E0BD1 /* StringExtensionTests.swift in Sources */, 0F7142C425514A6F00C18A61 /* ContentstackUtilsArrayTest.swift in Sources */, + 6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */, 0FFD88EE266DE1A600BA5919 /* NodeParser.swift in Sources */, 0FFD88F7266DE1FB00BA5919 /* JsonNodes.swift in Sources */, 0F00785B26A5A0EB00FC4925 /* GQLJsonToHtml.swift in Sources */, diff --git a/Sources/ContentstackUtils/ContentstackUtils.swift b/Sources/ContentstackUtils/ContentstackUtils.swift index 64ef341..67f0c4b 100644 --- a/Sources/ContentstackUtils/ContentstackUtils.swift +++ b/Sources/ContentstackUtils/ContentstackUtils.swift @@ -2,6 +2,10 @@ import Foundation public struct ContentstackUtils { + public enum VariantUtilityError: Error { + case invalidArgument(String) + } + public struct GQL { public static func jsonToHtml(rte document: [String: Any?], _ option: Option = Option()) throws -> Any { do { @@ -72,6 +76,79 @@ public struct ContentstackUtils { public static func jsonToHtml(node document: Node, _ option: Option = Option()) -> String { return nodeChildrenToHtml(children: document.children, option) } + + public static func getVariantAliases(entry: [String: Any], contentTypeUid: String) throws -> [String: Any] { + + try validateContentTypeUid(contentTypeUid) + + guard let uid = entry["uid"] as? String, !uid.isEmpty else{ + throw VariantUtilityError.invalidArgument("entry uid is required.") + } + + guard let publish = entry["publish_details"] as? [String: Any], + let variants = publish["variants"] as? [String: Any] else{ + return [ + "entry_uid": uid, + "contenttype_uid": contentTypeUid, + "variants": [] as [String] + ] + } + var aliases : [String] = [] + for(_, value) in variants { + if let obj = value as? [String: Any], + let alias = obj["alias"] as? String { + aliases.append(alias) + } + } + + return [ + "entry_uid": uid, + "contenttype_uid": contentTypeUid, + "variants": aliases + ] + } + + public static func getVariantAliases(entries: [[String: Any]], contentTypeUid: String) throws -> [[String: Any]] { + try validateContentTypeUid(contentTypeUid) + return try entries.map { entry in + try getVariantAliases(entry: entry, contentTypeUid: contentTypeUid) + } + } + + public static func getDataCsvariantsAttribute(entry: [String: Any]?, contentTypeUid: String) throws -> [String: Any]{ + guard let e = entry else { + return ["data-csvariants": "[]"] + } + + let payload = try getVariantAliases(entry: e, contentTypeUid: contentTypeUid) + let s = try jsonString(for: [payload]) + return ["data-csvariants": s] + + } + + public static func getDataCsvariantsAttribute(entries: [[String: Any]], contentTypeUid: String) throws -> [String: Any]{ + try validateContentTypeUid(contentTypeUid) + let payloads = try getVariantAliases(entries: entries, contentTypeUid: contentTypeUid) + let s = try jsonString(for: payloads) + return ["data-csvariants": s] + } + + + private static func validateContentTypeUid(_ contentTypeUid: String) throws { + if contentTypeUid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + throw VariantUtilityError.invalidArgument("contentTypeUid must not be empty") + } + } + + private static func jsonString(for array: [[String: Any]]) throws -> String{ + let data = try JSONSerialization.data(withJSONObject: array, options: []) + guard let json = String(data: data, encoding: .utf8) else { + throw VariantUtilityError.invalidArgument("Failed to encode JSON string") + } + return json + } + + static private func nodeChildrenToHtml(children nodes:[Node], _ option: Option) -> String { nodes.map{ (node) -> String in @@ -156,3 +233,4 @@ public struct ContentstackUtils { return nil } } + diff --git a/Tests/ContentstackUtilsTests/TestClient.swift b/Tests/ContentstackUtilsTests/TestClient.swift index 0f4a596..14d4bd9 100644 --- a/Tests/ContentstackUtilsTests/TestClient.swift +++ b/Tests/ContentstackUtilsTests/TestClient.swift @@ -26,4 +26,25 @@ class TestDecodable { static func getMultilevelEmbed() -> ContentBlock? { return decode("EntryEmbedded") } + + /// Loads a JSON object from a file (e.g. `variantsEntries` → `variantsEntries.json`). + /// Tries the test bundle first (Xcode), then the directory containing this source file (SwiftPM `swift test`). + static func loadJSONObject(named fileName: String) throws -> [String: Any] { + let url: URL + if let path = Bundle(for: TestDecodable.self).path(forResource: fileName, ofType: "json") { + url = URL(fileURLWithPath: path) + } else { + let testsDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() + let fallback = testsDir.appendingPathComponent("\(fileName).json") + guard FileManager.default.fileExists(atPath: fallback.path) else { + throw NSError(domain: "TestDecodable", code: 1, userInfo: [NSLocalizedDescriptionKey: "Missing json: \(fileName).json"]) + } + url = fallback + } + let data = try Data(contentsOf: url, options: .mappedIfSafe) + guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { + throw NSError(domain: "TestDecodable", code: 2, userInfo: [NSLocalizedDescriptionKey: "Root is not a JSON object"]) + } + return root + } } diff --git a/Tests/ContentstackUtilsTests/VariantUtilityTests.swift b/Tests/ContentstackUtilsTests/VariantUtilityTests.swift new file mode 100644 index 0000000..c26297e --- /dev/null +++ b/Tests/ContentstackUtilsTests/VariantUtilityTests.swift @@ -0,0 +1,212 @@ +import XCTest +@testable import ContentstackUtils + +final class VariantUtilityTests: XCTestCase { + + private let contentTypeUid = "movie" + + private func stringSet(from variants: Any?) -> Set { + guard let arr = variants as? [String] else { return [] } + return Set(arr) + } + + private func parseDataCsvariantsArray(_ wrapper: [String: Any]) throws -> [[String: Any]] { + let jsonStr = try XCTUnwrap(wrapper["data-csvariants"] as? String) + let data = try XCTUnwrap(jsonStr.data(using: .utf8)) + let any = try JSONSerialization.jsonObject(with: data) + guard let arr = any as? [Any] else { + XCTFail("data-csvariants is not a JSON array") + return [] + } + return arr.compactMap { $0 as? [String: Any] } + } + + func testGetVariantAliasesSingleEntry() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsSingleEntry") + let entry = try XCTUnwrap(root["entry"] as? [String: Any]) + let result = try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: contentTypeUid) + + XCTAssertEqual(result["entry_uid"] as? String, "entry_uid_single") + XCTAssertEqual(result["contenttype_uid"] as? String, contentTypeUid) + XCTAssertEqual(stringSet(from: result["variants"]), ["cs_personalize_0_0", "cs_personalize_0_3"]) + } + + func testGetDataCsvariantsAttributeSingleEntry() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsSingleEntry") + let entry = try XCTUnwrap(root["entry"] as? [String: Any]) + let wrapper = try ContentstackUtils.getDataCsvariantsAttribute(entry: entry, contentTypeUid: contentTypeUid) + + let parsed = try parseDataCsvariantsArray(wrapper) + XCTAssertEqual(parsed.count, 1) + let first = try XCTUnwrap(parsed.first) + XCTAssertEqual(first["entry_uid"] as? String, "entry_uid_single") + XCTAssertEqual(first["contenttype_uid"] as? String, contentTypeUid) + XCTAssertEqual(stringSet(from: first["variants"]), ["cs_personalize_0_0", "cs_personalize_0_3"]) + } + + func testGetVariantAliasesMultipleEntries() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsEntries") + let entries = try XCTUnwrap(root["entries"] as? [[String: Any]]) + let result = try ContentstackUtils.getVariantAliases(entries: entries, contentTypeUid: contentTypeUid) + + XCTAssertEqual(result.count, 3) + + let first = try XCTUnwrap(result.first) + XCTAssertEqual(first["entry_uid"] as? String, "entry_uid_1") + XCTAssertEqual(first["contenttype_uid"] as? String, contentTypeUid) + XCTAssertEqual(stringSet(from: first["variants"]), ["cs_personalize_0_0", "cs_personalize_0_3"]) + + let second = result[1] + XCTAssertEqual(second["entry_uid"] as? String, "entry_uid_2") + XCTAssertEqual((second["variants"] as? [String])?.count, 1) + XCTAssertEqual((second["variants"] as? [String])?.first, "cs_personalize_0_0") + + let third = result[2] + XCTAssertEqual(third["entry_uid"] as? String, "entry_uid_3") + XCTAssertEqual((third["variants"] as? [String])?.count, 0) + } + + func testGetDataCsvariantsAttributeMultipleEntries() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsEntries") + let entries = try XCTUnwrap(root["entries"] as? [[String: Any]]) + let wrapper = try ContentstackUtils.getDataCsvariantsAttribute(entries: entries, contentTypeUid: contentTypeUid) + + let parsed = try parseDataCsvariantsArray(wrapper) + XCTAssertEqual(parsed.count, 3) + + XCTAssertEqual(parsed[0]["entry_uid"] as? String, "entry_uid_1") + XCTAssertEqual((parsed[0]["variants"] as? [String])?.count, 2) + + XCTAssertEqual(parsed[1]["entry_uid"] as? String, "entry_uid_2") + XCTAssertEqual((parsed[1]["variants"] as? [String])?.count, 1) + + XCTAssertEqual(parsed[2]["entry_uid"] as? String, "entry_uid_3") + XCTAssertEqual((parsed[2]["variants"] as? [String])?.count, 0) + } + + func testGetVariantAliasesThrowsWhenEntryMissingUid() { + XCTAssertThrowsError(try ContentstackUtils.getVariantAliases(entry: [:], contentTypeUid: contentTypeUid)) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + func testGetVariantAliasesThrowsWhenContentTypeUidEmpty() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsSingleEntry") + let entry = try XCTUnwrap(root["entry"] as? [String: Any]) + + XCTAssertThrowsError(try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: "")) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + XCTAssertThrowsError(try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: " ")) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + func testGetDataCsvariantsAttributeWhenEntryNil() throws { + let result = try ContentstackUtils.getDataCsvariantsAttribute(entry: nil, contentTypeUid: "landing_page") + XCTAssertEqual(result["data-csvariants"] as? String, "[]") + } + + // MARK: - Edge cases + + func testGetVariantAliasesEmptyEntriesArray() throws { + let result = try ContentstackUtils.getVariantAliases(entries: [], contentTypeUid: contentTypeUid) + XCTAssertTrue(result.isEmpty) + } + + func testGetVariantAliasesEmptyEntriesThrowsWhenContentTypeUidEmpty() { + XCTAssertThrowsError(try ContentstackUtils.getVariantAliases(entries: [], contentTypeUid: "")) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + func testGetDataCsvariantsAttributeEmptyEntriesArray() throws { + let wrapper = try ContentstackUtils.getDataCsvariantsAttribute(entries: [], contentTypeUid: contentTypeUid) + XCTAssertEqual(wrapper["data-csvariants"] as? String, "[]") + } + + func testGetVariantAliasesEntryWithoutPublishDetails() throws { + let entry: [String: Any] = [ + "uid": "no_publish", + "title": "No publish_details key", + ] + let result = try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: contentTypeUid) + XCTAssertEqual(result["entry_uid"] as? String, "no_publish") + XCTAssertEqual(result["contenttype_uid"] as? String, contentTypeUid) + XCTAssertEqual(result["variants"] as? [String], []) + } + + func testGetVariantAliasesEntryWithEmptyVariantsMap() throws { + let entry: [String: Any] = [ + "uid": "empty_variants", + "publish_details": [ + "locale": "en-us", + "variants": [String: Any](), + ] as [String: Any], + ] + let result = try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: contentTypeUid) + XCTAssertEqual(result["entry_uid"] as? String, "empty_variants") + XCTAssertEqual(result["variants"] as? [String], []) + } + + func testGetVariantAliasesSkipsInvalidVariantValues() throws { + let entry: [String: Any] = [ + "uid": "mixed_variants", + "publish_details": [ + "variants": [ + "cs_variant_0_0": [ + "alias": "cs_personalize_0_0", + ], + "not_a_dict": "ignored", + "missing_alias": [ + "version": 1, + ], + ] as [String: Any], + ] as [String: Any], + ] + let result = try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: contentTypeUid) + XCTAssertEqual(result["variants"] as? [String], ["cs_personalize_0_0"]) + } + + func testGetVariantAliasesThrowsWhenUidIsEmptyString() { + let entry: [String: Any] = ["uid": "", "title": "x"] + XCTAssertThrowsError(try ContentstackUtils.getVariantAliases(entry: entry, contentTypeUid: contentTypeUid)) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + func testGetDataCsvariantsAttributeThrowsWhenContentTypeUidEmptyWithEntry() throws { + let root = try TestDecodable.loadJSONObject(named: "variantsSingleEntry") + let entry = try XCTUnwrap(root["entry"] as? [String: Any]) + XCTAssertThrowsError(try ContentstackUtils.getDataCsvariantsAttribute(entry: entry, contentTypeUid: "")) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + func testGetDataCsvariantsAttributeEntriesThrowsWhenContentTypeUidWhitespaceOnly() { + XCTAssertThrowsError(try ContentstackUtils.getDataCsvariantsAttribute(entries: [], contentTypeUid: "\t\n")) { error in + XCTAssertTrue(error is ContentstackUtils.VariantUtilityError) + } + } + + #if !canImport(ObjectiveC) + static var allTests = [ + ("testGetVariantAliasesSingleEntry", testGetVariantAliasesSingleEntry), + ("testGetDataCsvariantsAttributeSingleEntry", testGetDataCsvariantsAttributeSingleEntry), + ("testGetVariantAliasesMultipleEntries", testGetVariantAliasesMultipleEntries), + ("testGetDataCsvariantsAttributeMultipleEntries", testGetDataCsvariantsAttributeMultipleEntries), + ("testGetVariantAliasesThrowsWhenEntryMissingUid", testGetVariantAliasesThrowsWhenEntryMissingUid), + ("testGetVariantAliasesThrowsWhenContentTypeUidEmpty", testGetVariantAliasesThrowsWhenContentTypeUidEmpty), + ("testGetDataCsvariantsAttributeWhenEntryNil", testGetDataCsvariantsAttributeWhenEntryNil), + ("testGetVariantAliasesEmptyEntriesArray", testGetVariantAliasesEmptyEntriesArray), + ("testGetVariantAliasesEmptyEntriesThrowsWhenContentTypeUidEmpty", testGetVariantAliasesEmptyEntriesThrowsWhenContentTypeUidEmpty), + ("testGetDataCsvariantsAttributeEmptyEntriesArray", testGetDataCsvariantsAttributeEmptyEntriesArray), + ("testGetVariantAliasesEntryWithoutPublishDetails", testGetVariantAliasesEntryWithoutPublishDetails), + ("testGetVariantAliasesEntryWithEmptyVariantsMap", testGetVariantAliasesEntryWithEmptyVariantsMap), + ("testGetVariantAliasesSkipsInvalidVariantValues", testGetVariantAliasesSkipsInvalidVariantValues), + ("testGetVariantAliasesThrowsWhenUidIsEmptyString", testGetVariantAliasesThrowsWhenUidIsEmptyString), + ("testGetDataCsvariantsAttributeThrowsWhenContentTypeUidEmptyWithEntry", testGetDataCsvariantsAttributeThrowsWhenContentTypeUidEmptyWithEntry), + ("testGetDataCsvariantsAttributeEntriesThrowsWhenContentTypeUidWhitespaceOnly", testGetDataCsvariantsAttributeEntriesThrowsWhenContentTypeUidWhitespaceOnly), + ] + #endif +} diff --git a/Tests/ContentstackUtilsTests/XCTestManifests.swift b/Tests/ContentstackUtilsTests/XCTestManifests.swift index 0a94b19..4e17149 100644 --- a/Tests/ContentstackUtilsTests/XCTestManifests.swift +++ b/Tests/ContentstackUtilsTests/XCTestManifests.swift @@ -3,7 +3,8 @@ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ - testCase(ContentstackUtilsTests.allTests) + testCase(ContentstackUtilsTests.allTests), + testCase(VariantUtilityTests.allTests), ] } #endif diff --git a/Tests/ContentstackUtilsTests/variantsEntries.json b/Tests/ContentstackUtilsTests/variantsEntries.json new file mode 100644 index 0000000..0c0a376 --- /dev/null +++ b/Tests/ContentstackUtilsTests/variantsEntries.json @@ -0,0 +1,71 @@ +{ + "entries": [ + { + "uid": "entry_uid_1", + "_metadata": {}, + "locale": "en-us", + "_version": 1, + "title": "Sample Movie", + "publish_details": { + "time": "2025-12-11T07:56:17.574Z", + "user": "test_user", + "environment": "test_env", + "locale": "en-us", + "variants": { + "cs_variant_0_0": { + "alias": "cs_personalize_0_0", + "environment": "test_env", + "time": "2025-12-11T07:56:17.574Z", + "locale": "en-us", + "user": "test_user", + "version": 1 + }, + "cs_variant_0_3": { + "alias": "cs_personalize_0_3", + "environment": "test_env", + "time": "2025-12-11T07:56:17.582Z", + "locale": "en-us", + "user": "test_user", + "version": 1 + } + } + } + }, + { + "uid": "entry_uid_2", + "_metadata": {}, + "locale": "en-us", + "_version": 2, + "title": "Another Movie", + "publish_details": { + "time": "2025-12-11T07:10:19.964Z", + "user": "test_user", + "environment": "test_env", + "locale": "en-us", + "variants": { + "cs_variant_0_0": { + "alias": "cs_personalize_0_0", + "environment": "test_env", + "time": "2025-12-11T07:10:19.964Z", + "locale": "en-us", + "user": "test_user", + "version": 2 + } + } + } + }, + { + "uid": "entry_uid_3", + "_metadata": {}, + "locale": "en-us", + "_version": 1, + "title": "Movie No Variants", + "publish_details": { + "time": "2025-11-20T10:00:00.000Z", + "user": "test_user", + "environment": "test_env", + "locale": "en-us" + } + } + ] + } \ No newline at end of file diff --git a/Tests/ContentstackUtilsTests/variantsSingleEntry.json b/Tests/ContentstackUtilsTests/variantsSingleEntry.json new file mode 100644 index 0000000..ddb0e22 --- /dev/null +++ b/Tests/ContentstackUtilsTests/variantsSingleEntry.json @@ -0,0 +1,39 @@ +{ + "entry": { + "uid": "entry_uid_single", + "_metadata": {}, + "locale": "en-us", + "_version": 1, + "ACL": {}, + "_in_progress": false, + "title": "Sample Movie", + "created_at": "2025-11-20T10:00:00.000Z", + "updated_at": "2025-12-11T07:56:17.574Z", + "created_by": "test_user", + "updated_by": "test_user", + "publish_details": { + "time": "2025-12-11T07:56:17.574Z", + "user": "test_user", + "environment": "test_env", + "locale": "en-us", + "variants": { + "cs_variant_0_0": { + "alias": "cs_personalize_0_0", + "environment": "test_env", + "time": "2025-12-11T07:56:17.574Z", + "locale": "en-us", + "user": "test_user", + "version": 1 + }, + "cs_variant_0_3": { + "alias": "cs_personalize_0_3", + "environment": "test_env", + "time": "2025-12-11T07:56:17.582Z", + "locale": "en-us", + "user": "test_user", + "version": 1 + } + } + } + } + }