@@ -7039,6 +7039,330 @@ export class TestService {
70397039 insta:: assert_snapshot!( "jit_complex_decorator_arguments" , result. code) ;
70407040}
70417041
7042+ // =========================================================================
7043+ // Reference output comparison tests
7044+ // =========================================================================
7045+ // These tests compare our output against the actual output from Angular's
7046+ // official JIT compiler (@angular/compiler-cli) + TypeScript emit pipeline.
7047+ // Reference outputs were generated by compiling TypeScript files with
7048+ // Angular's downlevel_decorators_transform followed by tsc emit.
7049+
7050+ #[ test]
7051+ fn test_jit_reference_ngxs_animals_state ( ) {
7052+ // Reference: AnimalsState from Angular's actual JIT output
7053+ // Non-Angular @State class decorator + @Injectable, with @Selector (static) and @Action (instance)
7054+ let allocator = Allocator :: default ( ) ;
7055+ let source = r#"
7056+ import { Injectable } from '@angular/core';
7057+ import { State, Action, Selector } from '@ngxs/store';
7058+
7059+ @State({
7060+ name: 'animals',
7061+ defaults: []
7062+ })
7063+ @Injectable()
7064+ class AnimalsState {
7065+ @Selector()
7066+ static getAnimals(state: string[]): string[] {
7067+ return state;
7068+ }
7069+
7070+ @Action({ type: 'AddAnimal' })
7071+ addAnimal(ctx: any, action: any): void {}
7072+ }
7073+ "# ;
7074+
7075+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
7076+ let result = transform_angular_file ( & allocator, "animals.state.ts" , source, & options, None ) ;
7077+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
7078+
7079+ // Angular reference output (from full-compiled-output.js):
7080+ // __decorate([Action({type:'AddAnimal'})], AnimalsState.prototype, "addAnimal", null);
7081+ // __decorate([Selector()], AnimalsState, "getAnimals", null);
7082+ // AnimalsState = __decorate([State({...}), Injectable()], AnimalsState);
7083+
7084+ // Instance method → prototype, null
7085+ assert ! (
7086+ result. code. contains( "__decorate([Action({ type: 'AddAnimal' })], AnimalsState.prototype, \" addAnimal\" , null)" ) ,
7087+ "Instance method should match Angular reference output. Got:\n {}" ,
7088+ result. code
7089+ ) ;
7090+
7091+ // Static method → class directly, null
7092+ assert ! (
7093+ result. code. contains( "__decorate([Selector()], AnimalsState, \" getAnimals\" , null)" ) ,
7094+ "Static method should match Angular reference output. Got:\n {}" ,
7095+ result. code
7096+ ) ;
7097+
7098+ // Instance __decorate calls should come before static ones (TypeScript ordering)
7099+ let instance_pos = result. code . find ( "AnimalsState.prototype" ) . unwrap ( ) ;
7100+ let static_pos = result. code . find ( "AnimalsState, \" getAnimals\" " ) . unwrap ( ) ;
7101+ assert ! (
7102+ instance_pos < static_pos,
7103+ "Instance member __decorate should come before static. Got:\n {}" ,
7104+ result. code
7105+ ) ;
7106+
7107+ // Class __decorate should include both State and Injectable in source order
7108+ let class_decorate = result. code . find ( "AnimalsState = __decorate(" ) . unwrap ( ) ;
7109+ let class_section = & result. code [ class_decorate..] ;
7110+ assert ! (
7111+ class_section. contains( "State(" ) && class_section. contains( "Injectable()" ) ,
7112+ "Class __decorate should include both decorators. Got:\n {}" ,
7113+ result. code
7114+ ) ;
7115+
7116+ // No raw decorators
7117+ assert ! (
7118+ !result. code. contains( "@State" ) && !result. code. contains( "@Injectable" )
7119+ && !result. code. contains( "@Selector" ) && !result. code. contains( "@Action" ) ,
7120+ "No raw decorator syntax should remain. Got:\n {}" ,
7121+ result. code
7122+ ) ;
7123+
7124+ insta:: assert_snapshot!( "jit_reference_animals_state" , result. code) ;
7125+ }
7126+
7127+ #[ test]
7128+ fn test_jit_reference_ordering ( ) {
7129+ // Reference: OrderTestState from Angular's actual JIT output
7130+ // Tests that instance members are emitted before static members,
7131+ // each group in source order. This matches TypeScript's emit behavior.
7132+ let allocator = Allocator :: default ( ) ;
7133+ let source = r#"
7134+ import { Injectable } from '@angular/core';
7135+ import { State, Action, Selector } from '@ngxs/store';
7136+
7137+ @State({ name: 'order', defaults: {} })
7138+ @Injectable()
7139+ class OrderTestState {
7140+ @Action({ type: 'First' })
7141+ instanceFirst(ctx: any): void {}
7142+
7143+ @Selector()
7144+ static staticSecond(state: any): any { return state; }
7145+
7146+ @Action({ type: 'Third' })
7147+ instanceThird(ctx: any): void {}
7148+
7149+ @Selector()
7150+ static staticFourth(state: any): any { return state; }
7151+ }
7152+ "# ;
7153+
7154+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
7155+ let result = transform_angular_file ( & allocator, "order.state.ts" , source, & options, None ) ;
7156+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
7157+
7158+ // Angular reference output ordering (from decorate-patterns-output.js):
7159+ // __decorate([Action({type:'First'})], OrderTestState.prototype, "instanceFirst", null);
7160+ // __decorate([Action({type:'Third'})], OrderTestState.prototype, "instanceThird", null);
7161+ // __decorate([Selector()], OrderTestState, "staticSecond", null);
7162+ // __decorate([Selector()], OrderTestState, "staticFourth", null);
7163+ // OrderTestState = __decorate([State({...}), Injectable()], OrderTestState);
7164+
7165+ let first_pos = result. code . find ( "\" instanceFirst\" " ) . unwrap ( ) ;
7166+ let third_pos = result. code . find ( "\" instanceThird\" " ) . unwrap ( ) ;
7167+ let second_pos = result. code . find ( "\" staticSecond\" " ) . unwrap ( ) ;
7168+ let fourth_pos = result. code . find ( "\" staticFourth\" " ) . unwrap ( ) ;
7169+ let class_pos = result. code . find ( "OrderTestState = __decorate(" ) . unwrap ( ) ;
7170+
7171+ // Instance members first (in source order)
7172+ assert ! ( first_pos < third_pos, "instanceFirst before instanceThird" ) ;
7173+ // Then static members (in source order)
7174+ assert ! ( third_pos < second_pos, "instance group before static group" ) ;
7175+ assert ! ( second_pos < fourth_pos, "staticSecond before staticFourth" ) ;
7176+ // Class decorator last
7177+ assert ! ( fourth_pos < class_pos, "member decorators before class decorator" ) ;
7178+
7179+ insta:: assert_snapshot!( "jit_reference_ordering" , result. code) ;
7180+ }
7181+
7182+ #[ test]
7183+ fn test_jit_reference_decorate_patterns ( ) {
7184+ // Reference: TestDecoratePatternsService from Angular's actual JIT output
7185+ // Tests property/method/static/getter/setter decorator patterns
7186+ let allocator = Allocator :: default ( ) ;
7187+ let source = r#"
7188+ import { Injectable } from '@angular/core';
7189+
7190+ function CustomPropDecorator(): any { return () => {}; }
7191+ function CustomMethodDecorator(): any { return () => {}; }
7192+
7193+ @Injectable()
7194+ class TestDecoratePatternsService {
7195+ @CustomPropDecorator()
7196+ myProp: string = 'hello';
7197+
7198+ @CustomMethodDecorator()
7199+ myMethod(): void {}
7200+
7201+ @CustomMethodDecorator()
7202+ static myStaticMethod(): void {}
7203+
7204+ @CustomPropDecorator()
7205+ get myGetter(): string { return ''; }
7206+
7207+ @CustomPropDecorator()
7208+ set mySetter(val: string) {}
7209+ }
7210+ "# ;
7211+
7212+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
7213+ let result = transform_angular_file ( & allocator, "patterns.service.ts" , source, & options, None ) ;
7214+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
7215+
7216+ // Angular reference output (from decorate-patterns-output.js):
7217+ // __decorate([CustomPropDecorator()], X.prototype, "myProp", void 0);
7218+ // __decorate([CustomMethodDecorator()], X.prototype, "myMethod", null);
7219+ // __decorate([CustomPropDecorator()], X.prototype, "myGetter", null);
7220+ // __decorate([CustomPropDecorator()], X.prototype, "mySetter", null);
7221+ // __decorate([CustomMethodDecorator()], X, "myStaticMethod", null);
7222+
7223+ // Property → void 0
7224+ assert ! (
7225+ result. code. contains( "__decorate([CustomPropDecorator()], TestDecoratePatternsService.prototype, \" myProp\" , void 0)" ) ,
7226+ "Property decorator should use void 0 (Angular reference). Got:\n {}" ,
7227+ result. code
7228+ ) ;
7229+
7230+ // Method → null
7231+ assert ! (
7232+ result. code. contains( "__decorate([CustomMethodDecorator()], TestDecoratePatternsService.prototype, \" myMethod\" , null)" ) ,
7233+ "Method decorator should use null (Angular reference). Got:\n {}" ,
7234+ result. code
7235+ ) ;
7236+
7237+ // Static method → class, null
7238+ assert ! (
7239+ result. code. contains( "__decorate([CustomMethodDecorator()], TestDecoratePatternsService, \" myStaticMethod\" , null)" ) ,
7240+ "Static method should use class directly (Angular reference). Got:\n {}" ,
7241+ result. code
7242+ ) ;
7243+
7244+ // Getter → null (accessor, not property)
7245+ assert ! (
7246+ result. code. contains( "__decorate([CustomPropDecorator()], TestDecoratePatternsService.prototype, \" myGetter\" , null)" ) ,
7247+ "Getter should use null (Angular reference). Got:\n {}" ,
7248+ result. code
7249+ ) ;
7250+
7251+ // Setter → null (accessor, not property)
7252+ assert ! (
7253+ result. code. contains( "__decorate([CustomPropDecorator()], TestDecoratePatternsService.prototype, \" mySetter\" , null)" ) ,
7254+ "Setter should use null (Angular reference). Got:\n {}" ,
7255+ result. code
7256+ ) ;
7257+
7258+ // Ordering: instance members first (myProp, myMethod, myGetter, mySetter), then static
7259+ let prop_pos = result. code . find ( "\" myProp\" " ) . unwrap ( ) ;
7260+ let method_pos = result. code . find ( "\" myMethod\" " ) . unwrap ( ) ;
7261+ let getter_pos = result. code . find ( "\" myGetter\" " ) . unwrap ( ) ;
7262+ let setter_pos = result. code . find ( "\" mySetter\" " ) . unwrap ( ) ;
7263+ let static_pos = result. code . find ( "\" myStaticMethod\" " ) . unwrap ( ) ;
7264+
7265+ assert ! ( prop_pos < static_pos, "instance before static" ) ;
7266+ assert ! ( method_pos < static_pos, "instance before static" ) ;
7267+ assert ! ( getter_pos < static_pos, "instance before static" ) ;
7268+ assert ! ( setter_pos < static_pos, "instance before static" ) ;
7269+
7270+ insta:: assert_snapshot!( "jit_reference_decorate_patterns" , result. code) ;
7271+ }
7272+
7273+ #[ test]
7274+ fn test_jit_reference_angular_member_decorators ( ) {
7275+ // Reference: MyService from Angular's actual JIT output
7276+ // Angular member decorators go into propDecorators, constructor params into ctorParameters
7277+ let allocator = Allocator :: default ( ) ;
7278+ let source = r#"
7279+ import { Injectable, Inject, Optional, Input, Output, ViewChild, HostListener, HostBinding, ContentChild } from '@angular/core';
7280+
7281+ @Injectable()
7282+ class MyService {
7283+ @Input()
7284+ myInput: string = '';
7285+
7286+ @Output()
7287+ myOutput: any;
7288+
7289+ @ViewChild('ref')
7290+ myViewChild: any;
7291+
7292+ @HostBinding('class.active')
7293+ isActive: boolean = false;
7294+
7295+ @HostListener('click', ['$event'])
7296+ onClick(event: Event): void {}
7297+
7298+ @ContentChild('content')
7299+ myContent: any;
7300+
7301+ constructor(
7302+ @Inject('TOKEN') private token: string,
7303+ @Optional() private optService: any,
7304+ ) {}
7305+
7306+ normalMethod(): void {}
7307+ }
7308+ "# ;
7309+
7310+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
7311+ let result = transform_angular_file ( & allocator, "my.service.ts" , source, & options, None ) ;
7312+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
7313+
7314+ // Angular reference: propDecorators should contain all Angular member decorators
7315+ // From full-compiled-output.js:
7316+ // static propDecorators = {
7317+ // myInput: [{ type: Input }],
7318+ // myOutput: [{ type: Output }],
7319+ // myViewChild: [{ type: ViewChild, args: ['ref',] }],
7320+ // isActive: [{ type: HostBinding, args: ['class.active',] }],
7321+ // onClick: [{ type: HostListener, args: ['click', ['$event'],] }],
7322+ // myContent: [{ type: ContentChild, args: ['content',] }]
7323+ // };
7324+
7325+ assert ! ( result. code. contains( "propDecorators" ) , "Should have propDecorators. Got:\n {}" , result. code) ;
7326+ assert ! ( result. code. contains( "type: Input" ) , "propDecorators: Input. Got:\n {}" , result. code) ;
7327+ assert ! ( result. code. contains( "type: Output" ) , "propDecorators: Output. Got:\n {}" , result. code) ;
7328+ assert ! ( result. code. contains( "type: ViewChild, args: ['ref']" ) , "propDecorators: ViewChild. Got:\n {}" , result. code) ;
7329+ assert ! ( result. code. contains( "type: HostBinding, args: ['class.active']" ) , "propDecorators: HostBinding. Got:\n {}" , result. code) ;
7330+ assert ! ( result. code. contains( "type: HostListener, args: ['click', ['$event']]" ) , "propDecorators: HostListener. Got:\n {}" , result. code) ;
7331+ assert ! ( result. code. contains( "type: ContentChild, args: ['content']" ) , "propDecorators: ContentChild. Got:\n {}" , result. code) ;
7332+
7333+ // Angular reference: ctorParameters should contain constructor param types and decorators
7334+ // From full-compiled-output.js:
7335+ // static ctorParameters = () => [
7336+ // { type: String, decorators: [{ type: Inject, args: ['TOKEN',] }] },
7337+ // { type: undefined, decorators: [{ type: Optional }] }
7338+ // ];
7339+ assert ! ( result. code. contains( "ctorParameters" ) , "Should have ctorParameters. Got:\n {}" , result. code) ;
7340+ assert ! ( result. code. contains( "type: Inject, args: ['TOKEN']" ) , "ctorParameters: Inject. Got:\n {}" , result. code) ;
7341+ assert ! ( result. code. contains( "type: Optional" ) , "ctorParameters: Optional. Got:\n {}" , result. code) ;
7342+
7343+ // No raw Angular decorators should remain
7344+ assert ! (
7345+ !result. code. contains( "@Input" ) && !result. code. contains( "@Output" )
7346+ && !result. code. contains( "@ViewChild" ) && !result. code. contains( "@HostBinding" )
7347+ && !result. code. contains( "@HostListener" ) && !result. code. contains( "@ContentChild" )
7348+ && !result. code. contains( "@Inject" ) && !result. code. contains( "@Optional" ) ,
7349+ "No raw Angular decorator syntax should remain. Got:\n {}" ,
7350+ result. code
7351+ ) ;
7352+
7353+ // No __decorate calls for Angular member decorators (they go in propDecorators instead)
7354+ // Only the class __decorate([Injectable()], ...) should exist
7355+ let decorate_count = result. code . matches ( "__decorate(" ) . count ( ) ;
7356+ assert ! (
7357+ decorate_count == 1 ,
7358+ "Should have exactly 1 __decorate call (class only, not members). Got {} calls:\n {}" ,
7359+ decorate_count,
7360+ result. code
7361+ ) ;
7362+
7363+ insta:: assert_snapshot!( "jit_reference_angular_member_decorators" , result. code) ;
7364+ }
7365+
70427366// =========================================================================
70437367// Source map tests
70447368// =========================================================================
0 commit comments