/** * @license * Copyright Google LLC All Rights Reserved. * * 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 {ParseError, parseTemplate} from '@angular/compiler'; import * as e from '@angular/compiler/src/expression_parser/ast'; // e for expression AST import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST import {getTargetAtPosition, SingleNodeTarget, TargetNodeKind, TwoWayBindingContext} from '../../template_target'; import {isExpressionNode, isTemplateNode} from '../../utils'; interface ParseResult { nodes: t.Node[]; errors: ParseError[]|null; position: number; } function parse(template: string): ParseResult { const position = template.indexOf('¦'); if (position < 0) { throw new Error(`Template "${template}" does not contain the cursor`); } template = template.replace('¦', ''); const templateUrl = '/foo'; return { ...parseTemplate(template, templateUrl, { // Set `leadingTriviaChars` and `preserveWhitespaces` such that whitespace is not stripped // and fully accounted for in source spans. Without these flags the source spans can be // inaccurate. // Note: template parse options should be aligned with the `diagNodes` in // `ComponentDecoratorHandler._parseTemplate`. leadingTriviaChars: [], preserveWhitespaces: true, alwaysAttemptHtmlToR3AstConversion: true, }), position, }; } describe('getTargetAtPosition for template AST', () => { it('should locate incomplete tag', () => { const {errors, nodes, position} = parse(` { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); }); it('should locate element in closing tag', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); }); it('should locate element when cursor is at the beginning', () => { const {errors, nodes, position} = parse(`<¦div>
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); }); it('should locate element when cursor is at the end', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); }); it('should locate attribute key', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate attribute value', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); // TODO: Note that we do not have the ability to detect the RHS (yet) expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate bound attribute key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); }); it('should locate bound attribute value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should not locate bound attribute if cursor is between key and value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBeNull(); const nodeInfo = getTargetAtPosition(nodes, position)!; expect(nodeInfo).toBeNull(); }); it('should locate bound event key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundEvent); }); it('should locate bound event value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.MethodCall); }); it('should locate element children', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); expect((node as t.Element).name).toBe('span'); }); it('should locate element reference', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Reference); }); it('should locate template text attribute', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate template bound attribute key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); }); it('should locate template bound attribute value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should locate template bound attribute key in two-way binding', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context, parent} = getTargetAtPosition(nodes, position)!; expect(parent).toBeInstanceOf(t.Template); const {nodes: [boundAttribute, boundEvent]} = context as TwoWayBindingContext; expect(boundAttribute.name).toBe('foo'); expect(boundEvent.name).toBe('fooChange'); }); it('should locate template bound attribute value in two-way binding', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); // It doesn't actually matter if the template target returns the read or the write. // When the template target returns a property read, we only use the LHS downstream because the // RHS would have its own node in the AST that would have been returned instead. The LHS of the // `e.PropertyWrite` is the same as the `e.PropertyRead`. expect((node instanceof e.PropertyRead) || (node instanceof e.PropertyWrite)).toBeTrue(); expect((node as e.PropertyRead | e.PropertyWrite).name).toBe('bar'); }); it('should locate template bound event key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundEvent); }); it('should locate template bound event value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(node).toBeInstanceOf(e.MethodCall); }); it('should locate template attribute key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate template attribute value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); // TODO: Note that we do not have the ability to detect the RHS (yet) expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate template reference key via the # notation', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Reference); expect((node as t.Reference).name).toBe('foo'); }); it('should locate template reference key via the ref- notation', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Reference); expect((node as t.Reference).name).toBe('foo'); }); it('should locate template reference value via the # notation', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Reference); expect((node as t.Reference).value).toBe('exportAs'); // TODO: Note that we do not have the ability to distinguish LHS and RHS }); it('should locate template reference value via the ref- notation', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Reference); expect((node as t.Reference).value).toBe('exportAs'); // TODO: Note that we do not have the ability to distinguish LHS and RHS }); it('should locate template variable key', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Variable); }); it('should locate template variable value', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Variable); }); it('should locate template children', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); }); it('should locate ng-content', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Content); }); it('should locate ng-content attribute key', () => { const {errors, nodes, position} = parse(''); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.TextAttribute); }); it('should locate ng-content attribute value', () => { const {errors, nodes, position} = parse(''); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; // TODO: Note that we do not have the ability to detect the RHS (yet) expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.TextAttribute); }); it('should not locate implicit receiver', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should locate bound attribute key in two-way binding', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context, parent} = getTargetAtPosition(nodes, position)!; expect(parent).toBeInstanceOf(t.Element); const {nodes: [boundAttribute, boundEvent]} = context as TwoWayBindingContext; expect(boundAttribute.name).toBe('foo'); expect(boundEvent.name).toBe('fooChange'); }); it('should locate node when in value span of two-way binding', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context, parent} = getTargetAtPosition(nodes, position)!; // It doesn't actually matter if the template target returns the read or the write. // When the template target returns a property read, we only use the LHS downstream because the // RHS would have its own node in the AST that would have been returned instead. The LHS of the // `e.PropertyWrite` is the same as the `e.PropertyRead`. expect((parent instanceof t.BoundAttribute) || (parent instanceof t.BoundEvent)).toBe(true); const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect((node instanceof e.PropertyRead) || (node instanceof e.PropertyWrite)).toBeTrue(); expect((node as e.PropertyRead | e.PropertyWrite).name).toBe('bar'); }); it('should locate switch value in ICUs', () => { const {errors, nodes, position} = parse(`{sw¦itch, plural, other {text}}">`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('switch'); }); it('should locate switch value in nested ICUs', () => { const {errors, nodes, position} = parse( `{expr, plural, other { {ne¦sted, plural, =1 { {{nestedInterpolation}} }} }}">`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('nested'); }); it('should locate interpolation expressions in ICUs', () => { const {errors, nodes, position} = parse(`{expr, plural, other { {{ i¦nterpolation }} }}">`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('interpolation'); }); it('should locate interpolation expressions in nested ICUs', () => { const {errors, nodes, position} = parse( `{expr, plural, other { {nested, plural, =1 { {{n¦estedInterpolation}} }} }}">`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('nestedInterpolation'); }); }); describe('getTargetAtPosition for expression AST', () => { it('should not locate implicit receiver', () => { const {errors, nodes, position} = parse(`{{ ¦title }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('title'); }); it('should locate property read', () => { const {errors, nodes, position} = parse(`{{ ti¦tle }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('title'); }); it('should locate safe property read', () => { const {errors, nodes, position} = parse(`{{ foo?¦.bar }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.SafePropertyRead); expect((node as e.SafePropertyRead).name).toBe('bar'); }); it('should locate keyed read', () => { const {errors, nodes, position} = parse(`{{ foo['bar']¦ }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.KeyedRead); }); it('should locate property write', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyWrite); }); it('should locate keyed write', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.KeyedWrite); }); it('should locate binary', () => { const {errors, nodes, position} = parse(`{{ 1 +¦ 2 }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.Binary); }); it('should locate binding pipe with an identifier', () => { const {errors, nodes, position} = parse(`{{ title | p¦ }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.BindingPipe); }); it('should locate binding pipe without identifier', () => { const {errors, nodes, position} = parse(`{{ title | ¦ }}`); expect(errors?.length).toBe(1); expect(errors![0].toString()) .toContain( 'Unexpected end of input, expected identifier or keyword at the end of the expression'); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.BindingPipe); }); it('should locate binding pipe without identifier', () => { // TODO: We are not able to locate pipe if identifier is missing because the // parser throws an error. This case is important for autocomplete. // const {errors, nodes, position} = parse(`{{ title | ¦ }}`); // expect(errors).toBe(null); // const {context} = findNodeAtPosition(nodes, position)!; // expect(isExpressionNode(node!)).toBe(true); // expect(node).toBeInstanceOf(e.BindingPipe); }); it('should locate method call', () => { const {errors, nodes, position} = parse(`{{ title.toString(¦) }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.MethodCall); }); it('should locate safe method call', () => { const {errors, nodes, position} = parse(`{{ title?.toString(¦) }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.SafeMethodCall); }); it('should locate literal primitive in interpolation', () => { const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.LiteralPrimitive); expect((node as e.LiteralPrimitive).value).toBe('t'); }); it('should locate literal primitive in binding', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.LiteralPrimitive); expect((node as e.LiteralPrimitive).value).toBe('t'); }); it('should locate empty expression', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.EmptyExpr); }); it('should locate literal array', () => { const {errors, nodes, position} = parse(`{{ [1, 2,¦ 3] }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.LiteralArray); }); it('should locate literal map', () => { const {errors, nodes, position} = parse(`{{ { hello:¦ "world" } }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.LiteralMap); }); it('should locate conditional', () => { const {errors, nodes, position} = parse(`{{ cond ?¦ true : false }}`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.Conditional); }); }); describe('findNodeAtPosition for microsyntax expression', () => { it('should locate template key', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); }); it('should locate template value', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should locate property read next to variable in structural directive syntax', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should locate text attribute', () => { const {errors, nodes, position} = parse(`
`); // ngFor is a text attribute because the desugared form is // expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBeTrue(); expect(node).toBeInstanceOf(t.TextAttribute); expect((node as t.TextAttribute).name).toBe('ngFor'); }); it('should not locate let keyword', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBeNull(); const target = getTargetAtPosition(nodes, position)!; expect(target).toBeNull(); }); it('should locate let variable', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Variable); expect((node as t.Variable).name).toBe('item'); }); it('should locate bound attribute key', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); expect((node as t.BoundAttribute).name).toBe('ngForOf'); }); it('should locate bound attribute key when cursor is at the start', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const node = (context as SingleNodeTarget).node; expect(isTemplateNode(node)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); expect((node as t.BoundAttribute).name).toBe('ngForOf'); }); it('should locate bound attribute key for trackBy', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); expect((node as t.BoundAttribute).name).toBe('ngForTrackBy'); }); it('should locate first bound attribute when there are two', () => { // It used to be the case that all microsyntax bindings share the same // source span, so the second bound attribute would overwrite the first. // This has been fixed in pr/39036, this case is added to prevent regression. const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.BoundAttribute); expect((node as t.BoundAttribute).name).toBe('ngForOf'); }); it('should locate bound attribute value', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); expect((node as e.PropertyRead).name).toBe('items'); }); it('should locate template children', () => { const {errors, nodes, position} = parse(``); expect(errors).toBe(null); const {context, template} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Element); expect((node as t.Element).name).toBe('div'); expect(template).toBeInstanceOf(t.Template); }); it('should locate property read of variable declared within template', () => { const {errors, nodes, position} = parse(`
{{ i¦ }}
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isExpressionNode(node!)).toBe(true); expect(node).toBeInstanceOf(e.PropertyRead); }); it('should locate LHS of variable declaration', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Variable); // TODO: Currently there is no way to distinguish LHS from RHS expect((node as t.Variable).name).toBe('i'); }); it('should locate RHS of variable declaration', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; const {node} = context as SingleNodeTarget; expect(isTemplateNode(node!)).toBe(true); expect(node).toBeInstanceOf(t.Variable); // TODO: Currently there is no way to distinguish LHS from RHS expect((node as t.Variable).value).toBe('index'); }); it('should locate an element in its tag context', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; expect(context.kind).toBe(TargetNodeKind.ElementInTagContext); expect((context as SingleNodeTarget).node).toBeInstanceOf(t.Element); }); it('should locate an element in its body context', () => { const {errors, nodes, position} = parse(`
`); expect(errors).toBe(null); const {context} = getTargetAtPosition(nodes, position)!; expect(context.kind).toBe(TargetNodeKind.ElementInBodyContext); expect((context as SingleNodeTarget).node).toBeInstanceOf(t.Element); }); });