refactor(ivy): avoid circular dep with query/di/instructions (#21430)

To prepare for pending ngForOf work, the dep from instructions -> query
should be broken. This will enable a dep from di -> instructions while
avoiding a di -> instructions -> query -> di cycle.

Analyzing this cycle also uncovered another problem: the implementation
of query() breaks tree-shaking through a hard dependency on DI concepts
of TemplateRef, ElementRef, ViewContainerRef. This is fundamentally due
to how query() can query for those values without any configuration.

Instead, this fix introduces the concept by employing the strategy
pattern, and redefining QueryReadType to pass a function which will
return one of the above values. This strategy is then used for 'read'
instead of an enum in cases where special values should be read from
the DI system.

PR Close #21430
This commit is contained in:
Alex Rickabaugh 2018-01-17 09:45:40 -08:00 committed by Miško Hevery
parent c5586b7dfa
commit 6472661ae8
8 changed files with 168 additions and 147 deletions

View File

@ -10,7 +10,8 @@ import {RendererType2} from '../render/api';
import {Type} from '../type';
import {resolveRendererType2} from '../view/util';
import {componentRefresh, diPublic} from './instructions';
import {diPublic} from './di';
import {componentRefresh} from './instructions';
import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs} from './interfaces/definition';

View File

@ -17,10 +17,12 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref';
import {Type} from '../type';
import {assertPreviousIsParent, getPreviousOrParentNode} from './instructions';
import {ComponentTemplate, DirectiveDef, TypedDirectiveDef} from './interfaces/definition';
import {LInjector} from './interfaces/injector';
import {LContainerNode, LElementNode, LNodeFlags} from './interfaces/node';
import {assertNodeType} from './node_assert';
import {LContainerNode, LElementNode, LNode, LNodeFlags} from './interfaces/node';
import {QueryReadType} from './interfaces/query';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {notImplemented, stringify} from './util';
@ -87,6 +89,11 @@ export function bloomAdd(injector: LInjector, type: Type<any>): void {
}
}
export function getOrCreateNodeInjector(): LInjector {
ngDevMode && assertPreviousIsParent();
return getOrCreateNodeInjectorForNode(getPreviousOrParentNode() as LElementNode | LContainerNode);
}
/**
* Creates (or gets an existing) injector for a given element or container.
*
@ -151,6 +158,71 @@ export function diPublicInInjector(di: LInjector, def: TypedDirectiveDef<any>):
bloomAdd(di, def.type);
}
/**
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
*
* @param def The definition of the directive to be made public
*/
export function diPublic(def: TypedDirectiveDef<any>): void {
diPublicInInjector(getOrCreateNodeInjector(), def);
}
/**
* Searches for an instance of the given directive type up the injector tree and returns
* that instance if found.
*
* If not found, it will propagate up to the next parent injector until the token
* is found or the top is reached.
*
* Usage example (in factory function):
*
* class SomeDirective {
* constructor(directive: DirectiveA) {}
*
* static ngDirectiveDef = defineDirective({
* type: SomeDirective,
* factory: () => new SomeDirective(inject(DirectiveA))
* });
* }
*
* @param token The directive type to search for
* @param flags Injection flags (e.g. CheckParent)
* @returns The instance found
*/
export function inject<T>(token: Type<T>, flags?: InjectFlags): T {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
}
/**
* Creates an ElementRef and stores it on the injector.
* Or, if the ElementRef already exists, retrieves the existing ElementRef.
*
* @returns The ElementRef instance to use
*/
export function injectElementRef(): viewEngine_ElementRef {
return getOrCreateElementRef(getOrCreateNodeInjector());
}
/**
* Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already
* exists, retrieves the existing TemplateRef.
*
* @returns The TemplateRef instance to use
*/
export function injectTemplateRef<T>(): viewEngine_TemplateRef<T> {
return getOrCreateTemplateRef<T>(getOrCreateNodeInjector());
}
/**
* Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef
* already exists, retrieves the existing ViewContainerRef.
*
* @returns The ViewContainerRef instance to use
*/
export function injectViewContainerRef(): viewEngine_ViewContainerRef {
return getOrCreateContainerRef(getOrCreateNodeInjector());
}
/**
* Searches for an instance of the given directive type up the injector tree and returns
* that instance if found.
@ -300,6 +372,10 @@ export function bloomFindPossibleInjector(startInjector: LInjector, bloomBit: nu
return null;
}
export class ReadFromInjectorFn<T> {
constructor(readonly read: (injector: LInjector, node: LNode, directiveIndex?: number) => T) {}
}
/**
* Creates an ElementRef for a given node injector and stores it on the injector.
* Or, if the ElementRef already exists, retrieves the existing ElementRef.
@ -311,6 +387,31 @@ export function getOrCreateElementRef(di: LInjector): viewEngine_ElementRef {
return di.elementRef || (di.elementRef = new ElementRef(di.node.native));
}
export const QUERY_READ_TEMPLATE_REF = <QueryReadType<viewEngine_TemplateRef<any>>>(
new ReadFromInjectorFn<viewEngine_TemplateRef<any>>(
(injector: LInjector) => getOrCreateTemplateRef(injector)) as any);
export const QUERY_READ_CONTAINER_REF = <QueryReadType<viewEngine_ViewContainerRef>>(
new ReadFromInjectorFn<viewEngine_ViewContainerRef>(
(injector: LInjector) => getOrCreateContainerRef(injector)) as any);
export const QUERY_READ_ELEMENT_REF =
<QueryReadType<viewEngine_ElementRef>>(new ReadFromInjectorFn<viewEngine_ElementRef>(
(injector: LInjector) => getOrCreateElementRef(injector)) as any);
export const QUERY_READ_FROM_NODE =
(new ReadFromInjectorFn<any>((injector: LInjector, node: LNode, directiveIdx: number) => {
ngDevMode && assertNodeOfPossibleTypes(node, LNodeFlags.Container, LNodeFlags.Element);
if (directiveIdx > -1) {
return node.view.data[directiveIdx];
} else if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {
return getOrCreateElementRef(injector);
} else if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container) {
return getOrCreateTemplateRef(injector);
}
throw new Error('fail');
}) as any as QueryReadType<any>);
/** A ref to a node's native element. */
class ElementRef implements viewEngine_ElementRef {
readonly nativeElement: any;

View File

@ -8,8 +8,11 @@
import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective} from './definition';
import {InjectFlags} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
// C(Container), L(Listener)
@ -20,7 +23,6 @@ import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveD
// - lower case for closing: c(containerEnd), e(elementEnd), v(viewEnd)
// clang-format off
export {
inject, injectElementRef, injectTemplateRef, injectViewContainerRef,
LifecycleHook,
@ -57,17 +59,21 @@ export {
projection as P,
projectionDef as pD,
query as Q,
queryRefresh as qR,
text as T,
textBinding as t,
viewStart as V,
viewEnd as v,
} from './instructions';
export {
QueryList,
query as Q,
queryRefresh as qR,
} from './query';
// clang-format on
export {QueryList} from './query';
export {
ComponentDef,
ComponentTemplate,
@ -81,4 +87,3 @@ export {
defineDirective,
};
export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent};
export {InjectFlags} from './di';

View File

@ -25,12 +25,9 @@ import {assertNodeType} from './node_assert';
import {appendChild, insertChild, insertView, processProjectedNode, removeView} from './node_manipulation';
import {isNodeMatchingSelector} from './node_selector_matcher';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType, TypedDirectiveDef, TypedComponentDef} from './interfaces/definition';
import {InjectFlags, diPublicInInjector, getOrCreateNodeInjectorForNode, getOrCreateElementRef, getOrCreateTemplateRef, getOrCreateContainerRef, getOrCreateInjectable} from './di';
import {QueryList, LQuery_} from './query';
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer';
import {isDifferent, stringify} from './util';
export {queryRefresh} from './query';
/**
* Enum used by the lifecycle (l) instruction to determine which lifecycle hook is requesting
@ -331,76 +328,6 @@ export function renderComponentOrTemplate<T>(
}
}
export function getOrCreateNodeInjector(): LInjector {
ngDevMode && assertPreviousIsParent();
return getOrCreateNodeInjectorForNode(previousOrParentNode as LElementNode | LContainerNode);
}
/**
* Makes a directive public to the DI system by adding it to an injector's bloom filter.
*
* @param def The definition of the directive to be made public
*/
export function diPublic(def: TypedDirectiveDef<any>): void {
diPublicInInjector(getOrCreateNodeInjector(), def);
}
/**
* Searches for an instance of the given directive type up the injector tree and returns
* that instance if found.
*
* If not found, it will propagate up to the next parent injector until the token
* is found or the top is reached.
*
* Usage example (in factory function):
*
* class SomeDirective {
* constructor(directive: DirectiveA) {}
*
* static ngDirectiveDef = defineDirective({
* type: SomeDirective,
* factory: () => new SomeDirective(inject(DirectiveA))
* });
* }
*
* @param token The directive type to search for
* @param flags Injection flags (e.g. CheckParent)
* @returns The instance found
*/
export function inject<T>(token: Type<T>, flags?: InjectFlags): T {
return getOrCreateInjectable<T>(getOrCreateNodeInjector(), token, flags);
}
/**
* Creates an ElementRef and stores it on the injector.
* Or, if the ElementRef already exists, retrieves the existing ElementRef.
*
* @returns The ElementRef instance to use
*/
export function injectElementRef(): ElementRef {
return getOrCreateElementRef(getOrCreateNodeInjector());
}
/**
* Creates a TemplateRef and stores it on the injector. Or, if the TemplateRef already
* exists, retrieves the existing TemplateRef.
*
* @returns The TemplateRef instance to use
*/
export function injectTemplateRef<T>(): TemplateRef<T> {
return getOrCreateTemplateRef<T>(getOrCreateNodeInjector());
}
/**
* Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef
* already exists, retrieves the existing ViewContainerRef.
*
* @returns The ViewContainerRef instance to use
*/
export function injectViewContainerRef(): ViewContainerRef {
return getOrCreateContainerRef(getOrCreateNodeInjector());
}
//////////////////////////
//// Element
//////////////////////////
@ -1892,19 +1819,15 @@ function valueInData<T>(data: any[], index: number, value?: T): T {
return value !;
}
export function query<T>(
predicate: Type<any>| string[], descend?: boolean,
read?: QueryReadType | Type<T>): QueryList<T> {
ngDevMode && assertPreviousIsParent();
const queryList = new QueryList<T>();
const query = currentQuery || (currentQuery = new LQuery_());
query.track(queryList, predicate, descend, read);
return queryList;
export function getCurrentQuery(QueryType: {new (): LQuery}): LQuery {
return currentQuery || (currentQuery = new QueryType());
}
export function getPreviousOrParentNode(): LNode {
return previousOrParentNode;
}
function assertPreviousIsParent() {
export function assertPreviousIsParent() {
assertEqual(isParent, true, 'isParent');
}

View File

@ -9,6 +9,7 @@
import {QueryList} from '../../linker';
import {Type} from '../../type';
import {LInjector} from './injector';
import {LContainerNode, LNode, LViewNode} from './node';
@ -48,15 +49,10 @@ export interface LQuery {
*/
track<T>(
queryList: QueryList<T>, predicate: Type<any>|string[], descend?: boolean,
read?: QueryReadType|Type<T>): void;
read?: QueryReadType<T>|Type<T>): void;
}
/** An enum representing possible values of the "read" option for queries. */
export const enum QueryReadType {
ElementRef = 0,
ViewContainerRef = 1,
TemplateRef = 2,
}
export class QueryReadType<T> { private defeatStructuralTyping: any; }
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.

View File

@ -16,7 +16,8 @@ import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
import {Type} from '../type';
import {assertNotNull} from './assert';
import {getOrCreateContainerRef, getOrCreateElementRef, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from './di';
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
import {assertPreviousIsParent, getCurrentQuery} from './instructions';
import {DirectiveDef, TypedDirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
@ -26,6 +27,20 @@ import {assertNodeOfPossibleTypes} from './node_assert';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
export function query<T>(
predicate: Type<any>| string[], descend?: boolean,
read?: QueryReadType<T>| Type<T>): QueryList<T> {
ngDevMode && assertPreviousIsParent();
const queryList = new QueryList<T>();
const query = getCurrentQuery(LQuery_);
query.track(queryList, predicate, descend, read);
return queryList;
}
export function queryRefresh(query: QueryList<any>): boolean {
return (query as any)._refresh();
}
/**
* A predicate which determines if a given element/directive should be included in the query
*/
@ -53,7 +68,7 @@ export interface QueryPredicate<T> {
/**
* Indicates which token should be read from DI for this query.
*/
read: QueryReadType|null;
read: QueryReadType<T>|Type<T>|null;
/**
* Values which have been located.
@ -71,7 +86,7 @@ export class LQuery_ implements LQuery {
track<T>(
queryList: viewEngine_QueryList<T>, predicate: Type<T>|string[], descend?: boolean,
read?: QueryReadType): void {
read?: QueryReadType<T>|Type<T>): void {
// TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
// mutate parent.
if (descend) {
@ -152,26 +167,13 @@ function geIdxOfMatchingDirective(node: LNode, type: Type<any>): number|null {
return null;
}
function readDefaultInjectable(nodeInjector: LInjector, node: LNode): viewEngine_ElementRef|
viewEngine_TemplateRef<any>|undefined {
ngDevMode && assertNodeOfPossibleTypes(node, LNodeFlags.Container, LNodeFlags.Element);
if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) {
return getOrCreateElementRef(nodeInjector);
} else if ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Container) {
return getOrCreateTemplateRef(nodeInjector);
}
}
function readFromNodeInjector(
nodeInjector: LInjector, node: LNode, read: QueryReadType | Type<any>): any {
if (read === QueryReadType.ElementRef) {
return getOrCreateElementRef(nodeInjector);
} else if (read === QueryReadType.ViewContainerRef) {
return getOrCreateContainerRef(nodeInjector);
} else if (read === QueryReadType.TemplateRef) {
return getOrCreateTemplateRef(nodeInjector);
nodeInjector: LInjector, node: LNode, read: QueryReadType<any>| Type<any>| null,
directiveIdx: number = -1): any {
if (read instanceof ReadFromInjectorFn) {
return read.read(nodeInjector, node, directiveIdx);
} else {
const matchingIdx = geIdxOfMatchingDirective(node, read);
const matchingIdx = geIdxOfMatchingDirective(node, read as Type<any>);
if (matchingIdx !== null) {
return node.view.data[matchingIdx];
}
@ -203,17 +205,12 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
// is anything on a node matching a selector?
if (directiveIdx !== null) {
if (predicate.read !== null) {
const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read);
if (requestedRead !== null) {
predicate.values.push(requestedRead);
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
if (result !== null) {
predicate.values.push(result);
}
} else {
// is local name pointing to a directive?
if (directiveIdx > -1) {
predicate.values.push(node.view.data[directiveIdx]);
} else {
predicate.values.push(readDefaultInjectable(nodeInjector, node));
}
}
}
}
@ -224,7 +221,7 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
function createPredicate<T>(
previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
read: QueryReadType | null): QueryPredicate<T> {
read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> {
const isArray = Array.isArray(predicate);
const values = <any>[];
if ((queryList as any as QueryList_<T>)._valuesTree === null) {
@ -309,7 +306,3 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
// it can't be implemented only extended.
export type QueryList<T> = viewEngine_QueryList<T>;
export const QueryList: typeof viewEngine_QueryList = QueryList_ as any;
export function queryRefresh(query: QueryList<any>): boolean {
return (query as any as QueryList_<any>)._refresh();
}

View File

@ -8,9 +8,9 @@
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {bloomAdd, bloomFindPossibleInjector} from '../../src/render3/di';
import {bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index';
import {createLNode, createLView, enterView, getOrCreateNodeInjector, leaveView} from '../../src/render3/instructions';
import {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions';
import {LInjector} from '../../src/render3/interfaces/injector';
import {LNodeFlags} from '../../src/render3/interfaces/node';

View File

@ -5,12 +5,14 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di';
import {C, E, Q, QueryList, e, m, qR} from '../../src/render3/index';
import {QueryReadType} from '../../src/render3/interfaces/query';
import {createComponent, createDirective, renderComponent} from './render_util';
/**
* Helper function to check if a given candidate object resembles ElementRef
* @param candidate
@ -91,7 +93,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(Child, false, QueryReadType.ElementRef));
m(0, Q(Child, false, QUERY_READ_ELEMENT_REF));
elToQuery = E(1, 'div', null, [Child]);
e();
}
@ -173,7 +175,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo']));
m(0, Q(['foo'], false, QUERY_READ_ELEMENT_REF));
elToQuery = E(1, 'div', null, null, ['foo', '']);
e();
E(2, 'div');
@ -203,7 +205,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo', 'bar']));
m(0, Q(['foo', 'bar'], undefined, QUERY_READ_ELEMENT_REF));
el1ToQuery = E(1, 'div', null, null, ['foo', '']);
e();
E(2, 'div');
@ -234,7 +236,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, QueryReadType.ElementRef));
m(0, Q(['foo'], false, QUERY_READ_ELEMENT_REF));
elToQuery = E(1, 'div', null, null, ['foo', '']);
e();
E(2, 'div');
@ -260,7 +262,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, QueryReadType.ViewContainerRef));
m(0, Q(['foo'], false, QUERY_READ_CONTAINER_REF));
E(1, 'div', null, null, ['foo', '']);
e();
}
@ -283,7 +285,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, QueryReadType.ViewContainerRef));
m(0, Q(['foo'], false, QUERY_READ_CONTAINER_REF));
C(1, undefined, undefined, undefined, undefined, ['foo', '']);
}
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -306,7 +308,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, QueryReadType.ElementRef));
m(0, Q(['foo'], false, QUERY_READ_ELEMENT_REF));
C(1, undefined, undefined, undefined, undefined, ['foo', '']);
}
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -330,7 +332,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo']));
m(0, Q(['foo'], undefined, QUERY_READ_TEMPLATE_REF));
C(1, undefined, undefined, undefined, undefined, ['foo', '']);
}
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -353,7 +355,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], false, QueryReadType.TemplateRef));
m(0, Q(['foo'], false, QUERY_READ_TEMPLATE_REF));
C(1, undefined, undefined, undefined, undefined, ['foo', '']);
}
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -465,7 +467,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo'], undefined, QueryReadType.ElementRef));
m(0, Q(['foo'], undefined, QUERY_READ_ELEMENT_REF));
div = E(1, 'div', null, [Child], ['foo', 'child']);
e();
}
@ -491,7 +493,7 @@ describe('query', () => {
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
let tmp: any;
if (cm) {
m(0, Q(['foo', 'bar']));
m(0, Q(['foo', 'bar'], undefined, QUERY_READ_FROM_NODE));
div = E(1, 'div', null, [Child], ['foo', '', 'bar', 'child']);
{ childInstance = m(2); }
e();