fix(ivy): match class and attribute value without case-sensitivity (#32548)
Prior to this commit, a directive with a selector `selector=".Titledir"` would not match an element like `div class="titleDir"` in Ivy whereas it would in VE. The same issue was present for `selector="[title=Titledir]` and `title="titleDir"`. This fixes the Ivy behavior by changing the matching algorithm to use lowercased values. Note that some `render3` tests needed to be changed to reflect that the compiler generates lowercase selectors. These tests are in the process to be migrated to `acceptance` to use `TestBed` in another PR anyway. PR Close #32548
This commit is contained in:
parent
8a6e54a06d
commit
ded57245e1
@ -21,7 +21,10 @@ const NG_TEMPLATE_SELECTOR = 'ng-template';
|
|||||||
|
|
||||||
function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean {
|
function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean {
|
||||||
const nodeClassesLen = nodeClassAttrVal.length;
|
const nodeClassesLen = nodeClassAttrVal.length;
|
||||||
const matchIndex = nodeClassAttrVal !.indexOf(cssClassToMatch);
|
// we lowercase the class attribute value to be able to match
|
||||||
|
// selectors without case-sensitivity
|
||||||
|
// (selectors are already in lowercase when generated)
|
||||||
|
const matchIndex = nodeClassAttrVal.toLowerCase().indexOf(cssClassToMatch);
|
||||||
const matchEndIdx = matchIndex + cssClassToMatch.length;
|
const matchEndIdx = matchIndex + cssClassToMatch.length;
|
||||||
if (matchIndex === -1 // no match
|
if (matchIndex === -1 // no match
|
||||||
|| (matchIndex > 0 && nodeClassAttrVal ![matchIndex - 1] !== ' ') // no space before
|
|| (matchIndex > 0 && nodeClassAttrVal ![matchIndex - 1] !== ' ') // no space before
|
||||||
@ -132,7 +135,10 @@ export function isNodeMatchingSelector(
|
|||||||
ngDevMode && assertNotEqual(
|
ngDevMode && assertNotEqual(
|
||||||
nodeAttrs[attrIndexInNode], AttributeMarker.NamespaceURI,
|
nodeAttrs[attrIndexInNode], AttributeMarker.NamespaceURI,
|
||||||
'We do not match directives on namespaced attributes');
|
'We do not match directives on namespaced attributes');
|
||||||
nodeAttrValue = nodeAttrs[attrIndexInNode + 1] as string;
|
// we lowercase the attribute value to be able to match
|
||||||
|
// selectors without case-sensitivity
|
||||||
|
// (selectors are already in lowercase when generated)
|
||||||
|
nodeAttrValue = (nodeAttrs[attrIndexInNode + 1] as string).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
const compareAgainstClassName = mode & SelectorFlags.CLASS ? nodeAttrValue : null;
|
const compareAgainstClassName = mode & SelectorFlags.CLASS ? nodeAttrValue : null;
|
||||||
|
@ -913,7 +913,8 @@ describe('content projection', () => {
|
|||||||
*/
|
*/
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
ɵɵprojectionDef(['*', [['span', 'title', 'toFirst']], [['span', 'title', 'toSecond']]]);
|
// selectors are in lowercase once compiled
|
||||||
|
ɵɵprojectionDef(['*', [['span', 'title', 'tofirst']], [['span', 'title', 'tosecond']]]);
|
||||||
ɵɵelementStart(0, 'div', ['id', 'first']);
|
ɵɵelementStart(0, 'div', ['id', 'first']);
|
||||||
{ ɵɵprojection(1, 1); }
|
{ ɵɵprojection(1, 1); }
|
||||||
ɵɵelementEnd();
|
ɵɵelementEnd();
|
||||||
@ -957,9 +958,10 @@ describe('content projection', () => {
|
|||||||
*/
|
*/
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
|
// selectors are in lowercase once compiled
|
||||||
ɵɵprojectionDef([
|
ɵɵprojectionDef([
|
||||||
'*', [['span', SelectorFlags.CLASS, 'toFirst']],
|
'*', [['span', SelectorFlags.CLASS, 'tofirst']],
|
||||||
[['span', SelectorFlags.CLASS, 'toSecond']]
|
[['span', SelectorFlags.CLASS, 'tosecond']]
|
||||||
]);
|
]);
|
||||||
ɵɵelementStart(0, 'div', ['id', 'first']);
|
ɵɵelementStart(0, 'div', ['id', 'first']);
|
||||||
{ ɵɵprojection(1, 1); }
|
{ ɵɵprojection(1, 1); }
|
||||||
@ -1004,9 +1006,10 @@ describe('content projection', () => {
|
|||||||
*/
|
*/
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
|
// selectors are in lowercase once compiled
|
||||||
ɵɵprojectionDef([
|
ɵɵprojectionDef([
|
||||||
'*', [['span', SelectorFlags.CLASS, 'toFirst']],
|
'*', [['span', SelectorFlags.CLASS, 'tofirst']],
|
||||||
[['span', SelectorFlags.CLASS, 'toSecond']]
|
[['span', SelectorFlags.CLASS, 'tosecond']]
|
||||||
]);
|
]);
|
||||||
ɵɵelementStart(0, 'div', ['id', 'first']);
|
ɵɵelementStart(0, 'div', ['id', 'first']);
|
||||||
{ ɵɵprojection(1, 1); }
|
{ ɵɵprojection(1, 1); }
|
||||||
@ -1095,7 +1098,8 @@ describe('content projection', () => {
|
|||||||
*/
|
*/
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'toFirst']]]);
|
// selectors are in lowercase once compiled
|
||||||
|
ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tofirst']]]);
|
||||||
ɵɵelementStart(0, 'div', ['id', 'first']);
|
ɵɵelementStart(0, 'div', ['id', 'first']);
|
||||||
{ ɵɵprojection(1, 1); }
|
{ ɵɵprojection(1, 1); }
|
||||||
ɵɵelementEnd();
|
ɵɵelementEnd();
|
||||||
@ -1140,7 +1144,8 @@ describe('content projection', () => {
|
|||||||
*/
|
*/
|
||||||
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'toSecond']]]);
|
// selectors are in lowercase once compiled
|
||||||
|
ɵɵprojectionDef(['*', [['span', SelectorFlags.CLASS, 'tosecond']]]);
|
||||||
ɵɵelementStart(0, 'div', ['id', 'first']);
|
ɵɵelementStart(0, 'div', ['id', 'first']);
|
||||||
{ ɵɵprojection(1); }
|
{ ɵɵprojection(1); }
|
||||||
ɵɵelementEnd();
|
ɵɵelementEnd();
|
||||||
|
@ -94,14 +94,16 @@ describe('css selector matching', () => {
|
|||||||
])).toBeTruthy(`Selector '[title]' should match <span id="my_id" title="test_title">`);
|
])).toBeTruthy(`Selector '[title]' should match <span id="my_id" title="test_title">`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We assume that compiler will lower-case all selectors when generating code
|
||||||
|
*/
|
||||||
it('should match single attribute with value', () => {
|
it('should match single attribute with value', () => {
|
||||||
expect(isMatching('span', ['title', 'My Title'], [
|
expect(isMatching('span', ['title', 'My Title'], [
|
||||||
'', 'title', 'My Title'
|
'', 'title', 'my title'
|
||||||
])).toBeTruthy(`Selector '[title="My Title"]' should match <span title="My Title">'`);
|
])).toBeTruthy(`Selector '[title="My Title"]' should match <span title="My Title">'`);
|
||||||
|
|
||||||
expect(isMatching('span', ['title', 'My Title'], [
|
expect(isMatching('span', ['title', 'My Title'], [
|
||||||
'', 'title', 'Other Title'
|
'', 'title', 'other title'
|
||||||
])).toBeFalsy(`Selector '[title="Other Title"]' should NOT match <span title="My Title">`);
|
])).toBeFalsy(`Selector '[title="Other Title"]' should NOT match <span title="My Title">`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -111,7 +113,7 @@ describe('css selector matching', () => {
|
|||||||
])).toBeFalsy(`Selector 'div[title]' should NOT match <span title="My Title">`);
|
])).toBeFalsy(`Selector 'div[title]' should NOT match <span title="My Title">`);
|
||||||
|
|
||||||
expect(isMatching('span', ['title', 'My Title'], [
|
expect(isMatching('span', ['title', 'My Title'], [
|
||||||
'div', 'title', 'My Title'
|
'div', 'title', 'my title'
|
||||||
])).toBeFalsy(`Selector 'div[title="My Title"]' should NOT match <span title="My Title">`);
|
])).toBeFalsy(`Selector 'div[title="My Title"]' should NOT match <span title="My Title">`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -147,7 +149,8 @@ describe('css selector matching', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We assume that compiler will lower-case all attribute names when generating code
|
* We assume that compiler will lower-case all selectors and attribute names when generating
|
||||||
|
* code
|
||||||
*/
|
*/
|
||||||
it('should match attribute name case-sensitively', () => {
|
it('should match attribute name case-sensitively', () => {
|
||||||
expect(isMatching('span', ['foo', ''], [
|
expect(isMatching('span', ['foo', ''], [
|
||||||
@ -159,14 +162,10 @@ describe('css selector matching', () => {
|
|||||||
])).toBeFalsy(`Selector '[Foo]' should NOT match <span foo>`);
|
])).toBeFalsy(`Selector '[Foo]' should NOT match <span foo>`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match attribute values case-sensitively', () => {
|
it('should match attribute values case-insensitively', () => {
|
||||||
expect(isMatching('span', ['foo', 'Bar'], [
|
expect(isMatching('span', ['foo', 'Bar'], [
|
||||||
'', 'foo', 'Bar'
|
'', 'foo', 'bar'
|
||||||
])).toBeTruthy(`Selector '[foo="Bar"]' should match <span foo="Bar">`);
|
])).toBeTruthy(`Selector '[foo="bar"]' should match <span foo="Bar">`);
|
||||||
|
|
||||||
expect(isMatching('span', ['foo', 'Bar'], [
|
|
||||||
'', 'Foo', 'bar'
|
|
||||||
])).toBeFalsy(`Selector '[Foo="bar"]' should match <span foo="Bar">`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match class as an attribute', () => {
|
it('should match class as an attribute', () => {
|
||||||
@ -288,14 +287,13 @@ describe('css selector matching', () => {
|
|||||||
])).toBeTruthy(`Selector '.bar.foo' should match <span class="bar foo">`);
|
])).toBeTruthy(`Selector '.bar.foo' should match <span class="bar foo">`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match class name case-sensitively', () => {
|
/**
|
||||||
expect(isMatching('span', ['class', 'Foo'], [
|
* We assume that compiler will lower-case all selectors when generating code
|
||||||
'', SelectorFlags.CLASS, 'Foo'
|
*/
|
||||||
])).toBeTruthy(`Selector '.Foo' should match <span class="Foo">`);
|
it('should match class name case-insensitively', () => {
|
||||||
|
|
||||||
expect(isMatching('span', ['class', 'Foo'], [
|
expect(isMatching('span', ['class', 'Foo'], [
|
||||||
'', SelectorFlags.CLASS, 'foo'
|
'', SelectorFlags.CLASS, 'foo'
|
||||||
])).toBeFalsy(`Selector '.foo' should NOT match <span class-"Foo">`);
|
])).toBeTruthy(`Selector '.Foo' should match <span class="Foo">`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work without a class attribute', () => {
|
it('should work without a class attribute', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user