diff --git a/MonsterCards.xcodeproj/project.pbxproj b/MonsterCards.xcodeproj/project.pbxproj index 5d80e08..7f48f93 100644 --- a/MonsterCards.xcodeproj/project.pbxproj +++ b/MonsterCards.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ E20D032B25031BE500FB6E43 /* LibraryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E20D032A25031BE500FB6E43 /* LibraryViewController.m */; }; E20D032E25031BEF00FB6E43 /* CollectionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E20D032D25031BEF00FB6E43 /* CollectionsViewController.m */; }; E20D033125031BFD00FB6E43 /* DashboardViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E20D033025031BFD00FB6E43 /* DashboardViewController.m */; }; + E22F837C2511D14E0072105C /* JSONHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E22F837B2511D14E0072105C /* JSONHelper.m */; }; + E22F837F2511E8500072105C /* JSONHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E22F837E2511E8500072105C /* JSONHelperTests.m */; }; E2532E8925038DE100CA4CBA /* StringHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = E2532E8825038DE100CA4CBA /* StringHelper.m */; }; E2592B8D250D6B8100906A40 /* EditMonsterViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2592B8C250D6B8100906A40 /* EditMonsterViewControllerTests.m */; }; E25BD5F5250352C4007B04EF /* Monster.m in Sources */ = {isa = PBXBuildFile; fileRef = E25BD5F4250352C4007B04EF /* Monster.m */; }; @@ -82,6 +84,9 @@ E20D032D25031BEF00FB6E43 /* CollectionsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CollectionsViewController.m; sourceTree = ""; }; E20D032F25031BFD00FB6E43 /* DashboardViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DashboardViewController.h; sourceTree = ""; }; E20D033025031BFD00FB6E43 /* DashboardViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DashboardViewController.m; sourceTree = ""; }; + E22F837A2511D14E0072105C /* JSONHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSONHelper.h; sourceTree = ""; }; + E22F837B2511D14E0072105C /* JSONHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSONHelper.m; sourceTree = ""; }; + E22F837E2511E8500072105C /* JSONHelperTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JSONHelperTests.m; sourceTree = ""; }; E2532E8725038DE100CA4CBA /* StringHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StringHelper.h; sourceTree = ""; }; E2532E8825038DE100CA4CBA /* StringHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StringHelper.m; sourceTree = ""; }; E2591EB62509DD4900B396FD /* EditableFormFieldDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EditableFormFieldDelegate.h; sourceTree = ""; }; @@ -234,6 +239,16 @@ E2532E8825038DE100CA4CBA /* StringHelper.m */, E26A73552511BA1900C5677E /* HTMLHelper.h */, E26A73562511BA1900C5677E /* HTMLHelper.m */, + E22F837A2511D14E0072105C /* JSONHelper.h */, + E22F837B2511D14E0072105C /* JSONHelper.m */, + ); + path = Helpers; + sourceTree = ""; + }; + E22F837D2511E8350072105C /* Helpers */ = { + isa = PBXGroup; + children = ( + E22F837E2511E8500072105C /* JSONHelperTests.m */, ); path = Helpers; sourceTree = ""; @@ -302,6 +317,7 @@ E2F7249425005E8A007D87ED /* MonsterCardsTests */ = { isa = PBXGroup; children = ( + E22F837D2511E8350072105C /* Helpers */, E2592B8B250D6B6000906A40 /* Views */, E2FD91E225047C1D00D5E935 /* Models */, E2F7249525005E8A007D87ED /* MonsterCardsTests.m */, @@ -591,6 +607,7 @@ E25BD5FB250369D7007B04EF /* Skill.m in Sources */, E2F7247825005E89007D87ED /* SceneDelegate.m in Sources */, E20D032425031B9D00FB6E43 /* SearchViewController.m in Sources */, + E22F837C2511D14E0072105C /* JSONHelper.m in Sources */, E25BD60125036BF8007B04EF /* Language.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -609,6 +626,7 @@ E2FD91E425047C4400D5E935 /* AbilityTests.m in Sources */, E2E25805250CC3A7002E7308 /* MonsterCards.xcdatamodeld in Sources */, E2F7249625005E8A007D87ED /* MonsterCardsTests.m in Sources */, + E22F837F2511E8500072105C /* JSONHelperTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -772,6 +790,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = J793L9LQJ2; + HEADER_SEARCH_PATHS = "$(SRCROOT)/Pods/**"; INFOPLIST_FILE = MonsterCards/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -791,6 +810,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = J793L9LQJ2; + HEADER_SEARCH_PATHS = "$(SRCROOT)/Pods/**"; INFOPLIST_FILE = MonsterCards/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/MonsterCards/Helpers/JSONHelper.h b/MonsterCards/Helpers/JSONHelper.h new file mode 100644 index 0000000..f8efb01 --- /dev/null +++ b/MonsterCards/Helpers/JSONHelper.h @@ -0,0 +1,21 @@ +// +// JSONHelper.h +// MonsterCards +// +// Created by Tom Hicks on 9/15/20. +// Copyright © 2020 Tom Hicks. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface JSONHelper : NSObject + ++(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key; ++(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSString* _Nullable)defaultValue; ++(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index; ++(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSString* _Nullable)defaultValue; +@end + +NS_ASSUME_NONNULL_END diff --git a/MonsterCards/Helpers/JSONHelper.m b/MonsterCards/Helpers/JSONHelper.m new file mode 100644 index 0000000..61c956e --- /dev/null +++ b/MonsterCards/Helpers/JSONHelper.m @@ -0,0 +1,39 @@ +// +// JSONHelper.m +// MonsterCards +// +// Created by Tom Hicks on 9/15/20. +// Copyright © 2020 Tom Hicks. All rights reserved. +// + +#import "JSONHelper.h" + +@implementation JSONHelper + +NSString* coerceObjectToString(NSObject *object, NSString *defaultValue) { + if ([object isKindOfClass:[NSString class]]) { + return (NSString*)object; + } + + return defaultValue; +} + ++(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key { + return [JSONHelper readStringFromDictionary:dictionary forKey:key withDefaultValue:nil]; +} + ++(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSString* _Nullable)defaultValue { + NSObject *object = [dictionary objectForKey:key]; + return coerceObjectToString(object, defaultValue); +} + ++(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index{ + return [JSONHelper readStringFromArray:array forIndex:index withDefaultValue:nil]; +} + ++(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSString* _Nullable)defaultValue { + NSObject *object = [array objectAtIndex:index]; + return coerceObjectToString(object, defaultValue); +} + +@end diff --git a/MonsterCardsTests/Helpers/JSONHelperTests.m b/MonsterCardsTests/Helpers/JSONHelperTests.m new file mode 100644 index 0000000..1acd0cc --- /dev/null +++ b/MonsterCardsTests/Helpers/JSONHelperTests.m @@ -0,0 +1,147 @@ +// +// JSONHelperTests.m +// MonsterCardsTests +// +// Created by Tom Hicks on 9/15/20. +// Copyright © 2020 Tom Hicks. All rights reserved. +// + +#import +#import "JSONHelper.h" + +@interface JSONHelperTests : XCTestCase + +@end + +@implementation JSONHelperTests { + NSString *_jsonStringKey; + NSString *_jsonStringValue; + NSString *_jsonStringFragment; + NSString *_jsonIntegerKey; + NSNumber *_jsonIntegerValue; + NSString *_jsonIntegerFragment; +} + +NSString* escapeStringForJSON(NSString *unescaped) { + return [[unescaped stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; +} + +NSDictionary* readJSONDictionaryFromString(NSString *jsonString) { + NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + if (![jsonRoot isKindOfClass:[NSDictionary class]]) { + return nil; + } else { + return jsonRoot; + } +} + +NSArray* readJSONArrayFromString(NSString *jsonString) { + NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + if (![jsonRoot isKindOfClass:[NSArray class]]) { + return nil; + } else { + return jsonRoot; + } +} + +- (void)setUp { + _jsonStringKey = @"my_string"; + _jsonStringValue = @"Hello, World!"; + _jsonStringFragment = [NSString stringWithFormat:@"\"%@\":\"%@\"", escapeStringForJSON(_jsonStringKey), escapeStringForJSON(_jsonStringValue)]; + _jsonIntegerKey = @"my_int"; + _jsonIntegerValue = @12345; + _jsonIntegerFragment = [NSString stringWithFormat:@"\"%@\":%@", escapeStringForJSON(_jsonIntegerKey), [_jsonIntegerValue stringValue]]; +} + +- (void)tearDown { +} + +#pragma mark - Strings in Dictionaries + +- (void)testReadStringFromDictionaryReturnsNilIfKeyNotPresent { + NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment]; + NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey]; + XCTAssertNil(readString); +} + +- (void)testReadStringFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent { + NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment]; + NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey withDefaultValue:_jsonStringValue]; + XCTAssertEqualObjects(_jsonStringValue, readString); +} + +- (void) testReadStringFromDictionaryReturnsCorrectValue { + NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment]; + NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey]; + XCTAssertEqualObjects(_jsonStringValue, readString); +} + +- (void)testReadStringFromDictionaryWithDefaultReturnsCorrectValue { + NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment]; + NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey withDefaultValue:@"Some other string"]; + XCTAssertEqualObjects(_jsonStringValue, readString); +} + +- (void) testReadStringFromDictionaryReturnsNilIfWrongType { + NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":%@}", _jsonStringKey, _jsonIntegerValue]; + NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey]; + XCTAssertNil(readString); +} + +#pragma mark - Strings in Arrays + +- (void)testReadStringFromArrayReturnsNilIfNotAString { + NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue]; + NSArray *jsonRoot = readJSONArrayFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0]; + XCTAssertNil(readString); +} + +- (void)testReadStringFromArrayWithDefaultReturnsDefaultValueIfNotAString { + NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue]; + NSArray *jsonRoot = readJSONArrayFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0 withDefaultValue:_jsonStringValue]; + XCTAssertEqualObjects(_jsonStringValue, readString); +} + +- (void)testReadStringFromArrayThrowsIfIndexOutOfRange { + // TODO: Decide if this should throw or return nil + NSString *jsonString = @"[]"; + NSArray *jsonRoot = readJSONArrayFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + XCTAssertThrows([JSONHelper readStringFromArray:jsonRoot forIndex:0]); + XCTAssertThrows([JSONHelper readStringFromArray:jsonRoot forIndex:-1]); +} + +- (void)testReadStringFromArrayReturnsCorrectValue { + NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue]; + NSArray *jsonRoot = readJSONArrayFromString(jsonString); + XCTAssertNotNil(jsonRoot); + + NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0]; + XCTAssertEqualObjects(_jsonStringValue, readString); +} + +@end