/**
 * Copyright (c) 2016-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import * as AlgebraicType from '../../algebraic-type' ; import * as Coding from '../../plugins/coding' ; import * as Error from '../../error' ; import * as CLangCommon from '../../clang-common' ; import * as ObjC from '../../objc' ; import * as ObjectSpec from '../../object-spec' ; import * as ObjectSpecHelpers from '../../object-spec-helpers' ; const ObjectSpecPlugin = Coding . createPlugin ( ) ; const AlgebraicTypePlugin = Coding . createAlgebraicTypePlugin ( ) ; describe ( 'ObjectSpecPlugins.Coding' , function ( ) { describe ( '#validationErrors' , function ( ) { it ( 'returns no validation errors when there are no attributes on the found type' , function ( ) { const objectType = { annotations : { } , attributes : [ ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const errors = ObjectSpecPlugin . validationErrors ( objectType ) ; expect ( errors ) . toEqualJSON ( [ ] ) ; } ) ; it ( 'returns a validation error when there is an attribute with an unknown type' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . asObject ( ) , new ObjectSpecHelpers . AttributeBuilder ( 'likeStatus' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'LikeStatus' , 'LikeStatus' , null ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const errors = ObjectSpecPlugin . validationErrors ( objectType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the type "LikeStatus" from Foo.likeStatus. Did you forget to declare a backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; it ( 'returns two validation errors when there are two attributes with unknown types' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'Name' , 'Name' , null ) ) . asObject ( ) , new ObjectSpecHelpers . AttributeBuilder ( 'likeStatus' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'LikeStatus' , 'LikeStatus' , null ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const errors = ObjectSpecPlugin . validationErrors ( objectType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the type "Name" from Foo.name. Did you forget to declare a backing type?' ) , Error . Error ( 'The Coding plugin does not know how to decode and encode the type "LikeStatus" from Foo.likeStatus. Did you forget to declare a backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; it ( 'returns a validation error when there is an attribute with an unknown underlying type' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'FooBar' , 'FooBar' , 'Baz' ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const errors = ObjectSpecPlugin . validationErrors ( objectType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "Baz" from Foo.name. Did you declare the wrong backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; it ( 'returns a validation error when there is an attribute with legacy key with unsupported type' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'size' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'CGSize' , 'CGSize' , null ) ) . withAnnotations ( { codingLegacyKey : [ { properties : { name : 'legacySizeCodingKey' } } , ] } ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const errors = ObjectSpecPlugin . validationErrors ( objectType ) ; const expectedErrors = [ Error . Error ( '%codingLegacyKey can\'t be used with "CGSize" at Foo.size.' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; } ) ; describe ( '#instanceMethods' , function ( ) { it ( 'returns no instance methods when there are no attributes on the found type' , function ( ) { const objectType = { annotations : { } , attributes : [ ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const instanceMethods = ObjectSpecPlugin . instanceMethods ( objectType ) ; expect ( instanceMethods ) . toEqualJSON ( [ ] ) ; } ) ; it ( 'returns two instance methods which will encode and decode a value, respecting the legacy key name' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . withAnnotations ( { codingLegacyKey : [ { properties : { name : 'oldNameKey' } } , ] } ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const instanceMethods = ObjectSpecPlugin . instanceMethods ( objectType ) ; const expectedInstanceMethods = [ { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ 'if ((self = [super init])) {' , '  _name = (id)[aDecoder decodeObjectForKey:kNameKey];' , '  if (_name == nil) {' , '    _name = (id)[aDecoder decodeObjectForKey:@"oldNameKey"];' , '  }' , '}' , 'return self;' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'initWithCoder' , argument : { name : 'aDecoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ObjC . KeywordArgumentModifier . Nullable ( ) ] } } , { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ '[aCoder encodeObject:_name forKey:kNameKey];' ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'encodeWithCoder' , argument : { name : 'aCoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : null , modifiers : [ ] } } , ] ; expect ( instanceMethods ) . toEqualJSON ( expectedInstanceMethods ) ; } ) ; it ( 'returns two instance methods which will encode and decode three values when called' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . asObject ( ) , new ObjectSpecHelpers . AttributeBuilder ( 'doesUserLike' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'BOOL' , 'BOOL' , null ) ) . asObject ( ) , new ObjectSpecHelpers . AttributeBuilder ( 'someObject' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'id' , 'id' , null ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const instanceMethods = ObjectSpecPlugin . instanceMethods ( objectType ) ; const expectedInstanceMethods = [ { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ 'if ((self = [super init])) {' , '  _name = (id)[aDecoder decodeObjectForKey:kNameKey];' , '  _doesUserLike = [aDecoder decodeBoolForKey:kDoesUserLikeKey];' , '  _someObject = [aDecoder decodeObjectForKey:kSomeObjectKey];' , '}' , 'return self;' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'initWithCoder' , argument : { name : 'aDecoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ObjC . KeywordArgumentModifier . Nullable ( ) ] } } , { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ '[aCoder encodeObject:_name forKey:kNameKey];' , '[aCoder encodeBool:_doesUserLike forKey:kDoesUserLikeKey];' , '[aCoder encodeObject:_someObject forKey:kSomeObjectKey];' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'encodeWithCoder' , argument : { name : 'aCoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : null , modifiers : [ ] } } , ] ; expect ( instanceMethods ) . toEqualJSON ( expectedInstanceMethods ) ; } ) ; } ) ; describe ( '#imports' , function ( ) { it ( 'A correct import the found type' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'size' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'CGSize' , 'CGSize' , null ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const imports = ObjectSpecPlugin . imports ( objectType ) ; const expectedImports = [ { file : 'UIGeometry.h' , isPublic : false , library : 'UIKit' , requiresCPlusPlus : false } , ] ; expect ( imports ) . toEqualJSON ( expectedImports ) ; } ) ; } ) ; describe ( '#staticConstants' , function ( ) { it ( 'returns no constants when there are no attributes on the found type' , function ( ) { const objectType = { annotations : { } , attributes : [ ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const staticConstants = ObjectSpecPlugin . staticConstants ( objectType ) ; expect ( staticConstants ) . toEqualJSON ( [ ] ) ; } ) ; it ( 'returns a constant referencing to the key for coding when the found type ' + 'has a single attribute' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'name' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const staticConstants = ObjectSpecPlugin . staticConstants ( objectType ) ; const expectedStaticConstants = [ { type : { name : 'NSString' , reference : 'NSString *' } , comments : [ ] , name : 'kNameKey' , value : '@"NAME"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , ] ; expect ( staticConstants ) . toEqualJSON ( expectedStaticConstants ) ; } ) ; it ( 'returns a constant referencing to the key for coding when the found type ' + 'has a single attribute of a different name' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'givenName' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const staticConstants = ObjectSpecPlugin . staticConstants ( objectType ) ; const expectedStaticConstants = [ { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kGivenNameKey' , value : '@"GIVEN_NAME"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , ] ; expect ( staticConstants ) . toEqualJSON ( expectedStaticConstants ) ; } ) ; it ( 'returns two constants referencing to the key for coding when the found type ' + 'has two attributes' , function ( ) { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'givenName' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . asObject ( ) , new ObjectSpecHelpers . AttributeBuilder ( 'age' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSUInteger' , 'NSUInteger' , null ) ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const staticConstants = ObjectSpecPlugin . staticConstants ( objectType ) ; const expectedStaticConstants = [ { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kGivenNameKey' , value : '@"GIVEN_NAME"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kAgeKey' , value : '@"AGE"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , ] ; expect ( staticConstants ) . toEqualJSON ( expectedStaticConstants ) ; } ) ; it ( 'uses the codingKey attribute if provided' , ( ) => { const objectType = { annotations : { } , attributes : [ new ObjectSpecHelpers . AttributeBuilder ( 'test' , new ObjectSpecHelpers . AttributeTypeBuilder ( 'NSString' , 'NSString *' , 'NSObject' ) ) . withAnnotations ( { codingKey : [ { properties : { name : 'test_custom_key' } } , ] } ) . asObject ( ) , ] , comments : [ ] , typeLookups : [ ] , excludes : [ ] , includes : [ ] , libraryName : null , typeName : 'Foo' } ; const staticConstants = ObjectSpecPlugin . staticConstants ( objectType ) ; const expectedConstants = [ { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kTestKey' , value : '@"test_custom_key"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , ] ; expect ( staticConstants ) . toEqualJSON ( expectedConstants ) ; } ) ; } ) ; describe ( '#decodeStatementForAttribute' , function ( ) { it ( 'returns code which will decode a BOOL value when called' , function ( ) { const attribute = { name : 'doesUserLike' , valueAccessor : '_doesUserLike' , constantName : 'kDoesUserLikeKey' , constantValue : 'DOES_USER_LIKE' , legacyKeyNames : [ ] , type : { name : 'BOOL' , reference : 'BOOL' } , originalType : { name : 'BOOL' , reference : 'BOOL' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_doesUserLike = [aDecoder decodeBoolForKey:kDoesUserLikeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will decode an id value when called' , function ( ) { const attribute = { name : 'foo' , valueAccessor : '_foo' , constantName : 'kFooKey' , constantValue : 'FOO' , legacyKeyNames : [ ] , type : { name : 'id' , reference : 'id' } , originalType : { name : 'id' , reference : 'id' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_foo = [aDecoder decodeObjectForKey:kFooKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will decode an NSObject value when called' , function ( ) { const attribute = { name : 'name' , valueAccessor : '_name' , constantName : 'kNameKey' , constantValue : 'NAME' , legacyKeyNames : [ ] , type : { name : 'NSObject' , reference : 'NSObject *' } , originalType : { name : 'NSObject' , reference : 'NSObject *' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_name = (id)[aDecoder decodeObjectForKey:kNameKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will decode an NSInteger value when called' , function ( ) { const attribute = { name : 'age' , valueAccessor : '_age' , constantName : 'kAgeKey' , constantValue : 'AGE' , legacyKeyNames : [ ] , type : { name : 'NSInteger' , reference : 'NSInteger' } , originalType : { name : 'NSInteger' , reference : 'NSInteger' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_age = [aDecoder decodeIntegerForKey:kAgeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will decode an NSUInteger value when called' , function ( ) { const attribute = { name : 'age' , valueAccessor : '_age' , constantName : 'kAgeKey' , constantValue : 'AGE' , legacyKeyNames : [ ] , type : { name : 'NSUInteger' , reference : 'NSUInteger' } , originalType : { name : 'NSUInteger' , reference : 'NSUInteger' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_age = [aDecoder decodeIntegerForKey:kAgeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will decode a selector value when called' , function ( ) { const attribute = { name : 'callbackMethod' , valueAccessor : '_callbackMethod' , constantName : 'kCallbackMethodKey' , constantValue : 'CALLBACK_METHOD' , legacyKeyNames : [ ] , type : { name : 'SEL' , reference : 'SEL' } , originalType : { name : 'SEL' , reference : 'SEL' } } ; const code = Coding . decodeStatementForAttribute ( attribute , false ) ; const expectedCode = '_callbackMethod = NSSelectorFromString([aDecoder decodeObjectForKey:kCallbackMethodKey]);' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; } ) ; describe ( '#encodeStatementForAttribute' , function ( ) { it ( 'returns code which will encode a BOOL value when called' , function ( ) { const attribute = { name : 'doesUserLike' , valueAccessor : '_doesUserLike' , constantName : 'kDoesUserLikeKey' , constantValue : 'DOES_USER_LIKE' , legacyKeyNames : [ ] , type : { name : 'BOOL' , reference : 'BOOL' } , originalType : { name : 'BOOL' , reference : 'BOOL' } } ; const code = Coding . encodeStatementForAttribute ( attribute ) ; const expectedCode = '[aCoder encodeBool:_doesUserLike forKey:kDoesUserLikeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will encode an NSObject value when called' , function ( ) { const attribute = { name : 'name' , valueAccessor : '_name' , constantName : 'kNameKey' , constantValue : 'NAME' , legacyKeyNames : [ ] , type : { name : 'NSObject' , reference : 'NSObject *' } , originalType : { name : 'NSObject' , reference : 'NSObject *' } } ; const code = Coding . encodeStatementForAttribute ( attribute ) ; const expectedCode = '[aCoder encodeObject:_name forKey:kNameKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will encode an NSInteger value when called' , function ( ) { const attribute = { name : 'age' , valueAccessor : '_age' , constantName : 'kAgeKey' , constantValue : 'AGE' , legacyKeyNames : [ ] , type : { name : 'NSInteger' , reference : 'NSInteger' } , originalType : { name : 'NSInteger' , reference : 'NSInteger' } } ; const code = Coding . encodeStatementForAttribute ( attribute ) ; const expectedCode = '[aCoder encodeInteger:_age forKey:kAgeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will encode an NSUInteger value when called' , function ( ) { const attribute = { name : 'age' , valueAccessor : '_age' , constantName : 'kAgeKey' , constantValue : 'AGE' , legacyKeyNames : [ ] , type : { name : 'NSUInteger' , reference : 'NSUInteger' } , originalType : { name : 'NSUInteger' , reference : 'NSUInteger' } } ; const code = Coding . encodeStatementForAttribute ( attribute ) ; const expectedCode = '[aCoder encodeInteger:_age forKey:kAgeKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; it ( 'returns code which will encode a selector as a string value when called' , function ( ) { const attribute = { name : 'callbackMethod' , valueAccessor : '_callbackMethod' , constantName : 'kCallbackMethodKey' , constantValue : 'CALLBACK_METHOD' , legacyKeyNames : [ ] , type : { name : 'SEL' , reference : 'SEL' } , originalType : { name : 'SEL' , reference : 'SEL' } } ; const code = Coding . encodeStatementForAttribute ( attribute ) ; const expectedCode = '[aCoder encodeObject:NSStringFromSelector(_callbackMethod) forKey:kCallbackMethodKey];' ; expect ( code ) . toEqualJSON ( expectedCode ) ; } ) ; } ) ; } ) ; describe ( 'AlgebraicTypePlugins.Coding' , function ( ) { describe ( '#instanceMethods' , function ( ) { it ( 'returns two instance methods which will encode and decode two values when called' , function ( ) { const algebraicType = { annotations : { } , name : 'Test' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'SomeSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someString' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'NSString' , reference : 'NSString *' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : 'NSObject' , conformingProtocol : null , referencedGenericTypes : [ ] } } , { annotations : { } , name : 'someUnsignedInteger' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'NSUInteger' , reference : 'NSUInteger' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : null , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , AlgebraicType . Subtype . SingleAttributeSubtypeDefinition ( { annotations : { } , name : 'coolSingleAttributeSubtype' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'SingleAttributeType' , reference : 'SingleAttributeType *' , libraryTypeIsDefinedIn : 'SomeLib' , fileTypeIsDefinedIn : null , underlyingType : 'NSObject' , conformingProtocol : null , referencedGenericTypes : [ ] } } ) , ] } ; const instanceMethods = AlgebraicTypePlugin . instanceMethods ( algebraicType ) ; const expectedInstanceMethods = [ { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ 'if ((self = [super init])) {' , '  NSString *codedSubtype = [aDecoder decodeObjectForKey:kCodedSubtypeKey];' , '  if([codedSubtype isEqualToString:@"SUBTYPE_SOME_SUBTYPE"]) {' , '    _someSubtype_someString = (id)[aDecoder decodeObjectForKey:kSomeSubtypeSomeStringKey];' , '    _someSubtype_someUnsignedInteger = [aDecoder decodeIntegerForKey:kSomeSubtypeSomeUnsignedIntegerKey];' , '    _subtype = TestSubtypesSomeSubtype;' , '  }' , '  else if([codedSubtype isEqualToString:@"SUBTYPE_COOL_SINGLE_ATTRIBUTE_SUBTYPE"]) {' , '    _coolSingleAttributeSubtype = (id)[aDecoder decodeObjectForKey:kCoolSingleAttributeSubtypeKey];' , '    _subtype = TestSubtypesCoolSingleAttributeSubtype;' , '  }' , '  else {' , '    [aDecoder failWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSCoderReadCorruptError userInfo:@{NSDebugDescriptionErrorKey:[NSString stringWithFormat:@"*** [%@ %@]: Invalid subtype provided: %@", [self class], NSStringFromSelector(_cmd), codedSubtype]}]];' , '    return nil;' , '  }' , '}' , 'return self;' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'initWithCoder' , argument : { name : 'aDecoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ObjC . KeywordArgumentModifier . Nullable ( ) ] } } , { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : [ 'switch (_subtype) {' , '  case TestSubtypesSomeSubtype: {' , '    [aCoder encodeObject:_someSubtype_someString forKey:kSomeSubtypeSomeStringKey];' , '    [aCoder encodeInteger:_someSubtype_someUnsignedInteger forKey:kSomeSubtypeSomeUnsignedIntegerKey];' , '    [aCoder encodeObject:@"SUBTYPE_SOME_SUBTYPE" forKey:kCodedSubtypeKey];' , '    break;' , '  }' , '  case TestSubtypesCoolSingleAttributeSubtype: {' , '    [aCoder encodeObject:_coolSingleAttributeSubtype forKey:kCoolSingleAttributeSubtypeKey];' , '    [aCoder encodeObject:@"SUBTYPE_COOL_SINGLE_ATTRIBUTE_SUBTYPE" forKey:kCodedSubtypeKey];' , '    break;' , '  }' , '}' , ] , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'encodeWithCoder' , argument : { name : 'aCoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : null , modifiers : [ ] } } , ] ; expect ( instanceMethods ) . toEqualJSON ( expectedInstanceMethods ) ; } ) ; } ) ; describe ( '#staticConstants' , function ( ) { it ( 'returns four constants referencing to the key for coding when the algebraic type ' + 'has two subtypes with three cumulative attributes' , function ( ) { const algebraicType = { annotations : { } , name : 'Test' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'SomeSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someString' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'NSString' , reference : 'NSString *' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : 'NSObject' , conformingProtocol : null , referencedGenericTypes : [ ] } } , { annotations : { } , name : 'someUnsignedInteger' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'NSUInteger' , reference : 'NSUInteger' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : null , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , AlgebraicType . Subtype . SingleAttributeSubtypeDefinition ( { annotations : { } , name : 'coolSingleAttributeSubtype' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'SingleAttributeType' , reference : 'SingleAttributeType *' , libraryTypeIsDefinedIn : 'SomeLib' , fileTypeIsDefinedIn : null , underlyingType : 'NSObject' , conformingProtocol : null , referencedGenericTypes : [ ] } } ) , ] } ; const staticConstants = AlgebraicTypePlugin . staticConstants ( algebraicType ) ; const expectedStaticConstants = [ { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kCodedSubtypeKey' , value : '@"CODED_SUBTYPE"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kSomeSubtypeSomeStringKey' , value : '@"SOME_SUBTYPE_SOME_STRING"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kSomeSubtypeSomeUnsignedIntegerKey' , value : '@"SOME_SUBTYPE_SOME_UNSIGNED_INTEGER"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , { comments : [ ] , type : { name : 'NSString' , reference : 'NSString *' } , name : 'kCoolSingleAttributeSubtypeKey' , value : '@"COOL_SINGLE_ATTRIBUTE_SUBTYPE"' , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } , ] ; expect ( staticConstants ) . toEqualJSON ( expectedStaticConstants ) ; } ) ; } ) ; describe ( '#validationErrors' , function ( ) { it ( 'returns no validation errors when there are no attributes on the provided subtypes' , function ( ) { const algebraicType = { annotations : { } , name : 'Foo' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'ASubType' , comments : [ ] , attributes : [ ] , annotations : { } } ) , AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'AnotherSubtype' , comments : [ ] , attributes : [ ] , annotations : { } } ) , ] } ; const errors = AlgebraicTypePlugin . validationErrors ( algebraicType ) ; expect ( errors ) . toEqualJSON ( [ ] ) ; } ) ; it ( 'returns a validation error when there is an attribute with an unknown type' , function ( ) { const algebraicType = { annotations : { } , name : 'Test' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'SomeSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someFoo' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'Foo' , reference : 'Foo *' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : null , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , ] } ; const errors = AlgebraicTypePlugin . validationErrors ( algebraicType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the type "Foo" from Test.someFoo. Did you forget to declare a backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; it ( 'returns two validation errors when there are two attributes with unknown types' , function ( ) { const algebraicType = { annotations : { } , name : 'Test' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'SomeSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someFoo' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'Foo' , reference : 'Foo *' , libraryTypeIsDefinedIn : null , fileTypeIsDefinedIn : null , underlyingType : null , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'AnotherSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someFerr' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'Ferr' , reference : 'Ferr' , libraryTypeIsDefinedIn : 'SomeLib' , fileTypeIsDefinedIn : 'SomethingElse' , underlyingType : null , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , ] } ; const errors = AlgebraicTypePlugin . validationErrors ( algebraicType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the type "Foo" from Test.someFoo. Did you forget to declare a backing type?' ) , Error . Error ( 'The Coding plugin does not know how to decode and encode the type "Ferr" from Test.someFerr. Did you forget to declare a backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; it ( 'returns a validation error when there is an attribute with an unknown underlying type' , function ( ) { const algebraicType = { annotations : { } , name : 'Test' , includes : [ ] , typeLookups : [ ] , excludes : [ ] , libraryName : null , comments : [ ] , subtypes : [ AlgebraicType . Subtype . NamedAttributeCollectionDefinition ( { name : 'AnotherSubtype' , comments : [ ] , attributes : [ { annotations : { } , name : 'someFerr' , comments : [ ] , nullability : CLangCommon . Nullability . Inherited ( ) , type : { name : 'Ferr' , reference : 'Ferr' , libraryTypeIsDefinedIn : 'SomeLib' , fileTypeIsDefinedIn : 'SomethingElse' , underlyingType : 'SomethingRandom' , conformingProtocol : null , referencedGenericTypes : [ ] } } , ] , annotations : { } } ) , ] } ; const errors = AlgebraicTypePlugin . validationErrors ( algebraicType ) ; const expectedErrors = [ Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "SomethingRandom" from Test.someFerr. Did you declare the wrong backing type?' ) , ] ; expect ( errors ) . toEqualJSON ( expectedErrors ) ; } ) ; } ) ; } ) ;