From a87268ee8c8fbb8b1d86829059d59e995a20b410 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 23 Mar 2026 16:34:30 +0530 Subject: [PATCH 1/3] Add variant utility functions and JSON handling to ContentstackUtils --- CHANGELOG.md | 88 ++++++++ ContentstackUtils.xcodeproj/project.pbxproj | 14 ++ .../ContentstackUtils/ContentstackUtils.swift | 78 +++++++ Tests/ContentstackUtilsTests/TestClient.swift | 21 ++ .../VariantUtilityTests.swift | 212 ++++++++++++++++++ .../XCTestManifests.swift | 3 +- .../variantsEntries.json | 71 ++++++ .../variantsSingleEntry.json | 39 ++++ 8 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 Tests/ContentstackUtilsTests/VariantUtilityTests.swift create mode 100644 Tests/ContentstackUtilsTests/variantsEntries.json create mode 100644 Tests/ContentstackUtilsTests/variantsSingleEntry.json 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 + } + } + } + } + } From 0bbd2983bafb5b1e1b1ab083884b47ed85cebc06 Mon Sep 17 00:00:00 2001 From: Harshitha D Date: Fri, 27 Mar 2026 13:18:49 +0530 Subject: [PATCH 2/3] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 696bffe..9535c61 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016-2025 Contentstack +Copyright (c) 2016-2026 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 8b88f71cb0389159979019fcc54794a325502e86 Mon Sep 17 00:00:00 2001 From: Harshitha D Date: Fri, 27 Mar 2026 13:29:07 +0530 Subject: [PATCH 3/3] Update check-branch.yml --- .github/workflows/check-branch.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml index 1e2d24a..2332f0d 100644 --- a/.github/workflows/check-branch.yml +++ b/.github/workflows/check-branch.yml @@ -8,13 +8,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Comment PR - if: github.base_ref == 'master' && github.head_ref != 'next' + if: github.base_ref == 'master' && github.head_ref != 'staging' uses: thollander/actions-comment-pull-request@v2 with: message: | - We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch. + We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the staging branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch. - name: Check branch - if: github.base_ref == 'master' && github.head_ref != 'next' + if: github.base_ref == 'master' && github.head_ref != 'staging' run: | - echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch." - exit 1 \ No newline at end of file + echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the staging branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch." + exit 1