fix(ivy): set `ng-version` attribute on root component (#27175)

PR Close #27175
This commit is contained in:
Olivier Combe 2018-11-19 21:06:41 +01:00 committed by Misko Hevery
parent 01917733a1
commit bf3beb5959
4 changed files with 83 additions and 77 deletions

View File

@ -16,16 +16,16 @@ import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
import {RendererFactory2} from '../render/api'; import {RendererFactory2} from '../render/api';
import {Type} from '../type'; import {Type} from '../type';
import {VERSION} from '../version';
import {assertComponentType, assertDefined} from './assert'; import {assertComponentType, assertDefined} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component'; import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
import {getComponentDef} from './definition'; import {getComponentDef} from './definition';
import {NodeInjector} from './di'; import {NodeInjector} from './di';
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions'; import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
import {ComponentDef, RenderFlags} from './interfaces/definition'; import {ComponentDef, RenderFlags} from './interfaces/definition';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {RElement, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view'; import {HEADER_OFFSET, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
import {enterView, leaveView} from './state'; import {enterView, leaveView} from './state';
import {defaultScheduler, getTNode} from './util'; import {defaultScheduler, getTNode} from './util';
import {createElementRef} from './view_engine_compatibility'; import {createElementRef} from './view_engine_compatibility';
@ -141,6 +141,14 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef); const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef);
const rootViewInjector = const rootViewInjector =
ngModule ? createChainedInjector(injector, ngModule.injector) : injector; ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
if (rootSelectorOrNode && hostRNode) {
ngDevMode && ngDevMode.rendererSetAttribute++;
isProceduralRenderer(renderer) ?
renderer.setAttribute(hostRNode, 'ng-version', VERSION.full) :
hostRNode.setAttribute('ng-version', VERSION.full);
}
// Create the root view. Uses empty TView and ContentTemplate. // Create the root view. Uses empty TView and ContentTemplate.
const rootView: LViewData = createLViewData( const rootView: LViewData = createLViewData(
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined, renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,

View File

@ -440,6 +440,9 @@
{ {
"name": "UnsubscriptionErrorImpl" "name": "UnsubscriptionErrorImpl"
}, },
{
"name": "VERSION"
},
{ {
"name": "VIEWS" "name": "VIEWS"
}, },

View File

@ -926,6 +926,9 @@
{ {
"name": "VALID_ELEMENTS" "name": "VALID_ELEMENTS"
}, },
{
"name": "VERSION"
},
{ {
"name": "VIEWS" "name": "VIEWS"
}, },

View File

@ -539,32 +539,29 @@ class HiddenModule {
}); });
afterEach(() => { expect(called).toBe(true); }); afterEach(() => { expect(called).toBe(true); });
fixmeIvy('to investigate') && it('using long form should work', async(() => {
it('using long form should work', async(() => { const platform =
const platform = platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
platform.bootstrapModule(AsyncServerModule) platform.bootstrapModule(AsyncServerModule)
.then((moduleRef) => { .then((moduleRef) => {
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
return applicationRef.isStable.pipe(first((isStable: boolean) => isStable)) return applicationRef.isStable.pipe(first((isStable: boolean) => isStable))
.toPromise(); .toPromise();
}) })
.then((b) => { .then((b) => {
expect(platform.injector.get(PlatformState).renderToString()) expect(platform.injector.get(PlatformState).renderToString()).toBe(expectedOutput);
.toBe(expectedOutput); platform.destroy();
platform.destroy();
called = true;
});
}));
fixmeIvy('to investigate') &&
it('using renderModule should work', async(() => {
renderModule(AsyncServerModule, {document: doc}).then(output => {
expect(output).toBe(expectedOutput);
called = true; called = true;
}); });
})); }));
it('using renderModule should work', async(() => {
renderModule(AsyncServerModule, {document: doc}).then(output => {
expect(output).toBe(expectedOutput);
called = true;
});
}));
it('using renderModuleFactory should work', it('using renderModuleFactory should work',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => { async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@ -609,14 +606,13 @@ class HiddenModule {
})); }));
fixmeIvy('to investigate') && it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => { renderModule(ExampleStylesModule, {document: doc}).then(output => {
renderModule(ExampleStylesModule, {document: doc}).then(output => { expect(output).toMatch(
expect(output).toMatch( /<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/); called = true;
called = true; });
}); }));
}));
fixmeIvy('to investigate') && fixmeIvy('to investigate') &&
it('should handle false values on attributes', async(() => { it('should handle false values on attributes', async(() => {
@ -662,29 +658,27 @@ class HiddenModule {
}); });
})); }));
fixmeIvy('to investigate') && it('should call render hook', async(() => {
it('should call render hook', async(() => { renderModule(RenderHookModule, {document: doc}).then(output => {
renderModule(RenderHookModule, {document: doc}).then(output => { // title should be added by the render hook.
// title should be added by the render hook. expect(output).toBe(
expect(output).toBe( '<html><head><title>RenderHook</title></head><body>' +
'<html><head><title>RenderHook</title></head><body>' + '<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>'); called = true;
called = true; });
}); }));
}));
fixmeIvy('to investigate') && it('should call multiple render hooks', async(() => {
it('should call multiple render hooks', async(() => { const consoleSpy = spyOn(console, 'warn');
const consoleSpy = spyOn(console, 'warn'); renderModule(MultiRenderHookModule, {document: doc}).then(output => {
renderModule(MultiRenderHookModule, {document: doc}).then(output => { // title should be added by the render hook.
// title should be added by the render hook. expect(output).toBe(
expect(output).toBe( '<html><head><title>RenderHook</title><meta name="description"></head>' +
'<html><head><title>RenderHook</title><meta name="description"></head>' + '<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>'); expect(consoleSpy).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalled(); called = true;
called = true; });
}); }));
}));
}); });
describe('http', () => { describe('http', () => {
@ -856,13 +850,12 @@ class HiddenModule {
beforeEach(() => { called = false; }); beforeEach(() => { called = false; });
afterEach(() => { expect(called).toBe(true); }); afterEach(() => { expect(called).toBe(true); });
fixmeIvy('to investigate') && it('adds transfer script tag when using renderModule', async(() => {
it('adds transfer script tag when using renderModule', async(() => { renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => { expect(output).toBe(defaultExpectedOutput);
expect(output).toBe(defaultExpectedOutput); called = true;
called = true; });
}); }));
}));
it('adds transfer script tag when using renderModuleFactory', it('adds transfer script tag when using renderModuleFactory',
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => { async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@ -876,19 +869,18 @@ class HiddenModule {
}); });
}))); })));
fixmeIvy('to investigate') && it('cannot break out of <script> tag in serialized output', async(() => {
it('cannot break out of <script> tag in serialized output', async(() => { renderModule(EscapedTransferStoreModule, {
renderModule(EscapedTransferStoreModule, { document: '<esc-app></esc-app>'
document: '<esc-app></esc-app>' }).then(output => {
}).then(output => { expect(output).toBe(
expect(output).toBe( '<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' +
'<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' + '<script id="transfer-state" type="application/json">' +
'<script id="transfer-state" type="application/json">' + '{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' + 'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>');
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>'); called = true;
called = true; });
}); }));
}));
}); });
}); });
})(); })();