fix(ivy): throw on bindings to unknown properties (#28537)
This commit adds a devMode-only check which will throw if a user attempts to bind a property that does not match a directive input or a known HTML property. Example: ``` <div [unknownProp]="someValue"></div> ``` The above will throw because "unknownProp" is not a known property of HTMLDivElement. This check is similar to the check executed in View Engine during template parsing, but occurs at runtime instead of compile-time. Note: This change uncovered an existing bug with host binding inheritance, so some Material tests had to be turned off. They will be fixed in an upcoming PR. PR Close #28537
This commit is contained in:
parent
7660d0d74a
commit
1950e2d9ba
|
@ -10,7 +10,7 @@ import {InjectFlags, InjectionToken, Injector} from '../di';
|
|||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {Type} from '../interface/type';
|
||||
import {validateAttribute, validateProperty} from '../sanitization/sanitization';
|
||||
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../sanitization/sanitization';
|
||||
import {Sanitizer} from '../sanitization/security';
|
||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {assertDataInRange, assertDefined, assertEqual, assertLessThan, assertNotEqual} from '../util/assert';
|
||||
|
@ -39,7 +39,7 @@ import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector
|
|||
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
|
||||
import {getInitialClassNameValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialStylesAndClasses, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
|
||||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
import {ANIMATION_PROP_PREFIX, createEmptyStylingContext, getStylingContext, hasClassInput, hasStyling, isAnimationProp} from './styling/util';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {INTERPOLATION_DELIMITER, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
|
||||
|
||||
|
@ -1099,7 +1099,7 @@ export function elementAttribute(
|
|||
index: number, name: string, value: any, sanitizer?: SanitizerFn | null,
|
||||
namespace?: string): void {
|
||||
if (value !== NO_CHANGE) {
|
||||
ngDevMode && validateAttribute(name);
|
||||
ngDevMode && validateAgainstEventAttributes(name);
|
||||
const lView = getLView();
|
||||
const renderer = lView[RENDERER];
|
||||
const element = getNativeByIndex(index, lView);
|
||||
|
@ -1193,7 +1193,8 @@ function elementPropertyInternal<T>(
|
|||
}
|
||||
} else if (tNode.type === TNodeType.Element) {
|
||||
if (ngDevMode) {
|
||||
validateProperty(propName);
|
||||
validateAgainstEventProperties(propName);
|
||||
validateAgainstUnknownProperties(element, propName, tNode);
|
||||
ngDevMode.rendererSetProperty++;
|
||||
}
|
||||
|
||||
|
@ -1212,6 +1213,18 @@ function elementPropertyInternal<T>(
|
|||
}
|
||||
}
|
||||
|
||||
function validateAgainstUnknownProperties(
|
||||
element: RElement | RComment, propName: string, tNode: TNode) {
|
||||
// If prop is not a known property of the HTML element...
|
||||
if (!(propName in element) &&
|
||||
// and isn't a synthetic animation property...
|
||||
propName[0] !== ANIMATION_PROP_PREFIX) {
|
||||
// ... it is probably a user error and we should throw.
|
||||
throw new Error(
|
||||
`Template error: Can't bind to '${propName}' since it isn't a known property of '${tNode.tagName}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores debugging data for this property binding on first template pass.
|
||||
* This enables features like DebugElement.properties.
|
||||
|
|
|
@ -20,7 +20,7 @@ import {getTNode} from '../util';
|
|||
|
||||
import {CorePlayerHandler} from './core_player_handler';
|
||||
|
||||
const ANIMATION_PROP_PREFIX = '@';
|
||||
export const ANIMATION_PROP_PREFIX = '@';
|
||||
|
||||
export function createEmptyStylingContext(
|
||||
element?: RElement | null, sanitizer?: StyleSanitizeFn | null,
|
||||
|
|
|
@ -178,7 +178,7 @@ export const defaultStyleSanitizer = (function(prop: string, value?: string): st
|
|||
return sanitizeStyle(value);
|
||||
} as StyleSanitizeFn);
|
||||
|
||||
export function validateProperty(name: string) {
|
||||
export function validateAgainstEventProperties(name: string) {
|
||||
if (name.toLowerCase().startsWith('on')) {
|
||||
const msg = `Binding to event property '${name}' is disallowed for security reasons, ` +
|
||||
`please use (${name.slice(2)})=...` +
|
||||
|
@ -188,7 +188,7 @@ export function validateProperty(name: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function validateAttribute(name: string) {
|
||||
export function validateAgainstEventAttributes(name: string) {
|
||||
if (name.toLowerCase().startsWith('on')) {
|
||||
const msg = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
|
||||
`please use (${name.slice(2)})=...`;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DomElementSchemaRegistry, ElementSchemaRegistry, ResourceLoader, UrlResolver} from '@angular/compiler';
|
||||
import {MockResourceLoader, MockSchemaRegistry} from '@angular/compiler/testing';
|
||||
import {ResourceLoader, UrlResolver} from '@angular/compiler';
|
||||
import {MockResourceLoader} from '@angular/compiler/testing';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, Provider, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
|
@ -20,14 +20,12 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
|
|||
}
|
||||
|
||||
const TEST_COMPILER_PROVIDERS: Provider[] = [
|
||||
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
|
||||
{provide: ResourceLoader, useClass: MockResourceLoader, deps: []},
|
||||
{provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix, deps: []}
|
||||
];
|
||||
|
||||
|
||||
(function() {
|
||||
let elSchema: MockSchemaRegistry;
|
||||
let renderLog: RenderLog;
|
||||
let directiveLog: DirectiveLog;
|
||||
|
||||
|
@ -43,10 +41,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
}
|
||||
|
||||
function initHelpers(): void {
|
||||
elSchema = TestBed.get(ElementSchemaRegistry);
|
||||
renderLog = TestBed.get(RenderLog);
|
||||
directiveLog = TestBed.get(DirectiveLog);
|
||||
elSchema.existingProperties['someProp'] = true;
|
||||
patchLoggingRenderer2(TestBed.get(RendererFactory2), renderLog);
|
||||
}
|
||||
|
||||
|
@ -67,7 +63,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
function _bindSimpleValue<T>(expression: any, compType: Type<T>): ComponentFixture<T>;
|
||||
function _bindSimpleValue<T>(
|
||||
expression: any, compType: Type<T> = <any>TestComponent): ComponentFixture<T> {
|
||||
return _bindSimpleProp(`[someProp]='${expression}'`, compType);
|
||||
return _bindSimpleProp(`[id]='${expression}'`, compType);
|
||||
}
|
||||
|
||||
function _bindAndCheckSimpleValue(
|
||||
|
@ -117,145 +113,125 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
describe('expressions', () => {
|
||||
it('should support literals',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue(10)).toEqual(['someProp=10']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue(10)).toEqual(['id=10']); }));
|
||||
|
||||
it('should strip quotes from literals',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"str"')).toEqual(['someProp=str']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"str"')).toEqual(['id=str']); }));
|
||||
|
||||
it('should support newlines in literals', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('"a\n\nb"')).toEqual(['someProp=a\n\nb']);
|
||||
}));
|
||||
it('should support newlines in literals',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"a\n\nb"')).toEqual(['id=a\n\nb']); }));
|
||||
|
||||
it('should support + operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 + 2')).toEqual(['someProp=12']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 + 2')).toEqual(['id=12']); }));
|
||||
|
||||
it('should support - operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 - 2')).toEqual(['someProp=8']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 - 2')).toEqual(['id=8']); }));
|
||||
|
||||
it('should support * operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 * 2')).toEqual(['someProp=20']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('10 * 2')).toEqual(['id=20']); }));
|
||||
|
||||
it('should support / operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('10 / 2')).toEqual([`someProp=${5.0}`]);
|
||||
expect(_bindAndCheckSimpleValue('10 / 2')).toEqual([`id=${5.0}`]);
|
||||
})); // dart exp=5.0, js exp=5
|
||||
|
||||
it('should support % operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('11 % 2')).toEqual(['someProp=1']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('11 % 2')).toEqual(['id=1']); }));
|
||||
|
||||
it('should support == operations on identical', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 == 1')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support == operations on identical',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 == 1')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support != operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 != 1')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support != operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 != 1')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support == operations on coerceible', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 == true')).toEqual([`someProp=true`]);
|
||||
}));
|
||||
it('should support == operations on coerceible',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 == true')).toEqual([`id=true`]); }));
|
||||
|
||||
it('should support === operations on identical', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 === 1')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support === operations on identical',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 === 1')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support !== operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 !== 1')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support !== operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 !== 1')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support === operations on coerceible', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 === true')).toEqual(['someProp=false']);
|
||||
expect(_bindAndCheckSimpleValue('1 === true')).toEqual(['id=false']);
|
||||
}));
|
||||
|
||||
it('should support true < operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 < 2')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support true < operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 < 2')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support false < operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 < 1')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support false < operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 < 1')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support false > operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 > 2')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support false > operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 > 2')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support true > operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 > 1')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support true > operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 > 1')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support true <= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 <= 2')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support true <= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 <= 2')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support equal <= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 <= 2')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support equal <= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 <= 2')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support false <= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 <= 1')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support false <= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 <= 1')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support true >= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 >= 1')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support true >= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 >= 1')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support equal >= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('2 >= 2')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support equal >= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('2 >= 2')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support false >= operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 >= 2')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support false >= operations',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 >= 2')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support true && operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('true && true')).toEqual(['someProp=true']);
|
||||
expect(_bindAndCheckSimpleValue('true && true')).toEqual(['id=true']);
|
||||
}));
|
||||
|
||||
it('should support false && operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('true && false')).toEqual(['someProp=false']);
|
||||
expect(_bindAndCheckSimpleValue('true && false')).toEqual(['id=false']);
|
||||
}));
|
||||
|
||||
it('should support true || operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('true || false')).toEqual(['someProp=true']);
|
||||
expect(_bindAndCheckSimpleValue('true || false')).toEqual(['id=true']);
|
||||
}));
|
||||
|
||||
it('should support false || operations', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('false || false')).toEqual(['someProp=false']);
|
||||
expect(_bindAndCheckSimpleValue('false || false')).toEqual(['id=false']);
|
||||
}));
|
||||
|
||||
it('should support negate', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('!true')).toEqual(['someProp=false']);
|
||||
}));
|
||||
it('should support negate',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('!true')).toEqual(['id=false']); }));
|
||||
|
||||
it('should support double negate', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('!!true')).toEqual(['someProp=true']);
|
||||
}));
|
||||
it('should support double negate',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('!!true')).toEqual(['id=true']); }));
|
||||
|
||||
it('should support true conditionals', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 < 2 ? 1 : 2')).toEqual(['someProp=1']);
|
||||
}));
|
||||
it('should support true conditionals',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 < 2 ? 1 : 2')).toEqual(['id=1']); }));
|
||||
|
||||
it('should support false conditionals', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('1 > 2 ? 1 : 2')).toEqual(['someProp=2']);
|
||||
}));
|
||||
it('should support false conditionals',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('1 > 2 ? 1 : 2')).toEqual(['id=2']); }));
|
||||
|
||||
it('should support keyed access to a list item', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('["foo", "bar"][0]')).toEqual(['someProp=foo']);
|
||||
expect(_bindAndCheckSimpleValue('["foo", "bar"][0]')).toEqual(['id=foo']);
|
||||
}));
|
||||
|
||||
it('should support keyed access to a map item', fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('{"foo": "bar"}["foo"]')).toEqual(['someProp=bar']);
|
||||
expect(_bindAndCheckSimpleValue('{"foo": "bar"}["foo"]')).toEqual(['id=bar']);
|
||||
}));
|
||||
|
||||
it('should report all changes on the first run including uninitialized values',
|
||||
fakeAsync(() => {
|
||||
expect(_bindAndCheckSimpleValue('value', Uninitialized)).toEqual(['someProp=null']);
|
||||
expect(_bindAndCheckSimpleValue('value', Uninitialized)).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should report all changes on the first run including null values', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('a', TestData);
|
||||
ctx.componentInstance.a = null;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support simple chained property access', fakeAsync(() => {
|
||||
|
@ -263,7 +239,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
ctx.componentInstance.name = 'Victor';
|
||||
ctx.componentInstance.address = new Address('Grenoble');
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=Grenoble']);
|
||||
expect(renderLog.log).toEqual(['id=Grenoble']);
|
||||
}));
|
||||
|
||||
describe('safe navigation operator', () => {
|
||||
|
@ -271,54 +247,54 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
const ctx = _bindSimpleValue('address?.city', Person);
|
||||
ctx.componentInstance.address = null !;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support calling methods on nulls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('address?.toString()', Person);
|
||||
ctx.componentInstance.address = null !;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support reading properties on non nulls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('address?.city', Person);
|
||||
ctx.componentInstance.address = new Address('MTV');
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=MTV']);
|
||||
expect(renderLog.log).toEqual(['id=MTV']);
|
||||
}));
|
||||
|
||||
it('should support calling methods on non nulls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('address?.toString()', Person);
|
||||
ctx.componentInstance.address = new Address('MTV');
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=MTV']);
|
||||
expect(renderLog.log).toEqual(['id=MTV']);
|
||||
}));
|
||||
|
||||
it('should support short-circuting safe navigation', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('value?.address.city', PersonHolder);
|
||||
ctx.componentInstance.value = null !;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support nested short-circuting safe navigation', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('value.value?.address.city', PersonHolderHolder);
|
||||
ctx.componentInstance.value = new PersonHolder();
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support chained short-circuting safe navigation', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('value?.value?.address.city', PersonHolderHolder);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should support short-circuting array index operations', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('value?.phones[0]', PersonHolder);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=null']);
|
||||
expect(renderLog.log).toEqual(['id=null']);
|
||||
}));
|
||||
|
||||
it('should still throw if right-side would throw', fakeAsync(() => {
|
||||
|
@ -335,21 +311,21 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
it('should support method calls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('sayHi("Jim")', Person);
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=Hi, Jim']);
|
||||
expect(renderLog.log).toEqual(['id=Hi, Jim']);
|
||||
}));
|
||||
|
||||
it('should support function calls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('a()(99)', TestData);
|
||||
ctx.componentInstance.a = () => (a: any) => a;
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=99']);
|
||||
expect(renderLog.log).toEqual(['id=99']);
|
||||
}));
|
||||
|
||||
it('should support chained method calls', fakeAsync(() => {
|
||||
const ctx = _bindSimpleValue('address.toString()', Person);
|
||||
ctx.componentInstance.address = new Address('MTV');
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=MTV']);
|
||||
expect(renderLog.log).toEqual(['id=MTV']);
|
||||
}));
|
||||
|
||||
it('should support NaN', fakeAsync(() => {
|
||||
|
@ -357,7 +333,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
ctx.componentInstance.age = NaN;
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=NaN']);
|
||||
expect(renderLog.log).toEqual(['id=NaN']);
|
||||
renderLog.clear();
|
||||
|
||||
ctx.detectChanges(false);
|
||||
|
@ -369,7 +345,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
ctx.componentInstance.name = 'misko';
|
||||
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=misko']);
|
||||
expect(renderLog.log).toEqual(['id=misko']);
|
||||
renderLog.clear();
|
||||
|
||||
ctx.detectChanges(false);
|
||||
|
@ -378,7 +354,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
ctx.componentInstance.name = 'Misko';
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.log).toEqual(['someProp=Misko']);
|
||||
expect(renderLog.log).toEqual(['id=Misko']);
|
||||
}));
|
||||
|
||||
it('should support literal array made of literals', fakeAsync(() => {
|
||||
|
@ -445,7 +421,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
|
||||
it('should ignore empty bindings', fakeAsync(() => {
|
||||
const ctx = _bindSimpleProp('[someProp]', TestData);
|
||||
const ctx = _bindSimpleProp('[id]', TestData);
|
||||
ctx.componentInstance.a = 'value';
|
||||
ctx.detectChanges(false);
|
||||
|
||||
|
@ -453,23 +429,23 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
}));
|
||||
|
||||
it('should support interpolation', fakeAsync(() => {
|
||||
const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData);
|
||||
const ctx = _bindSimpleProp('id="B{{a}}A"', TestData);
|
||||
ctx.componentInstance.a = 'value';
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=BvalueA']);
|
||||
expect(renderLog.log).toEqual(['id=BvalueA']);
|
||||
}));
|
||||
|
||||
it('should output empty strings for null values in interpolation', fakeAsync(() => {
|
||||
const ctx = _bindSimpleProp('someProp="B{{a}}A"', TestData);
|
||||
const ctx = _bindSimpleProp('id="B{{a}}A"', TestData);
|
||||
ctx.componentInstance.a = null;
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=BA']);
|
||||
expect(renderLog.log).toEqual(['id=BA']);
|
||||
}));
|
||||
|
||||
it('should escape values in literals that indicate interpolation',
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['someProp=$']); }));
|
||||
fakeAsync(() => { expect(_bindAndCheckSimpleValue('"$"')).toEqual(['id=$']); }));
|
||||
|
||||
it('should read locals', fakeAsync(() => {
|
||||
const ctx = createCompFixture(
|
||||
|
@ -515,7 +491,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
expect(renderLog.log).toEqual(['id=Megatron']);
|
||||
|
||||
renderLog.clear();
|
||||
ctx.detectChanges(false);
|
||||
|
@ -528,12 +504,12 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
expect(renderLog.log).toEqual(['id=Megatron']);
|
||||
|
||||
renderLog.clear();
|
||||
ctx.detectChanges(false);
|
||||
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
expect(renderLog.log).toEqual(['id=Megatron']);
|
||||
}));
|
||||
|
||||
it('should record unwrapped values via ngOnChanges', fakeAsync(() => {
|
||||
|
@ -591,8 +567,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
.it('should call pure pipes that are used multiple times only when the arguments change and share state between pipe instances',
|
||||
fakeAsync(() => {
|
||||
const ctx = createCompFixture(
|
||||
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
|
||||
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
|
||||
`<div [id]="name | countingPipe"></div><div [id]="age | countingPipe"></div>` +
|
||||
'<div *ngFor="let x of [1,2]" [id]="address.city | countingPipe"></div>',
|
||||
Person);
|
||||
ctx.componentInstance.name = 'a';
|
||||
ctx.componentInstance.age = 10;
|
||||
|
@ -618,8 +594,8 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
it('should call pure pipes that are used multiple times only when the arguments change',
|
||||
fakeAsync(() => {
|
||||
const ctx = createCompFixture(
|
||||
`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>` +
|
||||
'<div *ngFor="let x of [1,2]" [someProp]="address.city | countingPipe"></div>',
|
||||
`<div [id]="name | countingPipe"></div><div [id]="age | countingPipe"></div>` +
|
||||
'<div *ngFor="let x of [1,2]" [id]="address.city | countingPipe"></div>',
|
||||
Person);
|
||||
ctx.componentInstance.name = 'a';
|
||||
ctx.componentInstance.age = 10;
|
||||
|
@ -731,7 +707,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
describe('reading directives', () => {
|
||||
it('should read directive properties', fakeAsync(() => {
|
||||
const ctx = createCompFixture(
|
||||
'<div testDirective [a]="42" ref-dir="testDirective" [someProp]="dir.a"></div>');
|
||||
'<div testDirective [a]="42" ref-dir="testDirective" [id]="dir.a"></div>');
|
||||
ctx.detectChanges(false);
|
||||
expect(renderLog.loggedValues).toEqual([42]);
|
||||
}));
|
||||
|
@ -1191,7 +1167,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
TestBed.configureTestingModule({declarations: [ChangingDirective]});
|
||||
|
||||
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
|
||||
const ctx = createCompFixture('<div [id]="a" [changed]="b"></div>', TestData);
|
||||
|
||||
ctx.componentInstance.b = 1;
|
||||
const errMsgRegExp = ivyEnabled ?
|
||||
|
@ -1210,7 +1186,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
TestBed.configureTestingModule({declarations: [ChangingDirective]});
|
||||
|
||||
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
|
||||
const ctx = createCompFixture('<div [id]="a" [changed]="b"></div>', TestData);
|
||||
|
||||
ctx.componentInstance.b = 1;
|
||||
ctx.detectChanges();
|
||||
|
@ -1532,13 +1508,7 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
}
|
||||
|
||||
const ctx =
|
||||
TestBed
|
||||
.configureCompiler({
|
||||
providers:
|
||||
[{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}]
|
||||
})
|
||||
.configureTestingModule({declarations: [Comp, SomeDir]})
|
||||
.createComponent(Comp);
|
||||
TestBed.configureTestingModule({declarations: [Comp, SomeDir]}).createComponent(Comp);
|
||||
|
||||
ctx.detectChanges();
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
|||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {dispatchEvent, el} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {fixmeIvy, modifiedInIvy, obsoleteInIvy} from '@angular/private/testing';
|
||||
import {modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
import {stringify} from '../../src/util/stringify';
|
||||
|
||||
|
@ -1592,7 +1592,7 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
});
|
||||
|
||||
describe('Property bindings', () => {
|
||||
fixmeIvy('FW-721: Bindings to unknown properties are not reported as errors')
|
||||
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should throw on bindings to unknown properties', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const template = '<div unknown="{{ctxProp}}"></div>';
|
||||
|
@ -1606,6 +1606,21 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
}
|
||||
});
|
||||
|
||||
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should throw on bindings to unknown properties', () => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const template = '<div unknown="{{ctxProp}}"></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
try {
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
throw 'Should throw';
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch(
|
||||
/Template error: Can't bind to 'unknown' since it isn't a known property of 'div'./);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not throw for property binding to a non-existing property when there is a matching directive property',
|
||||
() => {
|
||||
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
|
||||
|
|
|
@ -239,7 +239,7 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
});
|
||||
|
||||
describe('schemas', () => {
|
||||
fixmeIvy('FW-819: ngtsc compiler should support schemas')
|
||||
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should error on unknown bound properties on custom elements by default', () => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
|
@ -252,19 +252,36 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(() => createModule(SomeModule)).toThrowError(/Can't bind to 'someUnknownProp'/);
|
||||
});
|
||||
|
||||
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
||||
() => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should error on unknown bound properties on custom elements by default', () => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
|
||||
@NgModule(
|
||||
{schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({declarations: [ComponentUsingInvalidProperty]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule)).not.toThrow();
|
||||
});
|
||||
const fixture = createComp(ComponentUsingInvalidProperty, SomeModule);
|
||||
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-819: ngtsc compiler should support schemas')
|
||||
.it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
||||
() => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [ComponentUsingInvalidProperty]
|
||||
})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => createModule(SomeModule)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('id', () => {
|
||||
|
|
|
@ -255,7 +255,7 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
expect(getDOM().getStyle(e, 'background')).not.toContain('javascript');
|
||||
});
|
||||
|
||||
fixmeIvy('FW-850: Should throw on unsafe SVG attributes')
|
||||
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should escape unsafe SVG attributes', () => {
|
||||
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
@ -264,6 +264,15 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
.toThrowError(/Can't bind to 'xlink:href'/);
|
||||
});
|
||||
|
||||
onlyInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should escape unsafe SVG attributes', () => {
|
||||
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'xlink:href'/);
|
||||
});
|
||||
|
||||
it('should escape unsafe HTML values', () => {
|
||||
const template = `<div [innerHTML]="ctxProp">Text</div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
|
|
|
@ -182,11 +182,12 @@ describe('instructions', () => {
|
|||
});
|
||||
|
||||
it('should not stringify non string values', () => {
|
||||
const t = new TemplateFixture(createDiv, () => {}, 1);
|
||||
const t = new TemplateFixture(() => { element(0, 'input'); }, () => {}, 1);
|
||||
|
||||
t.update(() => elementProperty(0, 'hidden', false));
|
||||
// The hidden property would be true if `false` was stringified into `"false"`.
|
||||
expect((t.hostElement as HTMLElement).querySelector('div') !.hidden).toEqual(false);
|
||||
// Note: don't use 'hidden' here because IE10 does not support the hidden property
|
||||
t.update(() => elementProperty(0, 'required', false));
|
||||
// The required property would be true if `false` was stringified into `"false"`.
|
||||
expect((t.hostElement as HTMLElement).querySelector('input') !.required).toEqual(false);
|
||||
expect(ngDevMode).toHaveProperties({
|
||||
firstTemplatePass: 1,
|
||||
tNode: 2, // 1 for div, 1 for host element
|
||||
|
|
|
@ -443,7 +443,8 @@ describe('lifecycles', () => {
|
|||
factory: () => new Component(), template,
|
||||
consts: consts,
|
||||
vars: vars,
|
||||
directives: directives
|
||||
directives: directives,
|
||||
inputs: {val: 'val'}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -2665,10 +2666,10 @@ describe('lifecycles', () => {
|
|||
element(0, 'div');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'data-a', bind(ctx.a));
|
||||
elementProperty(0, 'id', bind(ctx.a));
|
||||
}
|
||||
},
|
||||
selectors: [['mycomp']],
|
||||
selectors: [['my-comp']],
|
||||
inputs: {
|
||||
value: 'value',
|
||||
},
|
||||
|
@ -2683,7 +2684,7 @@ describe('lifecycles', () => {
|
|||
|
||||
const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
element(0, 'mycomp');
|
||||
element(0, 'my-comp');
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'value', bind(1));
|
||||
|
|
|
@ -170,12 +170,12 @@ describe('pipe', () => {
|
|||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 1, 'Megatron')));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 1, 'Megatron')));
|
||||
}
|
||||
}
|
||||
|
||||
renderToHtml(Template, person, 2, 3, null, [IdentityPipe], rendererFactory2);
|
||||
expect(renderLog.log).toEqual(['someProp=Megatron']);
|
||||
expect(renderLog.log).toEqual(['id=Megatron']);
|
||||
|
||||
renderLog.clear();
|
||||
renderToHtml(Template, person, 2, 3, null, pipes, rendererFactory2);
|
||||
|
@ -255,8 +255,8 @@ describe('pipe', () => {
|
|||
container(4);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true)));
|
||||
elementProperty(2, 'someProp', bind(pipeBind1(3, 4, true)));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 2, true)));
|
||||
elementProperty(2, 'id', bind(pipeBind1(3, 4, true)));
|
||||
pipeInstances.push(load<CountingImpurePipe>(1), load(3));
|
||||
containerRefreshStart(4);
|
||||
{
|
||||
|
@ -269,7 +269,7 @@ describe('pipe', () => {
|
|||
elementEnd();
|
||||
}
|
||||
if (rf1 & RenderFlags.Update) {
|
||||
elementProperty(0, 'someProp', bind(pipeBind1(1, 1, true)));
|
||||
elementProperty(0, 'id', bind(pipeBind1(1, 1, true)));
|
||||
pipeInstances.push(load<CountingImpurePipe>(1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,12 +42,14 @@ describe('ViewContainerRef', () => {
|
|||
factory: () => directiveInstance = new DirectiveWithVCRef(
|
||||
|
||||
directiveInject(ViewContainerRef as any), injectComponentFactoryResolver()),
|
||||
inputs: {tplRef: 'tplRef'}
|
||||
inputs: {tplRef: 'tplRef', name: 'name'}
|
||||
});
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
tplRef !: TemplateRef<{}>;
|
||||
|
||||
name: string = '';
|
||||
|
||||
// injecting a ViewContainerRef to create a dynamic container in which embedded views will be
|
||||
// created
|
||||
constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {MockBackend, MockConnection} from '@angular/http/testing';
|
|||
import {BrowserModule, DOCUMENT, Title, TransferState, makeStateKey} from '@angular/platform-browser';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, ServerTransferStateModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
||||
import {fixmeIvy, ivyEnabled} from '@angular/private/testing';
|
||||
import {fixmeIvy, ivyEnabled, modifiedInIvy} from '@angular/private/testing';
|
||||
import {Observable} from 'rxjs';
|
||||
import {first} from 'rxjs/operators';
|
||||
|
||||
|
@ -99,7 +99,7 @@ class TitleApp {
|
|||
class TitleAppModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: '{{text}}<h1 [innerText]="h1"></h1>'})
|
||||
@Component({selector: 'app', template: '{{text}}<h1 [textContent]="h1"></h1>'})
|
||||
class MyAsyncServerApp {
|
||||
text = '';
|
||||
h1 = '';
|
||||
|
@ -276,6 +276,19 @@ class MyHostComponent {
|
|||
class FalseAttributesModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: '<div [innerText]="foo"></div>'})
|
||||
class InnerTextComponent {
|
||||
foo = 'Some text';
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [InnerTextComponent],
|
||||
bootstrap: [InnerTextComponent],
|
||||
imports: [ServerModule, BrowserModule.withServerTransition({appId: 'inner-text'})]
|
||||
})
|
||||
class InnerTextModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: '<input [name]="name">'})
|
||||
class MyInputComponent {
|
||||
@Input()
|
||||
|
@ -528,7 +541,7 @@ class HiddenModule {
|
|||
let doc: string;
|
||||
let called: boolean;
|
||||
let expectedOutput =
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 innertext="fine">fine</h1></app></body></html>';
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">Works!<h1 textcontent="fine">fine</h1></app></body></html>';
|
||||
|
||||
beforeEach(() => {
|
||||
// PlatformConfig takes in a parsed document so that it can be cached across requests.
|
||||
|
@ -567,6 +580,15 @@ class HiddenModule {
|
|||
});
|
||||
}));
|
||||
|
||||
modifiedInIvy('Will not support binding to innerText in Ivy since domino does not')
|
||||
.it('should support binding to innerText', async(() => {
|
||||
renderModule(InnerTextModule, {document: doc}).then(output => {
|
||||
expect(output).toBe(
|
||||
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER"><div innertext="Some text">Some text</div></app></body></html>');
|
||||
called = true;
|
||||
});
|
||||
}));
|
||||
|
||||
it('using renderModuleFactory should work',
|
||||
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
|
||||
const compilerFactory: CompilerFactory =
|
||||
|
|
|
@ -953,6 +953,58 @@ window.testBlocklist = {
|
|||
"error": "Error: Expected undefined to be truthy.",
|
||||
"notes": "Unknown"
|
||||
},
|
||||
"MatButton should apply class based on color attribute": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton should not clear previous defined classes": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button[mat-fab] should have accent palette by default": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button[mat-mini-fab] should have accent palette by default": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button[mat-button] should not increment if disabled": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button[mat-button] should disable the native button element": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton a[mat-button] should not redirect if disabled": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton a[mat-button] should remove tabindex if disabled": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton a[mat-button] should add aria-disabled attribute if disabled": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton a[mat-button] should not add aria-disabled attribute if disabled is false": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton a[mat-button] should be able to set a custom tabindex": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button ripples should disable the ripple if matRippleDisabled input is set": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatButton button ripples should disable the ripple when the button is disabled": {
|
||||
"error": "Template error: Can't bind to 'disabled' since it isn't a known property of 'a'",
|
||||
"notes": "FW-1037: Host bindings for host objects in metadata are inherited"
|
||||
},
|
||||
"MatTabHeader focusing should initialize to the selected index": {
|
||||
"error": "TypeError: Cannot read property 'nativeElement' of undefined",
|
||||
"notes": "FW-1019: Design new API to replace static queries"
|
||||
|
|
Loading…
Reference in New Issue