.`);
});
it('should match shallow content queries in views inserted / removed by ngIf', () => {
function IfTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
element(0, 'div', null, ['foo', '']);
}
}
/**
*
*
*
*/
const AppComponent = createComponent('app-component', function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'shallow-comp');
{ template(1, IfTemplate, 2, 0, 'div', [AttributeMarker.SelectOnly, 'ngIf', '']); }
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(1, 'ngIf', bind(ctx.showing));
}
}, 2, 1, [ShallowComp, NgIf]);
const fixture = new ComponentFixture(AppComponent);
const qList = shallowCompInstance !.foos;
expect(qList.length).toBe(0);
fixture.component.showing = true;
fixture.update();
expect(qList.length).toBe(1);
fixture.component.showing = false;
fixture.update();
expect(qList.length).toBe(0);
});
// https://stackblitz.com/edit/angular-wlenwd?file=src%2Fapp%2Fapp.component.ts
it('should support view and content queries matching the same element', () => {
/**
*
*
* class Cmpt {
* @ViewChildren('foo, bar') foos;
* }
*/
const AppComponent = createComponent(
'app-component',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['with-content', '']);
{ element(1, 'div', null, ['foo', '']); }
elementEnd();
element(3, 'div', ['id', 'after'], ['bar', '']);
}
},
5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
viewQuery(['foo', 'bar'], true);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery
>()) &&
(ctx.foos = tmp as QueryList);
}
});
const fixture = new ComponentFixture(AppComponent);
const viewQList = fixture.component.foos;
expect(viewQList.length).toBe(2);
expect(withContentInstance !.foos.length).toBe(1);
expect(viewQList.first.nativeElement).toBe(withContentInstance !.foos.first.nativeElement);
expect(viewQList.last.nativeElement.id).toBe('after');
});
it('should not report deep content query matches found above content children', () => {
/**
*
*
<-- should match content query
*
* <-- should not match content query
* class AppComponent {
* @ViewChildren('bar') bars: QueryList;
* }
*/
const AppComponent = createComponent(
'app-component',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['with-content', '']);
{ element(1, 'div', ['id', 'yes'], ['foo', '']); }
elementEnd();
element(3, 'div', null, ['foo', '']);
}
},
5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
viewQuery(['bar'], true);
}
if (rf & RenderFlags.Update) {
let tmp: any;
queryRefresh(tmp = loadViewQuery>()) &&
(ctx.bars = tmp as QueryList);
}
});
const fixture = new ComponentFixture(AppComponent);
expect(withContentInstance !.foos.length).toBe(1);
expect(withContentInstance !.foos.first.nativeElement.id).toEqual('yes');
});
it('should report results to appropriate queries where deep content queries are nested', () => {
class QueryDirective {
fooBars: any;
static ngDirectiveDef = defineDirective({
type: QueryDirective,
selectors: [['', 'query', '']],
exportAs: ['query'],
factory: () => new QueryDirective(),
contentQueries: (dirIndex: number) => {
// @ContentChildren('foo, bar, baz', {descendants: true}) fooBars:
// QueryList;
contentQuery(dirIndex, ['foo', 'bar', 'baz'], true);
},
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.fooBars = tmp);
},
});
}
let outInstance: QueryDirective;
let inInstance: QueryDirective;
const AppComponent = createComponent(
'app-component',
/**
*
*/
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', [AttributeMarker.SelectOnly, 'query'], ['out', 'query']);
{
element(2, 'span', ['id', 'foo'], ['foo', '']);
elementStart(4, 'div', [AttributeMarker.SelectOnly, 'query'], ['in', 'query']);
{ element(6, 'span', ['id', 'bar'], ['bar', '']); }
elementEnd();
element(8, 'span', ['id', 'baz'], ['baz', '']);
}
elementEnd();
}
if (rf & RenderFlags.Update) {
outInstance = load(1);
inInstance = load(5);
}
},
10, 0, [QueryDirective]);
const fixture = new ComponentFixture(AppComponent);
expect(outInstance !.fooBars.length).toBe(3);
expect(inInstance !.fooBars.length).toBe(1);
});
it('should support nested shallow content queries ', () => {
let outInstance: QueryDirective;
let inInstance: QueryDirective;
class QueryDirective {
fooBars: any;
static ngDirectiveDef = defineDirective({
type: QueryDirective,
selectors: [['', 'query', '']],
exportAs: ['query'],
factory: () => new QueryDirective(),
contentQueries: (dirIndex: number) => {
// @ContentChildren('foo, bar, baz', {descendants: true}) fooBars:
// QueryList;
contentQuery(dirIndex, ['foo'], false);
},
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.fooBars = tmp);
},
});
}
const AppComponent = createComponent(
'app-component',
/**
*
*/
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['query', ''], ['out', 'query']);
{
elementStart(2, 'div', ['query', ''], ['in', 'query', 'foo', '']);
{ element(5, 'span', ['id', 'bar'], ['foo', '']); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
outInstance = load(1);
inInstance = load(3);
}
},
7, 0, [QueryDirective]);
const fixture = new ComponentFixture(AppComponent);
expect(outInstance !.fooBars.length).toBe(1);
expect(inInstance !.fooBars.length).toBe(1);
});
it('should support nested shallow content queries across multiple component instances', () => {
let outInstance: QueryDirective;
let inInstance: QueryDirective;
class QueryDirective {
fooBars: any;
static ngDirectiveDef = defineDirective({
type: QueryDirective,
selectors: [['', 'query', '']],
exportAs: ['query'],
factory: () => new QueryDirective(),
contentQueries: (dirIndex: number) => {
// @ContentChildren('foo', {descendants: true}) fooBars:
// QueryList;
contentQuery(dirIndex, ['foo'], false);
},
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.fooBars = tmp);
},
});
}
const AppComponent = createComponent(
'app-component',
/**
*
*/
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['query', ''], ['out', 'query']);
{
elementStart(2, 'div', ['query', ''], ['in', 'query', 'foo', '']);
{ element(5, 'span', ['id', 'bar'], ['foo', '']); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
outInstance = load(1);
inInstance = load(3);
}
},
7, 0, [QueryDirective]);
const fixture1 = new ComponentFixture(AppComponent);
expect(outInstance !.fooBars.length).toBe(1);
expect(inInstance !.fooBars.length).toBe(1);
outInstance = inInstance = null !;
const fixture2 = new ComponentFixture(AppComponent);
expect(outInstance !.fooBars.length).toBe(1);
expect(inInstance !.fooBars.length).toBe(1);
});
it('should respect shallow flag on content queries when mixing deep and shallow queries',
() => {
class ShallowQueryDirective {
foos: any;
static ngDirectiveDef = defineDirective({
type: ShallowQueryDirective,
selectors: [['', 'shallow-query', '']],
exportAs: ['shallow-query'],
factory: () => new ShallowQueryDirective(),
contentQueries: (dirIndex: number) => {
// @ContentChildren('foo', {descendants: false}) foos: QueryList;
contentQuery(dirIndex, ['foo'], false);
},
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.foos = tmp);
},
});
}
class DeepQueryDirective {
foos: any;
static ngDirectiveDef = defineDirective({
type: DeepQueryDirective,
selectors: [['', 'deep-query', '']],
exportAs: ['deep-query'],
factory: () => new DeepQueryDirective(),
contentQueries: (dirIndex: number) => {
// @ContentChildren('foo', {descendants: false}) foos: QueryList;
contentQuery(dirIndex, ['foo'], true);
},
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.foos = tmp);
},
});
}
let shallowInstance: ShallowQueryDirective;
let deepInstance: DeepQueryDirective;
const AppComponent = createComponent(
'app-component',
/**
*
*/
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(
0, 'div', [AttributeMarker.SelectOnly, 'shallow-query', 'deep-query'],
['shallow', 'shallow-query', 'deep', 'deep-query']);
{
element(3, 'span', null, ['foo', '']);
elementStart(5, 'div');
{ element(6, 'span', null, ['foo', '']); }
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
shallowInstance = load(1);
deepInstance = load(2);
}
},
8, 0, [ShallowQueryDirective, DeepQueryDirective]);
const fixture = new ComponentFixture(AppComponent);
expect(shallowInstance !.foos.length).toBe(1);
expect(deepInstance !.foos.length).toBe(2);
});
});
describe('order', () => {
class TextDirective {
value !: string;
static ngDirectiveDef = defineDirective({
type: TextDirective,
selectors: [['', 'text', '']],
factory: () => new TextDirective(),
inputs: {value: 'text'}
});
}
it('should register content matches from top to bottom', () => {
let contentQueryDirective: ContentQueryDirective;
class ContentQueryDirective {
// @ContentChildren(TextDirective)
texts !: QueryList;
static ngComponentDef = defineDirective({
type: ContentQueryDirective,
selectors: [['', 'content-query', '']],
factory: () => contentQueryDirective = new ContentQueryDirective(),
contentQueries: (dirIndex: number) => { contentQuery(dirIndex, TextDirective, true); },
contentQueriesRefresh: (dirIndex: number) => {
let tmp: any;
const instance = load(dirIndex);
queryRefresh(tmp = loadContentQuery()) && (instance.texts = tmp);
}
});
}
const AppComponent = createComponent(
'app-component',
/**
*
*/
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'div', ['content-query']);
{
element(1, 'span', ['text', 'A']);
elementStart(2, 'div', ['text', 'B']);
elementStart(3, 'span', ['text', 'C']);
{ element(4, 'span', ['text', 'D']); }
elementEnd();
elementEnd();
element(5, 'span', ['text', 'E']);
}
elementEnd();
}
},
6, 0, [TextDirective, ContentQueryDirective]);
new ComponentFixture(AppComponent);
expect(contentQueryDirective !.texts.map(item => item.value)).toEqual([
'A', 'B', 'C', 'D', 'E'
]);
});
it('should register view matches from top to bottom', () => {
/**
*
*
*
*
*
*
*
*/
class ViewQueryComponent {
// @ViewChildren(TextDirective)
texts !: QueryList;
static ngComponentDef = defineComponent({
type: ViewQueryComponent,
selectors: [['view-query']],
factory: () => new ViewQueryComponent(),
template: function(rf: RenderFlags, ctx: ViewQueryComponent) {
if (rf & RenderFlags.Create) {
element(0, 'span', ['text', 'A']);
elementStart(1, 'div', ['text', 'B']);
elementStart(2, 'span', ['text', 'C']);
{ element(3, 'span', ['text', 'D']); }
elementEnd();
elementEnd();
element(4, 'span', ['text', 'E']);
}
},
consts: 5,
vars: 0,
viewQuery: function(rf: RenderFlags, ctx: ViewQueryComponent) {
let tmp: any;
if (rf & RenderFlags.Create) {
viewQuery(TextDirective, true);
}
if (rf & RenderFlags.Update) {
queryRefresh(tmp = loadViewQuery>()) &&
(ctx.texts = tmp as QueryList);
}
},
directives: [TextDirective]
});
}
const fixture = new ComponentFixture(ViewQueryComponent);
expect(fixture.component.texts.map(item => item.value)).toEqual(['A', 'B', 'C', 'D', 'E']);
});
});
});