The output of the compiler has to be the same given the same input. Requiring a unique id for every type already during compilation makes it hard to parallelize compilation. Part of #3605 Closes #4397
		
			
				
	
	
		
			327 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
  ddescribe,
 | 
						|
  describe,
 | 
						|
  xdescribe,
 | 
						|
  it,
 | 
						|
  iit,
 | 
						|
  xit,
 | 
						|
  expect,
 | 
						|
  beforeEach,
 | 
						|
  afterEach,
 | 
						|
  AsyncTestCompleter,
 | 
						|
  inject,
 | 
						|
  beforeEachBindings
 | 
						|
} from 'angular2/test_lib';
 | 
						|
import {bind} from 'angular2/src/core/di';
 | 
						|
import {SpyXHR} from '../core/spies';
 | 
						|
import {XHR} from 'angular2/src/core/render/xhr';
 | 
						|
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
 | 
						|
 | 
						|
import {CONST_EXPR, isPresent, isBlank, StringWrapper} from 'angular2/src/core/facade/lang';
 | 
						|
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
 | 
						|
import {evalModule} from './eval_module';
 | 
						|
import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
 | 
						|
import {
 | 
						|
  CompileDirectiveMetadata,
 | 
						|
  CompileTemplateMetadata,
 | 
						|
  CompileTypeMetadata
 | 
						|
} from 'angular2/src/compiler/directive_metadata';
 | 
						|
import {SourceExpression, SourceModule} from 'angular2/src/compiler/source_module';
 | 
						|
import {ViewEncapsulation} from 'angular2/src/core/render/api';
 | 
						|
import {TEST_BINDINGS} from './test_bindings';
 | 
						|
import {codeGenValueFn, codeGenExportVariable} from 'angular2/src/compiler/util';
 | 
						|
 | 
						|
// Attention: These module names have to correspond to real modules!
 | 
						|
const MODULE_NAME = 'angular2/test/compiler/style_compiler_spec';
 | 
						|
const IMPORT_ABS_MODULE_NAME = 'angular2/test/compiler/style_compiler_import';
 | 
						|
const IMPORT_REL_MODULE_NAME = './style_compiler_import';
 | 
						|
// Note: Not a real module, only used via mocks.
 | 
						|
const IMPORT_ABS_MODULE_NAME_WITH_IMPORT =
 | 
						|
    'angular2/test/compiler/style_compiler_transitive_import';
 | 
						|
 | 
						|
export function main() {
 | 
						|
  describe('StyleCompiler', () => {
 | 
						|
    var xhr: SpyXHR;
 | 
						|
    var templateId;
 | 
						|
    var appId;
 | 
						|
 | 
						|
    beforeEachBindings(() => {
 | 
						|
      xhr = <any>new SpyXHR();
 | 
						|
      return [TEST_BINDINGS, bind(XHR).toValue(xhr)];
 | 
						|
    });
 | 
						|
 | 
						|
    var compiler: StyleCompiler;
 | 
						|
 | 
						|
    beforeEach(inject([StyleCompiler], (_compiler) => {
 | 
						|
      templateId = 23;
 | 
						|
      appId = 'app1';
 | 
						|
      compiler = _compiler;
 | 
						|
    }));
 | 
						|
 | 
						|
    describe('compileComponentRuntime', () => {
 | 
						|
      var xhrUrlResults;
 | 
						|
      var xhrCount;
 | 
						|
 | 
						|
      beforeEach(() => {
 | 
						|
        xhrCount = 0;
 | 
						|
        xhrUrlResults = {};
 | 
						|
        xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: blue}';
 | 
						|
        xhrUrlResults[IMPORT_ABS_MODULE_NAME_WITH_IMPORT] =
 | 
						|
            `a {color: green}@import ${IMPORT_REL_MODULE_NAME};`;
 | 
						|
      });
 | 
						|
 | 
						|
      function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
 | 
						|
          Promise<string[]> {
 | 
						|
        // Note: Can't use MockXHR as the xhr is called recursively,
 | 
						|
        // so we can't trigger flush.
 | 
						|
        xhr.spy('get').andCallFake((url) => {
 | 
						|
          var response = xhrUrlResults[url];
 | 
						|
          xhrCount++;
 | 
						|
          if (isBlank(response)) {
 | 
						|
            throw new BaseException(`Unexpected url ${url}`);
 | 
						|
          }
 | 
						|
          return PromiseWrapper.resolve(response);
 | 
						|
        });
 | 
						|
        return compiler.compileComponentRuntime(
 | 
						|
            appId, templateId,
 | 
						|
            new CompileTemplateMetadata(
 | 
						|
                {styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation}));
 | 
						|
      }
 | 
						|
 | 
						|
      describe('no shim', () => {
 | 
						|
        var encapsulation = ViewEncapsulation.None;
 | 
						|
 | 
						|
        it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles)
 | 
						|
                       .toEqual(['div {color: red}', 'a {color: green}', 'span {color: blue}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
      });
 | 
						|
 | 
						|
      describe('with shim', () => {
 | 
						|
        var encapsulation = ViewEncapsulation.Emulated;
 | 
						|
 | 
						|
        it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   compareStyles(styles, [
 | 
						|
                     'div[_ngcontent-app1-23] {\ncolor: red;\n}',
 | 
						|
                     'span[_ngcontent-app1-23] {\ncolor: blue;\n}'
 | 
						|
                   ]);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   compareStyles(styles, [
 | 
						|
                     'div[_ngcontent-app1-23] {\ncolor: red;\n}',
 | 
						|
                     'span[_ngcontent-app1-23] {\ncolor: blue;\n}'
 | 
						|
                   ]);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   compareStyles(styles, [
 | 
						|
                     'div[_ngcontent-app1-23] {\ncolor: red;\n}',
 | 
						|
                     'a[_ngcontent-app1-23] {\ncolor: green;\n}',
 | 
						|
                     'span[_ngcontent-app1-23] {\ncolor: blue;\n}'
 | 
						|
                   ]);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
      });
 | 
						|
 | 
						|
      it('should cache stylesheets for parallel requests', inject([AsyncTestCompleter], (async) => {
 | 
						|
           PromiseWrapper.all([
 | 
						|
                           compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None),
 | 
						|
                           compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
 | 
						|
                         ])
 | 
						|
               .then((styleArrays) => {
 | 
						|
                 expect(styleArrays[0]).toEqual(['span {color: blue}']);
 | 
						|
                 expect(styleArrays[1]).toEqual(['span {color: blue}']);
 | 
						|
                 expect(xhrCount).toBe(1);
 | 
						|
                 async.done();
 | 
						|
               });
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should cache stylesheets for serial requests', inject([AsyncTestCompleter], (async) => {
 | 
						|
           compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
 | 
						|
               .then((styles0) => {
 | 
						|
                 xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
 | 
						|
                 return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
 | 
						|
                     .then((styles1) => {
 | 
						|
                       expect(styles0).toEqual(['span {color: blue}']);
 | 
						|
                       expect(styles1).toEqual(['span {color: blue}']);
 | 
						|
                       expect(xhrCount).toBe(1);
 | 
						|
                       async.done();
 | 
						|
                     });
 | 
						|
               });
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should allow to clear the cache', inject([AsyncTestCompleter], (async) => {
 | 
						|
           compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
 | 
						|
               .then((_) => {
 | 
						|
                 compiler.clearCache();
 | 
						|
                 xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
 | 
						|
                 return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None);
 | 
						|
               })
 | 
						|
               .then((styles) => {
 | 
						|
                 expect(xhrCount).toBe(2);
 | 
						|
                 expect(styles).toEqual(['span {color: black}']);
 | 
						|
                 async.done();
 | 
						|
               });
 | 
						|
         }));
 | 
						|
    });
 | 
						|
 | 
						|
    describe('compileComponentCodeGen', () => {
 | 
						|
      function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
 | 
						|
          Promise<string[]> {
 | 
						|
        var sourceExpression = compiler.compileComponentCodeGen(
 | 
						|
            `'${appId}'`, `${templateId}`,
 | 
						|
            new CompileTemplateMetadata(
 | 
						|
                {styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation}));
 | 
						|
        var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports();
 | 
						|
        return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
 | 
						|
      };
 | 
						|
 | 
						|
      describe('no shim', () => {
 | 
						|
        var encapsulation = ViewEncapsulation.None;
 | 
						|
 | 
						|
        it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should compile css rules with newlines and quotes',
 | 
						|
           inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div\n{"color": \'red\'}'], [], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles).toEqual(['div\n{"color": \'red\'}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }), 1000);
 | 
						|
      });
 | 
						|
 | 
						|
      describe('with shim', () => {
 | 
						|
        var encapsulation = ViewEncapsulation.Emulated;
 | 
						|
 | 
						|
        it('should compile plain css ruless', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   compareStyles(styles, [
 | 
						|
                     'div[_ngcontent-app1-23] {\ncolor: red;\n}',
 | 
						|
                     'span[_ngcontent-app1-23] {\ncolor: blue;\n}'
 | 
						|
                   ]);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }));
 | 
						|
 | 
						|
        it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
             compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
 | 
						|
                 .then(styles => {
 | 
						|
                   compareStyles(styles, [
 | 
						|
                     'div[_ngcontent-app1-23] {\ncolor: red;\n}',
 | 
						|
                     'span[_ngcontent-app1-23] {\ncolor: blue;\n}'
 | 
						|
                   ]);
 | 
						|
                   async.done();
 | 
						|
                 });
 | 
						|
           }), 1000);
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('compileStylesheetCodeGen', () => {
 | 
						|
      function compile(style: string): Promise<string[][]> {
 | 
						|
        var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style);
 | 
						|
        return PromiseWrapper.all(sourceModules.map(sourceModule => {
 | 
						|
          var sourceWithImports = testableModule(sourceModule).getSourceWithImports();
 | 
						|
          return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
 | 
						|
        }));
 | 
						|
      }
 | 
						|
 | 
						|
      it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
 | 
						|
           compile('div {color: red;}')
 | 
						|
               .then(stylesAndShimStyles => {
 | 
						|
                 var expected =
 | 
						|
                     [['div {color: red;}'], ['div[_ngcontent-%COMP%] {\ncolor: red;\n}']];
 | 
						|
                 compareStyles(stylesAndShimStyles[0], expected[0]);
 | 
						|
                 compareStyles(stylesAndShimStyles[1], expected[1]);
 | 
						|
                 async.done();
 | 
						|
               });
 | 
						|
         }));
 | 
						|
 | 
						|
      it('should allow to import rules with relative paths',
 | 
						|
         inject([AsyncTestCompleter], (async) => {
 | 
						|
           compile(`div {color: red}@import ${IMPORT_REL_MODULE_NAME};`)
 | 
						|
               .then(stylesAndShimStyles => {
 | 
						|
                 var expected = [
 | 
						|
                   ['div {color: red}', 'span {color: blue}'],
 | 
						|
                   [
 | 
						|
                     'div[_ngcontent-%COMP%] {\ncolor: red;\n}',
 | 
						|
                     'span[_ngcontent-%COMP%] {\ncolor: blue;\n}'
 | 
						|
                   ]
 | 
						|
                 ];
 | 
						|
                 compareStyles(stylesAndShimStyles[0], expected[0]);
 | 
						|
                 compareStyles(stylesAndShimStyles[1], expected[1]);
 | 
						|
                 async.done();
 | 
						|
               });
 | 
						|
         }));
 | 
						|
    });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function testableExpression(source: SourceExpression): SourceModule {
 | 
						|
  var testableSource = `${source.declarations.join('\n')}
 | 
						|
  ${codeGenExportVariable('run')}${codeGenValueFn(['_'], source.expression)};`;
 | 
						|
  return new SourceModule(null, testableSource);
 | 
						|
}
 | 
						|
 | 
						|
function testableModule(sourceModule: SourceModule): SourceModule {
 | 
						|
  var testableSource = `${sourceModule.sourceWithModuleRefs}
 | 
						|
  ${codeGenExportVariable('run')}${codeGenValueFn(['_'], 'STYLES')};`;
 | 
						|
  return new SourceModule(sourceModule.moduleId, testableSource);
 | 
						|
}
 | 
						|
 | 
						|
// Needed for Android browsers which add an extra space at the end of some lines
 | 
						|
function compareStyles(styles: string[], expectedStyles: string[]) {
 | 
						|
  expect(styles.length).toEqual(expectedStyles.length);
 | 
						|
  for (var i = 0; i < styles.length; i++) {
 | 
						|
    expect(StringWrapper.replaceAll(styles[i], /\s+\n/g, '\n')).toEqual(expectedStyles[i]);
 | 
						|
  }
 | 
						|
}
 |