fix(ivy): ensure component/directive class selectors are properly understood (#27849)

Angular allows for `<ng-content>` elements to include a selector which
filters which content-projected entries are inserted into the container
depending on whether or not the selector is matched.

With Ivy this feature has not fully worked due to the massive changes
that took place inside of Ivy's styling algorithm code (which is
responsible for assigning classes and styles to an element). This
fix ensures that content-projection can correctly identify which slot
an element should be placed into when class-based selectors are used.

PR Close #27849
This commit is contained in:
Matias Niemelä
2019-01-11 08:37:51 -08:00
committed by Andrew Kushnir
parent 06e5bf1661
commit e62eeed7d4
4 changed files with 179 additions and 113 deletions

View File

@ -10,6 +10,7 @@ import {AttributeMarker, TAttributes, TNode, TNodeType} from '../../src/render3/
import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags,} from '../../src/render3/interfaces/projection';
import {getProjectAsAttrValue, isNodeMatchingSelectorList, isNodeMatchingSelector} from '../../src/render3/node_selector_matcher';
import {initializeStaticContext} from '../../src/render3/styling/class_and_style_bindings';
import {createTNode} from '@angular/core/src/render3/instructions';
import {getLView} from '@angular/core/src/render3/state';
@ -18,9 +19,12 @@ function testLStaticData(tagName: string, attrs: TAttributes | null): TNode {
}
describe('css selector matching', () => {
function isMatching(tagName: string, attrs: TAttributes | null, selector: CssSelector): boolean {
return isNodeMatchingSelector(
createTNode(getLView(), TNodeType.Element, 0, tagName, attrs, null), selector, false);
function isMatching(
tagName: string, attrsOrTNode: TAttributes | TNode | null, selector: CssSelector): boolean {
const tNode = (!attrsOrTNode || Array.isArray(attrsOrTNode)) ?
createTNode(getLView(), TNodeType.Element, 0, tagName, attrsOrTNode as TAttributes, null) :
(attrsOrTNode as TNode);
return isNodeMatchingSelector(tNode, selector, false);
}
describe('isNodeMatchingSimpleSelector', () => {
@ -298,6 +302,26 @@ describe('css selector matching', () => {
// <div class="foo">
expect(isMatching('div', ['class', 'foo'], selector)).toBeFalsy();
});
it('should match against a class value before and after the styling context is created',
() => {
// selector: 'div.abc'
const selector = ['div', SelectorFlags.CLASS, 'abc'];
const tNode = createTNode(getLView(), TNodeType.Element, 0, 'div', [], null);
// <div> (without attrs or styling context)
expect(isMatching('div', tNode, selector)).toBeFalsy();
// <div class="abc"> (with attrs but without styling context)
tNode.attrs = ['class', 'abc'];
tNode.stylingTemplate = null;
expect(isMatching('div', tNode, selector)).toBeTruthy();
// <div class="abc"> (with styling context but without attrs)
tNode.stylingTemplate = initializeStaticContext([AttributeMarker.Classes, 'abc']);
tNode.attrs = null;
expect(isMatching('div', tNode, selector)).toBeTruthy();
});
});
});