diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/BUILD.bazel
new file mode 100644
index 0000000000..0d5b080380
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/BUILD.bazel
@@ -0,0 +1,26 @@
+load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
+
+ts_library(
+ name = "test_lib",
+ testonly = True,
+ srcs = ["spec.ts"],
+ deps = [
+ "//packages/compiler",
+ "//packages/compiler-cli/src/ngtsc/diagnostics",
+ "//packages/compiler-cli/src/ngtsc/file_system",
+ "//packages/compiler-cli/src/ngtsc/file_system/testing",
+ "//packages/compiler-cli/src/ngtsc/testing",
+ "//packages/compiler-cli/src/ngtsc/typecheck/extended",
+ "//packages/compiler-cli/src/ngtsc/typecheck/extended/src/template_checks/invalid_banana_in_box",
+ "//packages/compiler-cli/src/ngtsc/typecheck/testing",
+ "@npm//typescript",
+ ],
+)
+
+jasmine_node_test(
+ name = "test",
+ bootstrap = ["//tools/testing:node_no_angular_es5"],
+ deps = [
+ ":test_lib",
+ ],
+)
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/spec.ts
new file mode 100644
index 0000000000..4561fd166a
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/template_checks/invalid_banana_in_box/spec.ts
@@ -0,0 +1,103 @@
+/**
+ * @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 * as ts from 'typescript';
+import {ErrorCode} from '../../../../../diagnostics';
+import {absoluteFrom, getSourceFileOrError} from '../../../../../file_system';
+import {runInEachFileSystem} from '../../../../../file_system/testing';
+import {getSourceCodeForDiagnostic} from '../../../../../testing';
+import {getClass, setup} from '../../../../testing';
+import {getExtendedTemplateDiagnosticsForComponent} from '../../../src/template_checker';
+import {InvalidBananaInBoxCheck} from '../../../src/template_checks/invalid_banana_in_box/index';
+
+runInEachFileSystem(() => {
+ describe('TemplateChecks', () => {
+ it('should produce invalid banana in a box warning', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([{
+ fileName,
+ templates: {
+ 'TestCmp': '
',
+ },
+ source: 'export class TestCmp { var1: string = "text"; }'
+ }]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const diags = getExtendedTemplateDiagnosticsForComponent(
+ component, templateTypeChecker, program.getTypeChecker(),
+ [new InvalidBananaInBoxCheck()]);
+ expect(diags.length).toBe(1);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ErrorCode.INVALID_BANANA_IN_BOX);
+ expect(getSourceCodeForDiagnostic(diags[0])).toBe('([input])="var1"');
+ });
+
+ it('should not produce invalid banana in a box warning if written correctly', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([{
+ fileName,
+ templates: {
+ 'TestCmp': '
',
+ },
+ source: 'export class TestCmp { var1: string = "text"; }'
+ }]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const diags = getExtendedTemplateDiagnosticsForComponent(
+ component, templateTypeChecker, program.getTypeChecker(),
+ [new InvalidBananaInBoxCheck()]);
+ expect(diags.length).toBe(0);
+ });
+
+ it('should not produce invalid banana in a box warning with bracket in the middle of the name',
+ () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([{
+ fileName,
+ templates: {
+ 'TestCmp': '
',
+ },
+ source: 'export class TestCmp { var1: string = "text"; }'
+ }]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const diags = getExtendedTemplateDiagnosticsForComponent(
+ component, templateTypeChecker, program.getTypeChecker(),
+ [new InvalidBananaInBoxCheck()]);
+ expect(diags.length).toBe(0);
+ });
+
+ it('should produce invalid banana in a box warnings for *ngIf and ng-template', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([{
+ fileName,
+ templates: {
+ 'TestCmp': `
+
+
Content to render when condition is false.
+
`,
+ },
+ source: `export class TestCmp {
+ var1: string = "text";
+ }`
+ }]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const diags = getExtendedTemplateDiagnosticsForComponent(
+ component, templateTypeChecker, program.getTypeChecker(),
+ [new InvalidBananaInBoxCheck()]);
+ expect(diags.length).toBe(2);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ErrorCode.INVALID_BANANA_IN_BOX);
+ expect(getSourceCodeForDiagnostic(diags[0])).toBe('([foo])="var1"');
+ expect(diags[1].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[1].code).toBe(ErrorCode.INVALID_BANANA_IN_BOX);
+ expect(getSourceCodeForDiagnostic(diags[1])).toBe('([bar])="var1"');
+ });
+ });
+});