From c2d299d6ca85165a21b52379c6708299bc881e89 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 21 Jul 2025 21:38:08 -0400 Subject: [PATCH 1/3] Merge `JSONValueTests+Coverage.swift` into `JSONValueTests.swift` --- .../Tests/Unit/JSONValueTests+Coverage.swift | 88 ------------------- FirebaseAI/Tests/Unit/JSONValueTests.swift | 68 ++++++++++++++ 2 files changed, 68 insertions(+), 88 deletions(-) delete mode 100644 FirebaseAI/Tests/Unit/JSONValueTests+Coverage.swift diff --git a/FirebaseAI/Tests/Unit/JSONValueTests+Coverage.swift b/FirebaseAI/Tests/Unit/JSONValueTests+Coverage.swift deleted file mode 100644 index fce13ed8f3a..00000000000 --- a/FirebaseAI/Tests/Unit/JSONValueTests+Coverage.swift +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import XCTest - -@testable import FirebaseAI - -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension JSONValueTests { - func testDecodeNestedObject() throws { - let nestedObject: JSONObject = [ - "nestedKey": .string("nestedValue"), - ] - let expectedObject: JSONObject = [ - "numberKey": .number(numberValue), - "objectKey": .object(nestedObject), - ] - let json = """ - { - "numberKey": \(numberValue), - "objectKey": { - "nestedKey": "nestedValue" - } - } - """ - let jsonData = try XCTUnwrap(json.data(using: .utf8)) - - let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) - - XCTAssertEqual(jsonObject, .object(expectedObject)) - } - - func testDecodeNestedArray() throws { - let nestedArray: [JSONValue] = [.string("a"), .string("b")] - let expectedObject: JSONObject = [ - "numberKey": .number(numberValue), - "arrayKey": .array(nestedArray), - ] - let json = """ - { - "numberKey": \(numberValue), - "arrayKey": ["a", "b"] - } - """ - let jsonData = try XCTUnwrap(json.data(using: .utf8)) - - let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) - - XCTAssertEqual(jsonObject, .object(expectedObject)) - } - - func testEncodeNestedObject() throws { - let nestedObject: JSONObject = [ - "nestedKey": .string("nestedValue"), - ] - let objectValue: JSONObject = [ - "numberKey": .number(numberValue), - "objectKey": .object(nestedObject), - ] - - let jsonData = try encoder.encode(JSONValue.object(objectValue)) - let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) - XCTAssertEqual(jsonObject, .object(objectValue)) - } - - func testEncodeNestedArray() throws { - let nestedArray: [JSONValue] = [.string("a"), .string("b")] - let objectValue: JSONObject = [ - "numberKey": .number(numberValue), - "arrayKey": .array(nestedArray), - ] - - let jsonData = try encoder.encode(JSONValue.object(objectValue)) - let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) - XCTAssertEqual(jsonObject, .object(objectValue)) - } -} diff --git a/FirebaseAI/Tests/Unit/JSONValueTests.swift b/FirebaseAI/Tests/Unit/JSONValueTests.swift index 1ffe88eaf55..54ac3520e77 100644 --- a/FirebaseAI/Tests/Unit/JSONValueTests.swift +++ b/FirebaseAI/Tests/Unit/JSONValueTests.swift @@ -97,6 +97,48 @@ final class JSONValueTests: XCTestCase { XCTAssertEqual(json, "null") } + func testDecodeNestedObject() throws { + let nestedObject: JSONObject = [ + "nestedKey": .string("nestedValue"), + ] + let expectedObject: JSONObject = [ + "numberKey": .number(numberValue), + "objectKey": .object(nestedObject), + ] + let json = """ + { + "numberKey": \(numberValue), + "objectKey": { + "nestedKey": "nestedValue" + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) + + XCTAssertEqual(jsonObject, .object(expectedObject)) + } + + func testDecodeNestedArray() throws { + let nestedArray: [JSONValue] = [.string("a"), .string("b")] + let expectedObject: JSONObject = [ + "numberKey": .number(numberValue), + "arrayKey": .array(nestedArray), + ] + let json = """ + { + "numberKey": \(numberValue), + "arrayKey": ["a", "b"] + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) + + XCTAssertEqual(jsonObject, .object(expectedObject)) + } + func testEncodeNumber() throws { let jsonData = try encoder.encode(JSONValue.number(numberValue)) @@ -143,4 +185,30 @@ final class JSONValueTests: XCTestCase { let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) XCTAssertEqual(json, "[null,\(numberValueEncoded)]") } + + func testEncodeNestedObject() throws { + let nestedObject: JSONObject = [ + "nestedKey": .string("nestedValue"), + ] + let objectValue: JSONObject = [ + "numberKey": .number(numberValue), + "objectKey": .object(nestedObject), + ] + + let jsonData = try encoder.encode(JSONValue.object(objectValue)) + let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) + XCTAssertEqual(jsonObject, .object(objectValue)) + } + + func testEncodeNestedArray() throws { + let nestedArray: [JSONValue] = [.string("a"), .string("b")] + let objectValue: JSONObject = [ + "numberKey": .number(numberValue), + "arrayKey": .array(nestedArray), + ] + + let jsonData = try encoder.encode(JSONValue.object(objectValue)) + let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData)) + XCTAssertEqual(jsonObject, .object(objectValue)) + } } From 8acd4409e63cfbedbe63c9225cde95d388fbe5a9 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 21 Jul 2025 21:42:01 -0400 Subject: [PATCH 2/3] Merge `PartsRepresentableTests+Coverage.swift` into `PartsRepresentableTests.swift` --- .../PartsRepresentableTests+Coverage.swift | 76 ------------------- .../Tests/Unit/PartsRepresentableTests.swift | 51 +++++++++++++ 2 files changed, 51 insertions(+), 76 deletions(-) delete mode 100644 FirebaseAI/Tests/Unit/PartsRepresentableTests+Coverage.swift diff --git a/FirebaseAI/Tests/Unit/PartsRepresentableTests+Coverage.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests+Coverage.swift deleted file mode 100644 index e7c001f2c17..00000000000 --- a/FirebaseAI/Tests/Unit/PartsRepresentableTests+Coverage.swift +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import XCTest -#if canImport(UIKit) - import UIKit -#elseif canImport(AppKit) - import AppKit -#endif - -@testable import FirebaseAI - -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension PartsRepresentableTests { - func testMixedParts() throws { - let text = "This is a test" - let data = try XCTUnwrap("This is some data".data(using: .utf8)) - let inlineData = InlineDataPart(data: data, mimeType: "text/plain") - - let parts: [any PartsRepresentable] = [text, inlineData] - let modelContent = ModelContent(parts: parts) - - XCTAssertEqual(modelContent.parts.count, 2) - let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) - XCTAssertEqual(textPart.text, text) - let dataPart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) - XCTAssertEqual(dataPart, inlineData) - } - - #if canImport(UIKit) - func testMixedParts_withImage() throws { - let text = "This is a test" - let image = try XCTUnwrap(UIImage(systemName: "star")) - let parts: [any PartsRepresentable] = [text, image] - let modelContent = ModelContent(parts: parts) - - XCTAssertEqual(modelContent.parts.count, 2) - let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) - XCTAssertEqual(textPart.text, text) - let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) - XCTAssertEqual(imagePart.mimeType, "image/jpeg") - XCTAssertFalse(imagePart.data.isEmpty) - } - - #elseif canImport(AppKit) - func testMixedParts_withImage() throws { - let text = "This is a test" - let coreImage = CIImage(color: CIColor.blue) - .cropped(to: CGRect(origin: CGPoint.zero, size: CGSize(width: 16, height: 16))) - let rep = NSCIImageRep(ciImage: coreImage) - let image = NSImage(size: rep.size) - image.addRepresentation(rep) - - let parts: [any PartsRepresentable] = [text, image] - let modelContent = ModelContent(parts: parts) - - XCTAssertEqual(modelContent.parts.count, 2) - let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) - XCTAssertEqual(textPart.text, text) - let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) - XCTAssertEqual(imagePart.mimeType, "image/jpeg") - XCTAssertFalse(imagePart.data.isEmpty) - } - #endif -} diff --git a/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift index e7531d1da9e..658db79a50e 100644 --- a/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift @@ -121,4 +121,55 @@ final class PartsRepresentableTests: XCTestCase { } } #endif + + func testMixedParts() throws { + let text = "This is a test" + let data = try XCTUnwrap("This is some data".data(using: .utf8)) + let inlineData = InlineDataPart(data: data, mimeType: "text/plain") + + let parts: [any PartsRepresentable] = [text, inlineData] + let modelContent = ModelContent(parts: parts) + + XCTAssertEqual(modelContent.parts.count, 2) + let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) + XCTAssertEqual(textPart.text, text) + let dataPart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) + XCTAssertEqual(dataPart, inlineData) + } + + #if canImport(UIKit) + func testMixedParts_withImage() throws { + let text = "This is a test" + let image = try XCTUnwrap(UIImage(systemName: "star")) + let parts: [any PartsRepresentable] = [text, image] + let modelContent = ModelContent(parts: parts) + + XCTAssertEqual(modelContent.parts.count, 2) + let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) + XCTAssertEqual(textPart.text, text) + let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) + XCTAssertEqual(imagePart.mimeType, "image/jpeg") + XCTAssertFalse(imagePart.data.isEmpty) + } + + #elseif canImport(AppKit) + func testMixedParts_withImage() throws { + let text = "This is a test" + let coreImage = CIImage(color: CIColor.blue) + .cropped(to: CGRect(origin: CGPoint.zero, size: CGSize(width: 16, height: 16))) + let rep = NSCIImageRep(ciImage: coreImage) + let image = NSImage(size: rep.size) + image.addRepresentation(rep) + + let parts: [any PartsRepresentable] = [text, image] + let modelContent = ModelContent(parts: parts) + + XCTAssertEqual(modelContent.parts.count, 2) + let textPart = try XCTUnwrap(modelContent.parts[0] as? TextPart) + XCTAssertEqual(textPart.text, text) + let imagePart = try XCTUnwrap(modelContent.parts[1] as? InlineDataPart) + XCTAssertEqual(imagePart.mimeType, "image/jpeg") + XCTAssertFalse(imagePart.data.isEmpty) + } + #endif } From 68447dd7d9d28b9cd632aa457a9ba26263835de4 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 21 Jul 2025 21:51:07 -0400 Subject: [PATCH 3/3] Setup `JSONEncoder.outputFormatting` in `SafetyTests` to match other tests --- FirebaseAI/Tests/Unit/SafetyTests.swift | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/FirebaseAI/Tests/Unit/SafetyTests.swift b/FirebaseAI/Tests/Unit/SafetyTests.swift index 680567617c4..4a1e07e04e3 100644 --- a/FirebaseAI/Tests/Unit/SafetyTests.swift +++ b/FirebaseAI/Tests/Unit/SafetyTests.swift @@ -21,6 +21,12 @@ final class SafetyTests: XCTestCase { let decoder = JSONDecoder() let encoder = JSONEncoder() + override func setUp() { + encoder.outputFormatting = .init( + arrayLiteral: .prettyPrinted, .sortedKeys, .withoutEscapingSlashes + ) + } + // MARK: - SafetyRating Decoding func testDecodeSafetyRating_allFieldsPresent() throws { @@ -87,12 +93,15 @@ final class SafetyTests: XCTestCase { threshold: .blockMediumAndAbove, method: .severity ) - encoder.outputFormatting = .sortedKeys let jsonData = try encoder.encode(setting) let jsonString = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) XCTAssertEqual(jsonString, """ - {"category":"HARM_CATEGORY_HATE_SPEECH","method":"SEVERITY","threshold":"BLOCK_MEDIUM_AND_ABOVE"} + { + "category" : "HARM_CATEGORY_HATE_SPEECH", + "method" : "SEVERITY", + "threshold" : "BLOCK_MEDIUM_AND_ABOVE" + } """) } @@ -101,12 +110,14 @@ final class SafetyTests: XCTestCase { harmCategory: .sexuallyExplicit, threshold: .blockOnlyHigh ) - encoder.outputFormatting = .sortedKeys let jsonData = try encoder.encode(setting) let jsonString = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) XCTAssertEqual(jsonString, """ - {"category":"HARM_CATEGORY_SEXUALLY_EXPLICIT","threshold":"BLOCK_ONLY_HIGH"} + { + "category" : "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "threshold" : "BLOCK_ONLY_HIGH" + } """) } }