@@ -5857,3 +5857,391 @@ fn test_host_binding_pure_function_declarations_emitted() {
58575857 }
58585858 }
58595859}
5860+
5861+ // ============================================================================
5862+ // Standalone Emission Tests (Issue #95)
5863+ // ============================================================================
5864+
5865+ /// Test that a standalone component does NOT emit `standalone:true` in ɵɵdefineComponent.
5866+ ///
5867+ /// Angular's TS compiler (compiler.ts:96-98) only emits `standalone: false` when
5868+ /// isStandalone === false. When standalone is true, it's omitted because the Angular
5869+ /// runtime (definition.ts:637) defaults `standalone` to `true` via `?? true`.
5870+ ///
5871+ /// OXC matches this behavior exactly.
5872+ #[ test]
5873+ fn test_standalone_component_omits_standalone_field ( ) {
5874+ let allocator = Allocator :: default ( ) ;
5875+ let source = r#"
5876+ import { Component } from '@angular/core';
5877+
5878+ @Component({
5879+ selector: 'app-test',
5880+ standalone: true,
5881+ template: '<div>test</div>'
5882+ })
5883+ export class TestComponent {}
5884+ "# ;
5885+
5886+ let options = ComponentTransformOptions :: default ( ) ;
5887+ let result = transform_angular_file ( & allocator, "test.component.ts" , source, & options, None ) ;
5888+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
5889+
5890+ let normalized = result. code . replace ( [ ' ' , '\n' , '\t' ] , "" ) ;
5891+ // Angular TS compiler omits standalone when true (runtime defaults to true via ?? true)
5892+ assert ! (
5893+ !normalized. contains( "standalone:true" ) ,
5894+ "Standalone component should NOT emit `standalone:true` (runtime defaults to true). Output:\n {}" ,
5895+ result. code
5896+ ) ;
5897+ }
5898+
5899+ /// Test that a non-standalone component emits `standalone:false` in ɵɵdefineComponent.
5900+ #[ test]
5901+ fn test_non_standalone_component_emits_standalone_false ( ) {
5902+ let allocator = Allocator :: default ( ) ;
5903+ let source = r#"
5904+ import { Component } from '@angular/core';
5905+
5906+ @Component({
5907+ selector: 'app-legacy',
5908+ standalone: false,
5909+ template: '<div>legacy</div>'
5910+ })
5911+ export class LegacyComponent {}
5912+ "# ;
5913+
5914+ let options = ComponentTransformOptions :: default ( ) ;
5915+ let result = transform_angular_file ( & allocator, "test.component.ts" , source, & options, None ) ;
5916+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
5917+
5918+ let normalized = result. code . replace ( [ ' ' , '\n' , '\t' ] , "" ) ;
5919+ assert ! (
5920+ normalized. contains( "standalone:false" ) ,
5921+ "Non-standalone component MUST emit `standalone:false` in ɵɵdefineComponent. Output:\n {}" ,
5922+ result. code
5923+ ) ;
5924+ }
5925+
5926+ /// Test that an implicit standalone component (Angular 19+ default) omits `standalone` field.
5927+ ///
5928+ /// Angular 19+ defaults `standalone` to `true`. The Angular TS compiler omits the field
5929+ /// when true, and the runtime defaults it via `?? true`. OXC matches this behavior.
5930+ #[ test]
5931+ fn test_implicit_standalone_with_imports_omits_standalone_field ( ) {
5932+ let allocator = Allocator :: default ( ) ;
5933+ let source = r#"
5934+ import { Component } from '@angular/core';
5935+ import { NgIf } from '@angular/common';
5936+
5937+ @Component({
5938+ selector: 'app-implicit',
5939+ imports: [NgIf],
5940+ template: '<div *ngIf="true">implicit standalone</div>'
5941+ })
5942+ export class ImplicitStandaloneComponent {}
5943+ "# ;
5944+
5945+ let options = ComponentTransformOptions :: default ( ) ;
5946+ let result = transform_angular_file ( & allocator, "test.component.ts" , source, & options, None ) ;
5947+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
5948+
5949+ let normalized = result. code . replace ( [ ' ' , '\n' , '\t' ] , "" ) ;
5950+ // Angular TS compiler omits standalone when true (runtime defaults to true via ?? true)
5951+ assert ! (
5952+ !normalized. contains( "standalone:true" ) ,
5953+ "Implicit standalone component should NOT emit `standalone:true` (runtime defaults to true). Output:\n {}" ,
5954+ result. code
5955+ ) ;
5956+ }
5957+
5958+ // ============================================================================
5959+ // JIT Compilation Tests
5960+ // ============================================================================
5961+
5962+ #[ test]
5963+ fn test_jit_component_with_inline_template ( ) {
5964+ // When jit: true, the compiler should NOT compile templates.
5965+ // Instead, it should keep the decorator and downlevel it using __decorate.
5966+ let allocator = Allocator :: default ( ) ;
5967+ let source = r#"
5968+ import { Component } from '@angular/core';
5969+
5970+ @Component({
5971+ selector: 'app-root',
5972+ template: '<h1>Hello</h1>',
5973+ standalone: true,
5974+ })
5975+ export class AppComponent {}
5976+ "# ;
5977+
5978+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
5979+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
5980+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
5981+
5982+ // Should have __decorate import from tslib
5983+ assert ! (
5984+ result. code. contains( "import { __decorate } from \" tslib\" " ) ,
5985+ "JIT output should import __decorate from tslib. Got:\n {}" ,
5986+ result. code
5987+ ) ;
5988+
5989+ // Should NOT have ɵcmp or ɵfac (AOT-style definitions)
5990+ assert ! (
5991+ !result. code. contains( "ɵcmp" ) && !result. code. contains( "ɵfac" ) ,
5992+ "JIT output should NOT contain AOT definitions (ɵcmp/ɵfac). Got:\n {}" ,
5993+ result. code
5994+ ) ;
5995+
5996+ // Should have __decorate call with Component
5997+ assert ! (
5998+ result. code. contains( "__decorate(" ) ,
5999+ "JIT output should use __decorate. Got:\n {}" ,
6000+ result. code
6001+ ) ;
6002+
6003+ // Should keep the template property as-is (inline template)
6004+ assert ! (
6005+ result. code. contains( "template:" ) ,
6006+ "JIT output should preserve inline template. Got:\n {}" ,
6007+ result. code
6008+ ) ;
6009+
6010+ insta:: assert_snapshot!( "jit_inline_template" , result. code) ;
6011+ }
6012+
6013+ #[ test]
6014+ fn test_jit_component_with_template_url ( ) {
6015+ // When jit: true and templateUrl is used, it should be replaced with
6016+ // an import from angular:jit:template:file;./path
6017+ let allocator = Allocator :: default ( ) ;
6018+ let source = r#"
6019+ import { Component } from '@angular/core';
6020+
6021+ @Component({
6022+ selector: 'app-root',
6023+ templateUrl: './app.html',
6024+ standalone: true,
6025+ })
6026+ export class AppComponent {}
6027+ "# ;
6028+
6029+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6030+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
6031+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6032+
6033+ // Should have resource import for template
6034+ assert ! (
6035+ result. code. contains( "angular:jit:template:file;./app.html" ) ,
6036+ "JIT output should import template via angular:jit:template:file. Got:\n {}" ,
6037+ result. code
6038+ ) ;
6039+
6040+ // Should replace templateUrl with template referencing the import
6041+ assert ! (
6042+ !result. code. contains( "templateUrl" ) ,
6043+ "JIT output should replace templateUrl with template. Got:\n {}" ,
6044+ result. code
6045+ ) ;
6046+
6047+ insta:: assert_snapshot!( "jit_template_url" , result. code) ;
6048+ }
6049+
6050+ #[ test]
6051+ fn test_jit_component_with_style_url ( ) {
6052+ // When jit: true and styleUrl/styleUrls is used, it should be replaced with
6053+ // imports from angular:jit:style:file;./path
6054+ let allocator = Allocator :: default ( ) ;
6055+ let source = r#"
6056+ import { Component } from '@angular/core';
6057+
6058+ @Component({
6059+ selector: 'app-root',
6060+ template: '<h1>Hello</h1>',
6061+ styleUrl: './app.css',
6062+ })
6063+ export class AppComponent {}
6064+ "# ;
6065+
6066+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6067+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
6068+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6069+
6070+ // Should have resource import for style
6071+ assert ! (
6072+ result. code. contains( "angular:jit:style:file;./app.css" ) ,
6073+ "JIT output should import style via angular:jit:style:file. Got:\n {}" ,
6074+ result. code
6075+ ) ;
6076+
6077+ insta:: assert_snapshot!( "jit_style_url" , result. code) ;
6078+ }
6079+
6080+ #[ test]
6081+ fn test_jit_component_with_constructor_deps ( ) {
6082+ // JIT compilation should generate ctorParameters for constructor dependencies
6083+ let allocator = Allocator :: default ( ) ;
6084+ let source = r#"
6085+ import { Component } from '@angular/core';
6086+ import { TitleService } from './title.service';
6087+
6088+ @Component({
6089+ selector: 'app-root',
6090+ template: '<h1>Hello</h1>',
6091+ })
6092+ export class AppComponent {
6093+ constructor(private titleService: TitleService) {}
6094+ }
6095+ "# ;
6096+
6097+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6098+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
6099+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6100+
6101+ // Should have ctorParameters static property
6102+ assert ! (
6103+ result. code. contains( "ctorParameters" ) ,
6104+ "JIT output should contain ctorParameters. Got:\n {}" ,
6105+ result. code
6106+ ) ;
6107+
6108+ // Should reference TitleService type
6109+ assert ! (
6110+ result. code. contains( "TitleService" ) ,
6111+ "JIT ctorParameters should reference dependency type. Got:\n {}" ,
6112+ result. code
6113+ ) ;
6114+
6115+ insta:: assert_snapshot!( "jit_constructor_deps" , result. code) ;
6116+ }
6117+
6118+ #[ test]
6119+ fn test_jit_component_class_restructuring ( ) {
6120+ // JIT should restructure: export class X {} → let X = class X {}; X = __decorate([...], X); export { X };
6121+ let allocator = Allocator :: default ( ) ;
6122+ let source = r#"
6123+ import { Component } from '@angular/core';
6124+
6125+ @Component({
6126+ selector: 'app-root',
6127+ template: '<h1>Hello</h1>',
6128+ })
6129+ export class AppComponent {
6130+ title = 'app';
6131+ }
6132+ "# ;
6133+
6134+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6135+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
6136+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6137+
6138+ // Should have let declaration
6139+ assert ! (
6140+ result. code. contains( "let AppComponent = class AppComponent" ) ,
6141+ "JIT output should use 'let X = class X' pattern. Got:\n {}" ,
6142+ result. code
6143+ ) ;
6144+
6145+ // Should have export statement
6146+ assert ! (
6147+ result. code. contains( "export { AppComponent }" ) ,
6148+ "JIT output should have named export. Got:\n {}" ,
6149+ result. code
6150+ ) ;
6151+
6152+ insta:: assert_snapshot!( "jit_class_restructuring" , result. code) ;
6153+ }
6154+
6155+ #[ test]
6156+ fn test_jit_directive ( ) {
6157+ // @Directive should also be JIT-transformed with __decorate
6158+ let allocator = Allocator :: default ( ) ;
6159+ let source = r#"
6160+ import { Directive, Input } from '@angular/core';
6161+
6162+ @Directive({
6163+ selector: '[appHighlight]',
6164+ standalone: true,
6165+ })
6166+ export class HighlightDirective {
6167+ @Input() color: string = 'yellow';
6168+ }
6169+ "# ;
6170+
6171+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6172+ let result =
6173+ transform_angular_file ( & allocator, "highlight.directive.ts" , source, & options, None ) ;
6174+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6175+
6176+ // Should have __decorate with Directive
6177+ assert ! (
6178+ result. code. contains( "__decorate(" ) ,
6179+ "JIT directive output should use __decorate. Got:\n {}" ,
6180+ result. code
6181+ ) ;
6182+
6183+ // Should NOT have ɵdir or ɵfac
6184+ assert ! (
6185+ !result. code. contains( "ɵdir" ) && !result. code. contains( "ɵfac" ) ,
6186+ "JIT directive output should NOT contain AOT definitions. Got:\n {}" ,
6187+ result. code
6188+ ) ;
6189+
6190+ insta:: assert_snapshot!( "jit_directive" , result. code) ;
6191+ }
6192+
6193+ #[ test]
6194+ fn test_jit_full_component_example ( ) {
6195+ // Full example matching the issue #97 scenario
6196+ let allocator = Allocator :: default ( ) ;
6197+ let source = r#"
6198+ import { Component, signal } from '@angular/core';
6199+ import { RouterOutlet } from '@angular/router';
6200+ import { Lib1 } from 'lib1';
6201+ import { TitleService } from './title.service';
6202+
6203+ @Component({
6204+ selector: 'app-root',
6205+ imports: [RouterOutlet, Lib1],
6206+ templateUrl: './app.html',
6207+ styleUrl: './app.css',
6208+ })
6209+ export class App {
6210+ titleService;
6211+ title = signal('app');
6212+ constructor(titleService: TitleService) {
6213+ this.titleService = titleService;
6214+ this.title.set(this.titleService.getTitle());
6215+ }
6216+ }
6217+ "# ;
6218+
6219+ let options = ComponentTransformOptions { jit : true , ..Default :: default ( ) } ;
6220+ let result = transform_angular_file ( & allocator, "app.component.ts" , source, & options, None ) ;
6221+ assert ! ( !result. has_errors( ) , "Should not have errors: {:?}" , result. diagnostics) ;
6222+
6223+ // Should have all JIT characteristics
6224+ assert ! (
6225+ result. code. contains( "import { __decorate } from \" tslib\" " ) ,
6226+ "Missing __decorate import"
6227+ ) ;
6228+ assert ! (
6229+ result. code. contains( "angular:jit:template:file;./app.html" ) ,
6230+ "Missing template resource import"
6231+ ) ;
6232+ assert ! (
6233+ result. code. contains( "angular:jit:style:file;./app.css" ) ,
6234+ "Missing style resource import"
6235+ ) ;
6236+ assert ! ( result. code. contains( "let App = class App" ) , "Missing class restructuring" ) ;
6237+ assert ! ( result. code. contains( "ctorParameters" ) , "Missing ctorParameters" ) ;
6238+ assert ! ( result. code. contains( "__decorate(" ) , "Missing __decorate call" ) ;
6239+ assert ! ( result. code. contains( "export { App }" ) , "Missing named export" ) ;
6240+
6241+ // Should NOT have AOT output
6242+ assert ! ( !result. code. contains( "ɵcmp" ) , "Should not contain ɵcmp" ) ;
6243+ assert ! ( !result. code. contains( "ɵfac" ) , "Should not contain ɵfac" ) ;
6244+ assert ! ( !result. code. contains( "defineComponent" ) , "Should not contain defineComponent" ) ;
6245+
6246+ insta:: assert_snapshot!( "jit_full_component" , result. code) ;
6247+ }
0 commit comments