To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
		
			
				
	
	
		
			261 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
 | |
| import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
 | |
| 
 | |
| import {NgtscTestEnvironment} from './env';
 | |
| 
 | |
| const testFiles = loadStandardTestFiles();
 | |
| 
 | |
| runInEachFileSystem(() => {
 | |
|   describe('ngtsc incremental compilation', () => {
 | |
|     let env !: NgtscTestEnvironment;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       env = NgtscTestEnvironment.setup(testFiles);
 | |
|       env.enableMultipleCompilations();
 | |
|       env.tsconfig();
 | |
|     });
 | |
| 
 | |
|     it('should skip unchanged services', () => {
 | |
|       env.write('service.ts', `
 | |
|       import {Injectable} from '@angular/core';
 | |
| 
 | |
|       @Injectable()
 | |
|       export class Service {}
 | |
|     `);
 | |
|       env.write('test.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
|       import {Service} from './service';
 | |
| 
 | |
|       @Component({selector: 'cmp', template: 'cmp'})
 | |
|       export class Cmp {
 | |
|         constructor(service: Service) {}
 | |
|       }
 | |
|     `);
 | |
|       env.driveMain();
 | |
|       env.flushWrittenFileTracking();
 | |
| 
 | |
|       // Pretend a change was made to test.ts.
 | |
|       env.invalidateCachedFile('test.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
| 
 | |
|       // The changed file should be recompiled, but not the service.
 | |
|       expect(written).toContain('/test.js');
 | |
|       expect(written).not.toContain('/service.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components that have changed', () => {
 | |
|       env.write('component1.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: 'cmp', template: 'cmp'})
 | |
|       export class Cmp1 {}
 | |
|     `);
 | |
|       env.write('component2.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: 'cmp2', template: 'cmp'})
 | |
|       export class Cmp2 {}
 | |
|     `);
 | |
|       env.driveMain();
 | |
| 
 | |
|       // Pretend a change was made to Cmp1
 | |
|       env.flushWrittenFileTracking();
 | |
|       env.invalidateCachedFile('component1.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).toContain('/component1.js');
 | |
|       expect(written).not.toContain('/component2.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components whose templates have changed', () => {
 | |
|       env.write('component1.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: 'cmp', templateUrl: './component1.template.html'})
 | |
|       export class Cmp1 {}
 | |
|     `);
 | |
|       env.write('component1.template.html', 'cmp1');
 | |
|       env.write('component2.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: 'cmp2', templateUrl: './component2.template.html'})
 | |
|       export class Cmp2 {}
 | |
|     `);
 | |
|       env.write('component2.template.html', 'cmp2');
 | |
| 
 | |
|       env.driveMain();
 | |
| 
 | |
|       // Make a change to Cmp1 template
 | |
|       env.flushWrittenFileTracking();
 | |
|       env.write('component1.template.html', 'changed');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).toContain('/component1.js');
 | |
|       expect(written).not.toContain('/component2.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components whose partial-evaluation dependencies have changed', () => {
 | |
|       env.write('component1.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
| 
 | |
|       @Component({selector: 'cmp', template: 'cmp'})
 | |
|       export class Cmp1 {}
 | |
|     `);
 | |
|       env.write('component2.ts', `
 | |
|       import {Component} from '@angular/core';
 | |
|       import {SELECTOR} from './constants';
 | |
| 
 | |
|       @Component({selector: SELECTOR, template: 'cmp'})
 | |
|       export class Cmp2 {}
 | |
|     `);
 | |
|       env.write('constants.ts', `
 | |
|       export const SELECTOR = 'cmp';
 | |
|     `);
 | |
|       env.driveMain();
 | |
| 
 | |
|       // Pretend a change was made to SELECTOR
 | |
|       env.flushWrittenFileTracking();
 | |
|       env.invalidateCachedFile('constants.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).toContain('/constants.js');
 | |
|       expect(written).not.toContain('/component1.js');
 | |
|       expect(written).toContain('/component2.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components whose imported dependencies have changed', () => {
 | |
|       setupFooBarProgram(env);
 | |
| 
 | |
|       // Pretend a change was made to BarDir.
 | |
|       env.invalidateCachedFile('bar_directive.ts');
 | |
|       env.driveMain();
 | |
| 
 | |
|       let written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).toContain('/bar_directive.js');
 | |
|       expect(written).toContain('/bar_component.js');
 | |
|       expect(written).toContain('/bar_module.js');
 | |
|       expect(written).not.toContain('/foo_component.js');
 | |
|       expect(written).not.toContain('/foo_pipe.js');
 | |
|       expect(written).not.toContain('/foo_module.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components where their NgModule declared dependencies have changed', () => {
 | |
|       setupFooBarProgram(env);
 | |
| 
 | |
|       // Pretend a change was made to FooPipe.
 | |
|       env.invalidateCachedFile('foo_pipe.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).not.toContain('/bar_directive.js');
 | |
|       expect(written).not.toContain('/bar_component.js');
 | |
|       expect(written).not.toContain('/bar_module.js');
 | |
|       expect(written).toContain('/foo_component.js');
 | |
|       expect(written).toContain('/foo_pipe.js');
 | |
|       expect(written).toContain('/foo_module.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild components where their NgModule has changed', () => {
 | |
|       setupFooBarProgram(env);
 | |
| 
 | |
|       // Pretend a change was made to FooPipe.
 | |
|       env.invalidateCachedFile('foo_module.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).not.toContain('/bar_directive.js');
 | |
|       expect(written).not.toContain('/bar_component.js');
 | |
|       expect(written).not.toContain('/bar_module.js');
 | |
|       expect(written).toContain('/foo_component.js');
 | |
|       expect(written).toContain('/foo_pipe.js');
 | |
|       expect(written).toContain('/foo_module.js');
 | |
|     });
 | |
| 
 | |
|     it('should rebuild everything if a typings file changes', () => {
 | |
|       setupFooBarProgram(env);
 | |
| 
 | |
|       // Pretend a change was made to a typings file.
 | |
|       env.invalidateCachedFile('foo_selector.d.ts');
 | |
|       env.driveMain();
 | |
|       const written = env.getFilesWrittenSinceLastFlush();
 | |
|       expect(written).toContain('/bar_directive.js');
 | |
|       expect(written).toContain('/bar_component.js');
 | |
|       expect(written).toContain('/bar_module.js');
 | |
|       expect(written).toContain('/foo_component.js');
 | |
|       expect(written).toContain('/foo_pipe.js');
 | |
|       expect(written).toContain('/foo_module.js');
 | |
|     });
 | |
| 
 | |
|     it('should compile incrementally with template type-checking turned on', () => {
 | |
|       env.tsconfig({ivyTemplateTypeCheck: true});
 | |
|       env.write('main.ts', 'export class Foo {}');
 | |
|       env.driveMain();
 | |
|       env.invalidateCachedFile('main.ts');
 | |
|       env.driveMain();
 | |
|       // If program reuse were configured incorrectly (as was responsible for
 | |
|       // https://github.com/angular/angular/issues/30079), this would have crashed.
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   function setupFooBarProgram(env: NgtscTestEnvironment) {
 | |
|     env.write('foo_component.ts', `
 | |
|     import {Component} from '@angular/core';
 | |
|     import {fooSelector} from './foo_selector';
 | |
| 
 | |
|     @Component({selector: fooSelector, template: 'foo'})
 | |
|     export class FooCmp {}
 | |
|   `);
 | |
|     env.write('foo_pipe.ts', `
 | |
|     import {Pipe} from '@angular/core';
 | |
| 
 | |
|     @Pipe({name: 'foo'})
 | |
|     export class FooPipe {}
 | |
|   `);
 | |
|     env.write('foo_module.ts', `
 | |
|     import {NgModule} from '@angular/core';
 | |
|     import {FooCmp} from './foo_component';
 | |
|     import {FooPipe} from './foo_pipe';
 | |
|     import {BarModule} from './bar_module';
 | |
|     @NgModule({
 | |
|       declarations: [FooCmp, FooPipe],
 | |
|       imports: [BarModule],
 | |
|     })
 | |
|     export class FooModule {}
 | |
|   `);
 | |
|     env.write('bar_component.ts', `
 | |
|     import {Component} from '@angular/core';
 | |
| 
 | |
|     @Component({selector: 'bar', template: 'bar'})
 | |
|     export class BarCmp {}
 | |
|   `);
 | |
|     env.write('bar_directive.ts', `
 | |
|     import {Directive} from '@angular/core';
 | |
| 
 | |
|     @Directive({selector: '[bar]'})
 | |
|     export class BarDir {}
 | |
|   `);
 | |
|     env.write('bar_module.ts', `
 | |
|     import {NgModule} from '@angular/core';
 | |
|     import {BarCmp} from './bar_component';
 | |
|     import {BarDir} from './bar_directive';
 | |
|     @NgModule({
 | |
|       declarations: [BarCmp, BarDir],
 | |
|       exports: [BarCmp],
 | |
|     })
 | |
|     export class BarModule {}
 | |
|   `);
 | |
|     env.write('foo_selector.d.ts', `
 | |
|     export const fooSelector = 'foo';
 | |
|   `);
 | |
|     env.driveMain();
 | |
|     env.flushWrittenFileTracking();
 | |
|   }
 | |
| });
 |