diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35fdec7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +run_tests +*.d diff --git a/BDVCountryNameAndCode.h b/BDVCountryNameAndCode.h index 1f0dcd6..ad03ba2 100644 --- a/BDVCountryNameAndCode.h +++ b/BDVCountryNameAndCode.h @@ -7,13 +7,30 @@ // #import +#if __has_include() +#import +#endif -@interface BDVCountryNameAndCode : NSObject +@interface BDVCountryNameAndCode : NSObject { + NSArray *BDVCountryNameAndCodePlist; + NSMutableDictionary *dictDialingCodes; + NSMutableArray *countryNames; + NSMutableArray *prefixDialingCodes; +} + +-(instancetype)initWithPlistPath:(NSString *)plistPath; -(NSString *)prefixForCurrentLocale; -(NSString *)countryNameForCurrentLocale; +#if __has_include() -(UIImage *)countryFlagForCurrentLocale; +#endif + +-(NSString *)prefixForCountryCode:(NSString *)countryCode; +-(NSString *)countryNameAtIndex:(NSUInteger)index; +-(NSArray *)allCountryNames; +-(NSArray *)allPrefixDialingCodes; // returns possible region codes for provided prefix --(NSArray *)getCountrySuffixForPrefix:(NSString*)prefix; +-(NSArray *)getCountrySuffixForPrefix:(id)prefix; @end diff --git a/BDVCountryNameAndCode.m b/BDVCountryNameAndCode.m index 6f38da4..11d4530 100644 --- a/BDVCountryNameAndCode.m +++ b/BDVCountryNameAndCode.m @@ -9,11 +9,28 @@ #import "BDVCountryNameAndCode.h" -@implementation BDVCountryNameAndCode{ - NSArray *BDVCountryNameAndCodePlist; - NSMutableDictionary *dictDialingCodes; - NSMutableArray *countryNames; - NSMutableArray *prefixDialingCodes; +@implementation BDVCountryNameAndCode + +-(void)loadPlistFromPath:(NSString *)path { + NSArray *plist = [NSArray arrayWithContentsOfFile:path]; + if (!plist) return; + BDVCountryNameAndCodePlist = plist; + for (NSDictionary* obj in BDVCountryNameAndCodePlist) { + [countryNames addObject:[obj objectForKey:@"name"]]; + [prefixDialingCodes addObject:[obj objectForKey:@"dial_code"]]; + [dictDialingCodes setObject:[obj objectForKey:@"dial_code"] forKey:[obj objectForKey:@"code"]]; + } +} + +-(instancetype)initWithPlistPath:(NSString *)plistPath { + self = [super init]; + if (self) { + countryNames = [[NSMutableArray alloc] init]; + prefixDialingCodes = [[NSMutableArray alloc] init]; + dictDialingCodes = [[NSMutableDictionary alloc] init]; + [self loadPlistFromPath:plistPath]; + } + return self; } -(id)init{ @@ -278,19 +295,12 @@ -(id)init{ } */ - NSString* path = [[NSBundle mainBundle] pathForResource:@"BDVCountryNameAndCode" ofType:@"plist"]; - - BDVCountryNameAndCodePlist = [NSArray arrayWithContentsOfFile:path]; - countryNames = [[NSMutableArray alloc] init]; prefixDialingCodes = [[NSMutableArray alloc] init]; dictDialingCodes = [[NSMutableDictionary alloc] init]; - for (NSDictionary* obj in BDVCountryNameAndCodePlist) { - [countryNames addObject:[obj objectForKey:@"name"]]; - [prefixDialingCodes addObject:[obj objectForKey:@"dial_code"]]; - [dictDialingCodes setObject:[obj objectForKey:@"dial_code"] forKey:[obj objectForKey:@"code"]]; - } + NSString* path = [[NSBundle mainBundle] pathForResource:@"BDVCountryNameAndCode" ofType:@"plist"]; + [self loadPlistFromPath:path]; } return self; @@ -314,6 +324,7 @@ -(NSString *)countryNameForCurrentLocale { return countryName; } +#if __has_include() -(UIImage *)countryFlagForCurrentLocale { NSString *imageName = [NSString stringWithFormat:@"%@.png",[[NSLocale currentLocale] displayNameForKey:NSLocaleCountryCode value:[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]]]; @@ -328,8 +339,26 @@ -(UIImage *)countryFlagForCurrentLocale { return [UIImage new]; } +#endif + +-(NSString *)prefixForCountryCode:(NSString *)countryCode { + return [dictDialingCodes objectForKey:[countryCode uppercaseString]]; +} + +-(NSString *)countryNameAtIndex:(NSUInteger)index { + if (index >= [countryNames count]) return nil; + return [countryNames objectAtIndex:index]; +} + +-(NSArray *)allCountryNames { + return [countryNames copy]; +} + +-(NSArray *)allPrefixDialingCodes { + return [prefixDialingCodes copy]; +} --(NSArray *)getCountrySuffixForPrefix:(NSString *)prefix{ +-(NSArray *)getCountrySuffixForPrefix:(id)prefix{ return [dictDialingCodes allKeysForObject:prefix]; } diff --git a/BDVCountryNameAndCodeTests.m b/BDVCountryNameAndCodeTests.m new file mode 100644 index 0000000..b9071f3 --- /dev/null +++ b/BDVCountryNameAndCodeTests.m @@ -0,0 +1,240 @@ +// +// BDVCountryNameAndCodeTests.m +// BDVCountryNameAndCode +// +// Unit tests for BDVCountryNameAndCode methods. +// + +#import "BDVCountryNameAndCode.h" + +static int totalTests = 0; +static int passedTests = 0; +static int failedTests = 0; + +static void assertCondition(BOOL condition, const char *testName) { + totalTests++; + if (condition) { + passedTests++; + printf(" PASS: %s\n", testName); + } else { + failedTests++; + printf(" FAIL: %s\n", testName); + } +} + +#pragma mark - getCountrySuffixForPrefix tests + +static void testGetCountrySuffixForPrefix_uniqueCode(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: unique dial code ---\n"); + + // Afghanistan has dial_code 93 (stored as NSNumber) + // The method takes NSString, so passing an NSNumber is required for plist integer values + NSArray *result = [sut getCountrySuffixForPrefix:@93]; + assertCondition(result != nil, "result is not nil for Afghanistan dial code 93"); + assertCondition([result count] == 1, "exactly one country for dial code 93"); + assertCondition([result containsObject:@"AF"], "AF returned for dial code 93"); +} + +static void testGetCountrySuffixForPrefix_sharedCode(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: shared dial code ---\n"); + + // dial_code 44 is shared by GB, GG, IM, JE (all stored as NSNumber) + NSArray *result = [sut getCountrySuffixForPrefix:@44]; + assertCondition(result != nil, "result is not nil for dial code 44"); + assertCondition([result count] == 4, "four countries share dial code 44"); + assertCondition([result containsObject:@"GB"], "GB in results for dial code 44"); + assertCondition([result containsObject:@"GG"], "GG in results for dial code 44"); + assertCondition([result containsObject:@"IM"], "IM in results for dial code 44"); + assertCondition([result containsObject:@"JE"], "JE in results for dial code 44"); +} + +static void testGetCountrySuffixForPrefix_stringDialCode(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: string dial code ---\n"); + + // AmericanSamoa has dial_code "+1 684" (stored as NSString) + NSArray *result = [sut getCountrySuffixForPrefix:@"+1 684"]; + assertCondition(result != nil, "result is not nil for +1 684"); + assertCondition([result count] == 1, "one country for +1 684"); + assertCondition([result containsObject:@"AS"], "AS returned for +1 684"); +} + +static void testGetCountrySuffixForPrefix_noMatch(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: no matching prefix ---\n"); + + NSArray *result = [sut getCountrySuffixForPrefix:@"+99999"]; + assertCondition([result count] == 0, "empty/nil result for non-existent prefix +99999"); +} + +static void testGetCountrySuffixForPrefix_nilPrefix(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: nil prefix ---\n"); + + NSArray *result = [sut getCountrySuffixForPrefix:nil]; + assertCondition(result != nil || result == nil, "does not crash with nil prefix"); +} + +static void testGetCountrySuffixForPrefix_multipleSharedCodes(BDVCountryNameAndCode *sut) { + printf("\n--- getCountrySuffixForPrefix: additional shared codes ---\n"); + + // dial_code 61 is shared by AU, CX, CC + NSArray *result61 = [sut getCountrySuffixForPrefix:@61]; + assertCondition(result61 != nil, "result not nil for dial code 61"); + assertCondition([result61 count] == 3, "three countries share dial code 61"); + assertCondition([result61 containsObject:@"AU"], "AU in results for dial code 61"); + assertCondition([result61 containsObject:@"CX"], "CX in results for dial code 61"); + assertCondition([result61 containsObject:@"CC"], "CC in results for dial code 61"); + + // dial_code 1 is shared by CA, US + NSArray *result1 = [sut getCountrySuffixForPrefix:@1]; + assertCondition(result1 != nil, "result not nil for dial code 1"); + assertCondition([result1 count] == 2, "two countries share dial code 1"); + assertCondition([result1 containsObject:@"US"], "US in results for dial code 1"); + assertCondition([result1 containsObject:@"CA"], "CA in results for dial code 1"); + + // dial_code 47 is shared by NO, SJ + NSArray *result47 = [sut getCountrySuffixForPrefix:@47]; + assertCondition(result47 != nil, "result not nil for dial code 47"); + assertCondition([result47 count] == 2, "two countries share dial code 47"); + assertCondition([result47 containsObject:@"NO"], "NO in results for dial code 47"); + assertCondition([result47 containsObject:@"SJ"], "SJ in results for dial code 47"); +} + +#pragma mark - prefixForCountryCode tests + +static void testPrefixForCountryCode(BDVCountryNameAndCode *sut) { + printf("\n--- prefixForCountryCode ---\n"); + + // Known entries (integer dial codes stored as NSNumber) + id prefix = [sut prefixForCountryCode:@"US"]; + assertCondition(prefix != nil, "US has a prefix"); + assertCondition([prefix isEqual:@1], "US prefix is 1"); + + prefix = [sut prefixForCountryCode:@"DE"]; + assertCondition(prefix != nil, "DE has a prefix"); + assertCondition([prefix isEqual:@49], "DE prefix is 49"); + + prefix = [sut prefixForCountryCode:@"JP"]; + assertCondition(prefix != nil, "JP has a prefix"); + assertCondition([prefix isEqual:@81], "JP prefix is 81"); + + // Case insensitivity (uppercaseString is applied) + prefix = [sut prefixForCountryCode:@"us"]; + assertCondition(prefix != nil, "lowercase 'us' resolves to a prefix"); + assertCondition([prefix isEqual:@1], "lowercase 'us' prefix is 1"); + + // Non-existent country code + prefix = [sut prefixForCountryCode:@"ZZ"]; + assertCondition(prefix == nil, "ZZ returns nil"); +} + +#pragma mark - countryNameAtIndex tests + +static void testCountryNameAtIndex(BDVCountryNameAndCode *sut) { + printf("\n--- countryNameAtIndex ---\n"); + + // First entry in plist is Israel + NSString *name = [sut countryNameAtIndex:0]; + assertCondition(name != nil, "index 0 returns a name"); + assertCondition([name isEqualToString:@"Israel"], "index 0 is Israel"); + + // Second entry is Afghanistan + name = [sut countryNameAtIndex:1]; + assertCondition(name != nil, "index 1 returns a name"); + assertCondition([name isEqualToString:@"Afghanistan"], "index 1 is Afghanistan"); + + // Out-of-bounds returns nil + NSArray *allNames = [sut allCountryNames]; + name = [sut countryNameAtIndex:[allNames count]]; + assertCondition(name == nil, "out-of-bounds index returns nil"); +} + +#pragma mark - allCountryNames / allPrefixDialingCodes tests + +static void testAllCountryNames(BDVCountryNameAndCode *sut) { + printf("\n--- allCountryNames ---\n"); + + NSArray *names = [sut allCountryNames]; + assertCondition(names != nil, "allCountryNames is not nil"); + assertCondition([names count] == 242, "242 country names loaded from plist"); + assertCondition([names containsObject:@"Israel"], "contains Israel"); + assertCondition([names containsObject:@"Germany"], "contains Germany"); + assertCondition([names containsObject:@"Japan"], "contains Japan"); +} + +static void testAllPrefixDialingCodes(BDVCountryNameAndCode *sut) { + printf("\n--- allPrefixDialingCodes ---\n"); + + NSArray *codes = [sut allPrefixDialingCodes]; + assertCondition(codes != nil, "allPrefixDialingCodes is not nil"); + assertCondition([codes count] == 242, "242 dialing codes loaded from plist"); +} + +#pragma mark - initWithPlistPath tests + +static void testInitWithInvalidPath(void) { + printf("\n--- initWithPlistPath: invalid path ---\n"); + + BDVCountryNameAndCode *sut = [[BDVCountryNameAndCode alloc] initWithPlistPath:@"/nonexistent/path.plist"]; + assertCondition(sut != nil, "init does not return nil for invalid path"); + NSArray *names = [sut allCountryNames]; + assertCondition([names count] == 0, "no country names loaded for invalid plist path"); + NSArray *result = [sut getCountrySuffixForPrefix:@1]; + assertCondition([result count] == 0, "no results from getCountrySuffixForPrefix with empty data"); +} + +static void testInitWithNilPath(void) { + printf("\n--- initWithPlistPath: nil path ---\n"); + + BDVCountryNameAndCode *sut = [[BDVCountryNameAndCode alloc] initWithPlistPath:nil]; + assertCondition(sut != nil, "init does not return nil for nil path"); + NSArray *names = [sut allCountryNames]; + assertCondition([names count] == 0, "no country names loaded for nil plist path"); +} + +#pragma mark - Test runner + +int main(int argc, const char *argv[]) { + @autoreleasepool { + if (argc < 2) { + printf("Usage: %s \n", argv[0]); + return 1; + } + + NSString *plistPath = [NSString stringWithUTF8String:argv[1]]; + printf("Loading plist from: %s\n", argv[1]); + + BDVCountryNameAndCode *sut = [[BDVCountryNameAndCode alloc] initWithPlistPath:plistPath]; + if (!sut) { + printf("FATAL: Failed to initialize BDVCountryNameAndCode\n"); + return 1; + } + + printf("=== BDVCountryNameAndCode Unit Tests ===\n"); + + // getCountrySuffixForPrefix tests (primary focus) + testGetCountrySuffixForPrefix_uniqueCode(sut); + testGetCountrySuffixForPrefix_sharedCode(sut); + testGetCountrySuffixForPrefix_stringDialCode(sut); + testGetCountrySuffixForPrefix_noMatch(sut); + testGetCountrySuffixForPrefix_nilPrefix(sut); + testGetCountrySuffixForPrefix_multipleSharedCodes(sut); + + // prefixForCountryCode tests + testPrefixForCountryCode(sut); + + // countryNameAtIndex tests + testCountryNameAtIndex(sut); + + // allCountryNames / allPrefixDialingCodes tests + testAllCountryNames(sut); + testAllPrefixDialingCodes(sut); + + // Edge case: invalid/nil init paths + testInitWithInvalidPath(); + testInitWithNilPath(); + + printf("\n=== Results: %d/%d passed, %d failed ===\n", + passedTests, totalTests, failedTests); + + return failedTests > 0 ? 1 : 0; + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ffd3117 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CC = clang +GCC_OBJC_INCLUDE = $(shell ls -d /usr/lib/gcc/x86_64-linux-gnu/*/include 2>/dev/null | tail -1) +OBJC_FLAGS = $(shell gnustep-config --objc-flags) -I$(GCC_OBJC_INCLUDE) +OBJC_LIBS = $(shell gnustep-config --objc-libs) -lgnustep-base + +SRC_DIR = . +TEST_BINARY = run_tests +PLIST = $(SRC_DIR)/BDVCountryNameAndCode.plist + +SOURCES = $(SRC_DIR)/BDVCountryNameAndCode.m $(SRC_DIR)/BDVCountryNameAndCodeTests.m + +.PHONY: test clean + +test: $(TEST_BINARY) + ./$(TEST_BINARY) $(PLIST) + +$(TEST_BINARY): $(SOURCES) $(SRC_DIR)/BDVCountryNameAndCode.h + $(CC) $(OBJC_FLAGS) -o $@ $(SOURCES) $(OBJC_LIBS) + +clean: + rm -f $(TEST_BINARY)