diff --git a/adev/src/content/reference/extended-diagnostics/NG8021.md b/adev/src/content/reference/extended-diagnostics/NG8021.md
new file mode 100644
index 00000000000..629d34c9cdc
--- /dev/null
+++ b/adev/src/content/reference/extended-diagnostics/NG8021.md
@@ -0,0 +1,181 @@
+# Defer Trigger Misconfiguration
+
+This diagnostic detects unreachable or redundant triggers in `@defer` blocks.
+
+```typescript
+
+import {Component} from '@angular/core';
+
+@Component({
+ template: `
+ @defer (on immediate; on timer(500ms)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+## What's wrong with that?
+
+The diagnostic identifies several problematic patterns in defer trigger configuration that lead to:
+
+- **Unnecessary code** that never affects behavior
+- **Missed optimization opportunities** for better performance
+- **Unreachable prefetch triggers** that will never execute
+
+
+## Diagnostic warning cases
+
+This diagnostic flags the following problematic patterns:
+
+### `immediate` with prefetch triggers
+
+**Bad — prefetch never runs**
+
+```typescript
+@Component({
+ template: `
+ @defer (on immediate; prefetch on idle) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Good — remove redundant prefetch**
+
+```typescript
+@Component({
+ template: `
+ @defer (on immediate) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+### Prefetch timer not earlier than main timer
+
+**Bad — prefetch is later than main**
+
+```typescript
+@Component({
+ template: `
+ @defer (on timer(100ms); prefetch on timer(3000ms)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Bad — equal timing provides no benefit**
+
+```typescript
+@Component({
+ template: `
+ @defer (on timer(500ms); prefetch on timer(500ms)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Good — prefetch fires earlier**
+
+```typescript
+@Component({
+ template: `
+ @defer (on timer(1000ms); prefetch on timer(500ms)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+### Identical prefetch and main triggers
+
+**Bad — identical viewport trigger**
+
+```typescript
+@Component({
+ template: `
+ @defer (on viewport; prefetch on viewport) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Bad — identical interaction target**
+
+```typescript
+@Component({
+ template: `
+
+ @defer (on interaction(loadBtn); prefetch on interaction(loadBtn)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Good — remove redundant prefetch**
+
+```typescript
+@Component({
+ template: `
+
+ @defer (on interaction(loadBtn)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+**Good — use different targets for prefetch and main**
+
+```typescript
+@Component({
+ template: `
+
Hover to prefetch
+
+ @defer (on interaction(clickBtn); prefetch on hover(hoverArea)) {
+
+ }
+ `
+})
+class MyComponent {}
+```
+
+
+## Configuration requirements
+
+[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit.
+`deferTriggerMisconfiguration` has no additional requirements beyond `strictTemplates`.
+
+## What if I can't avoid this?
+
+This diagnostic can be disabled by editing the project's `tsconfig.json` file:
+
+```json
+{
+ "angularCompilerOptions": {
+ "extendedDiagnostics": {
+ "checks": {
+ "deferTriggerMisconfiguration": "suppress"
+ }
+ }
+ }
+}
+```
+
+See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.
diff --git a/adev/src/content/reference/extended-diagnostics/overview.md b/adev/src/content/reference/extended-diagnostics/overview.md
index b53c73e56c1..e70a073f7bb 100644
--- a/adev/src/content/reference/extended-diagnostics/overview.md
+++ b/adev/src/content/reference/extended-diagnostics/overview.md
@@ -25,6 +25,7 @@ Currently, Angular supports the following extended diagnostics:
| `NG8115` | [`uninvokedTrackFunction`](extended-diagnostics/NG8115) |
| `NG8116` | [`missingStructuralDirective`](extended-diagnostics/NG8116) |
| `NG8117` | [`uninvokedFunctionInTextInterpolation`](extended-diagnostics/NG8117) |
+| `NG8021` | [`deferTriggerMisconfiguration`](extended-diagnostics/NG8021) |
## Configuration
diff --git a/goldens/public-api/compiler-cli/error_code.api.md b/goldens/public-api/compiler-cli/error_code.api.md
index b12e3875c54..6a40c86a76e 100644
--- a/goldens/public-api/compiler-cli/error_code.api.md
+++ b/goldens/public-api/compiler-cli/error_code.api.md
@@ -41,6 +41,7 @@ export enum ErrorCode {
DECORATOR_UNEXPECTED = 1005,
DEFER_IMPLICIT_TRIGGER_INVALID_PLACEHOLDER = 8020,
DEFER_IMPLICIT_TRIGGER_MISSING_PLACEHOLDER = 8019,
+ DEFER_TRIGGER_MISCONFIGURATION = 8021,
DEFERRED_DEPENDENCY_IMPORTED_EAGERLY = 8014,
DEFERRED_DIRECTIVE_USED_EAGERLY = 8013,
DEFERRED_PIPE_USED_EAGERLY = 8012,
diff --git a/goldens/public-api/compiler-cli/extended_template_diagnostic_name.api.md b/goldens/public-api/compiler-cli/extended_template_diagnostic_name.api.md
index 5650f3d310c..7d3f21c23ba 100644
--- a/goldens/public-api/compiler-cli/extended_template_diagnostic_name.api.md
+++ b/goldens/public-api/compiler-cli/extended_template_diagnostic_name.api.md
@@ -9,6 +9,8 @@ export enum ExtendedTemplateDiagnosticName {
// (undocumented)
CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION = "controlFlowPreventingContentProjection",
// (undocumented)
+ DEFER_TRIGGER_MISCONFIGURATION = "deferTriggerMisconfiguration",
+ // (undocumented)
INTERPOLATED_SIGNAL_NOT_INVOKED = "interpolatedSignalNotInvoked",
// (undocumented)
INVALID_BANANA_IN_BOX = "invalidBananaInBox",
diff --git a/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts b/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
index d0a9b07fd42..7c3ffae8909 100644
--- a/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
+++ b/packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
@@ -423,6 +423,13 @@ export enum ErrorCode {
*/
DEFER_IMPLICIT_TRIGGER_INVALID_PLACEHOLDER = 8020,
+ /**
+ * Raised when an `@defer` block defines unreachable or redundant triggers.
+ * Examples: multiple main triggers, 'on immediate' together with other mains or any prefetch,
+ * prefetch timer delay that is not earlier than the main timer, or an identical prefetch
+ */
+ DEFER_TRIGGER_MISCONFIGURATION = 8021,
+
/**
* A two way binding in a template has an incorrect syntax,
* parentheses outside brackets. For example:
diff --git a/packages/compiler-cli/src/ngtsc/diagnostics/src/extended_template_diagnostic_name.ts b/packages/compiler-cli/src/ngtsc/diagnostics/src/extended_template_diagnostic_name.ts
index fb5f6f3664f..356339c9280 100644
--- a/packages/compiler-cli/src/ngtsc/diagnostics/src/extended_template_diagnostic_name.ts
+++ b/packages/compiler-cli/src/ngtsc/diagnostics/src/extended_template_diagnostic_name.ts
@@ -33,4 +33,5 @@ export enum ExtendedTemplateDiagnosticName {
UNUSED_STANDALONE_IMPORTS = 'unusedStandaloneImports',
UNPARENTHESIZED_NULLISH_COALESCING = 'unparenthesizedNullishCoalescing',
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION = 'uninvokedFunctionInTextInterpolation',
+ DEFER_TRIGGER_MISCONFIGURATION = 'deferTriggerMisconfiguration',
}
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/extended/BUILD.bazel
index 01b5bddcdb5..ddfeda8ac83 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/extended/BUILD.bazel
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/BUILD.bazel
@@ -13,6 +13,7 @@ ts_project(
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
+ "//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration",
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked",
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box",
"//packages/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_control_flow_directive",
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/BUILD.bazel
new file mode 100644
index 00000000000..3835d2f5938
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/BUILD.bazel
@@ -0,0 +1,17 @@
+load("//tools:defaults.bzl", "ts_project")
+
+ts_project(
+ name = "defer_trigger_misconfiguration",
+ srcs = ["index.ts"],
+ visibility = [
+ "//packages/compiler-cli/src/ngtsc:__subpackages__",
+ "//packages/compiler-cli/test/ngtsc:__pkg__",
+ ],
+ deps = [
+ "//:node_modules/typescript",
+ "//packages/compiler",
+ "//packages/compiler-cli/src/ngtsc/diagnostics",
+ "//packages/compiler-cli/src/ngtsc/typecheck/api",
+ "//packages/compiler-cli/src/ngtsc/typecheck/extended/api",
+ ],
+)
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/index.ts
new file mode 100644
index 00000000000..c4cb98766d2
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/defer_trigger_misconfiguration/index.ts
@@ -0,0 +1,140 @@
+/**
+ * @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.dev/license
+ */
+
+import ts from 'typescript';
+
+import {
+ TmplAstDeferredBlock,
+ TmplAstDeferredTrigger,
+ TmplAstHoverDeferredTrigger,
+ TmplAstImmediateDeferredTrigger,
+ TmplAstInteractionDeferredTrigger,
+ TmplAstTimerDeferredTrigger,
+ TmplAstViewportDeferredTrigger,
+} from '@angular/compiler';
+
+import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics';
+import {NgTemplateDiagnostic} from '../../../api';
+import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api';
+
+/**
+ * This check implements warnings for unreachable or redundant @defer triggers.
+ * Emits ErrorCode.DEFER_TRIGGER_MISCONFIGURATION with messages matching the project's
+ * expected text.
+ */
+class DeferTriggerMisconfiguration extends TemplateCheckWithVisitor {
+ override code = ErrorCode.DEFER_TRIGGER_MISCONFIGURATION as const;
+
+ override visitNode(
+ ctx: TemplateContext,
+ component: ts.ClassDeclaration,
+ node: any,
+ ): NgTemplateDiagnostic[] {
+ if (!(node instanceof TmplAstDeferredBlock)) return [];
+
+ const mainKeys = Object.keys(node.triggers) as Array;
+ const prefetchKeys = Object.keys(node.prefetchTriggers) as Array<
+ keyof typeof node.prefetchTriggers
+ >;
+
+ // Gather actual trigger objects for mains and prefetch (only defined ones)
+ const mains = mainKeys
+ .map((k) => node.triggers[k])
+ .filter((t): t is TmplAstDeferredTrigger => t !== undefined && t !== null);
+
+ const prefetches = prefetchKeys
+ .map((k) => node.prefetchTriggers[k])
+ .filter((t): t is TmplAstDeferredTrigger => t !== undefined && t !== null);
+
+ const diags: NgTemplateDiagnostic[] = [];
+
+ // 'on immediate' dominance
+ const hasImmediateMain = mains.some((t) => t instanceof TmplAstImmediateDeferredTrigger);
+ if (hasImmediateMain) {
+ if (mains.length > 1) {
+ const msg = `The 'immediate' trigger makes additional triggers redundant.`;
+ diags.push(ctx.makeTemplateDiagnostic(node.sourceSpan, msg));
+ }
+ if (prefetches.length > 0) {
+ const msg = `Prefetch triggers have no effect because 'immediate' executes earlier.`;
+ diags.push(ctx.makeTemplateDiagnostic(node.sourceSpan, msg));
+ }
+ }
+
+ // If there is exactly one main and at least one prefetch, compare them.
+ if (mains.length === 1 && prefetches.length > 0) {
+ const main = mains[0];
+
+ for (const pre of prefetches) {
+ // Timer vs Timer: warn when prefetch delay >= main delay
+ const isTimerTriggger =
+ main instanceof TmplAstTimerDeferredTrigger && pre instanceof TmplAstTimerDeferredTrigger;
+ if (isTimerTriggger) {
+ const mainDelay = main.delay;
+ const preDelay = pre.delay;
+ if (preDelay >= mainDelay) {
+ const msg = `The Prefetch 'timer(${preDelay}ms)' is not scheduled before the main 'timer(${mainDelay}ms)', so it won’t run prior to rendering. Lower the prefetch delay or remove it.`;
+ diags.push(ctx.makeTemplateDiagnostic(pre.sourceSpan ?? node.sourceSpan, msg));
+ }
+ }
+
+ // Reference-based triggers (hover/interaction/viewport): only warn if both
+ // have a reference and the references are identical. If references differ
+ // (or one is missing), the prefetch targets a different element and
+ // provides potential value.
+
+ const isHoverTrigger =
+ main instanceof TmplAstHoverDeferredTrigger && pre instanceof TmplAstHoverDeferredTrigger;
+
+ const isInteractionTrigger =
+ main instanceof TmplAstInteractionDeferredTrigger &&
+ pre instanceof TmplAstInteractionDeferredTrigger;
+
+ const isViewportTrigger =
+ main instanceof TmplAstViewportDeferredTrigger &&
+ pre instanceof TmplAstViewportDeferredTrigger;
+
+ if (isHoverTrigger || isInteractionTrigger || isViewportTrigger) {
+ const mainRef = main.reference;
+ const preRef = pre.reference;
+ if (mainRef && preRef && mainRef === preRef) {
+ const kindName = main.constructor.name.replace('DeferredTrigger', '').toLowerCase();
+ const msg = `Prefetch '${kindName}' matches the main trigger and provides no benefit. Remove the prefetch modifier.`;
+ diags.push(ctx.makeTemplateDiagnostic(pre.sourceSpan ?? node.sourceSpan, msg));
+ }
+ // otherwise, different references or missing reference => no warning
+ continue;
+ }
+
+ // Syntactic identical: same class for immediate/idle/never etc. (timers handled above)
+ if (
+ main.constructor === pre.constructor &&
+ !(main instanceof TmplAstTimerDeferredTrigger)
+ ) {
+ const kind =
+ main instanceof TmplAstImmediateDeferredTrigger
+ ? 'immediate'
+ : main.constructor.name.replace('DeferredTrigger', '').toLowerCase();
+ const msg = `Prefetch '${kind}' matches the main trigger and provides no benefit. Remove the prefetch modifier.`;
+ diags.push(ctx.makeTemplateDiagnostic(pre.sourceSpan ?? node.sourceSpan, msg));
+ }
+ }
+ }
+
+ return diags;
+ }
+}
+
+export const factory: TemplateCheckFactory<
+ ErrorCode.DEFER_TRIGGER_MISCONFIGURATION,
+ ExtendedTemplateDiagnosticName.DEFER_TRIGGER_MISCONFIGURATION
+> = {
+ code: ErrorCode.DEFER_TRIGGER_MISCONFIGURATION,
+ name: ExtendedTemplateDiagnosticName.DEFER_TRIGGER_MISCONFIGURATION,
+ create: () => new DeferTriggerMisconfiguration(),
+};
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/index.ts
index 8cac32402d3..a6676e123f1 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/extended/index.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/index.ts
@@ -24,6 +24,7 @@ import {factory as unparenthesizedNullishCoalescingFactory} from './checks/unpar
import {factory as unusedLetDeclarationFactory} from './checks/unused_let_declaration';
import {factory as uninvokedTrackFunctionFactory} from './checks/uninvoked_track_function';
import {factory as uninvokedFunctionInTextInterpolationFactory} from './checks/uninvoked_function_in_text_interpolation';
+import {factory as deferTriggerMisconfigurationFactory} from './checks/defer_trigger_misconfiguration';
export {ExtendedTemplateCheckerImpl} from './src/extended_template_checker';
@@ -46,6 +47,7 @@ export const ALL_DIAGNOSTIC_FACTORIES: readonly TemplateCheckFactory<
unparenthesizedNullishCoalescingFactory,
uninvokedTrackFunctionFactory,
uninvokedFunctionInTextInterpolationFactory,
+ deferTriggerMisconfigurationFactory,
];
export const SUPPORTED_DIAGNOSTIC_NAMES = new Set([
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/BUILD.bazel b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/BUILD.bazel
new file mode 100644
index 00000000000..31353abe7e7
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/BUILD.bazel
@@ -0,0 +1,27 @@
+load("//tools:defaults.bzl", "jasmine_test", "ts_project")
+
+ts_project(
+ name = "test_lib",
+ testonly = True,
+ srcs = ["defer_trigger_misconfiguration_spec.ts"],
+ deps = [
+ "//:node_modules/typescript",
+ "//packages/compiler",
+ "//packages/compiler-cli/src/ngtsc/core:api",
+ "//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/checks/defer_trigger_misconfiguration",
+ "//packages/compiler-cli/src/ngtsc/typecheck/testing",
+ ],
+)
+
+jasmine_test(
+ name = "test",
+ data = [
+ ":test_lib",
+ "//packages/core:npm_package",
+ ],
+)
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/defer_trigger_misconfiguration_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/defer_trigger_misconfiguration_spec.ts
new file mode 100644
index 00000000000..fc1f02d83e5
--- /dev/null
+++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/test/checks/defer_trigger_misconfiguration/defer_trigger_misconfiguration_spec.ts
@@ -0,0 +1,197 @@
+/**
+ * @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.dev/license
+ */
+
+import ts from 'typescript';
+import {ErrorCode, ExtendedTemplateDiagnosticName, ngErrorCode} from '../../../../../diagnostics';
+import {absoluteFrom, getSourceFileOrError} from '../../../../../file_system';
+import {runInEachFileSystem} from '../../../../../file_system/testing';
+import {getClass, setup} from '../../../../testing';
+import {factory as deferTriggerMisconfigurationFactory} from '../../../checks/defer_trigger_misconfiguration';
+import {ExtendedTemplateCheckerImpl} from '../../../src/extended_template_checker';
+
+runInEachFileSystem(() => {
+ describe('DeferTriggerMisconfiguration', () => {
+ it('binds the error code to its extended template diagnostic name', () => {
+ expect(deferTriggerMisconfigurationFactory.code).toBe(
+ ErrorCode.DEFER_TRIGGER_MISCONFIGURATION,
+ );
+ expect(deferTriggerMisconfigurationFactory.name).toBe(
+ ExtendedTemplateDiagnosticName.DEFER_TRIGGER_MISCONFIGURATION,
+ );
+ });
+
+ it('should emit when on immediate coexists with other mains', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on immediate; on timer(100ms) ; on viewport(ref) ) { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(1);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
+ });
+
+ it('should emit when on immediate coexists with prefetch', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on immediate; prefetch on viewport ) { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(1);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
+ });
+
+ it('should emit when prefetch timer >= main timer', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on timer(1s); prefetch on timer(2000ms)) { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(1);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
+ });
+
+ it('should emit when prefetch identical to main viewport/interaction/hover', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on viewport(ref); prefetch on viewport(ref)) { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(1);
+ expect(diags[0].category).toBe(ts.DiagnosticCategory.Warning);
+ expect(diags[0].code).toBe(ngErrorCode(ErrorCode.DEFER_TRIGGER_MISCONFIGURATION));
+ });
+
+ it('should not emit for valid prefetch earlier than main', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on timer(1s); prefetch on timer(500ms)) { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(0);
+ });
+
+ it('should not emit when main is viewport(ref) and prefetch is viewport without reference', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on viewport(ref); prefetch on viewport) { } @placeholder { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(0);
+ });
+
+ it('should not emit when main is interaction(refA) and prefetch is interaction(refB)', () => {
+ const fileName = absoluteFrom('/main.ts');
+ const {program, templateTypeChecker} = setup([
+ {
+ fileName,
+ templates: {
+ 'TestCmp': `@defer (on interaction(refA); prefetch on interaction(refB)) { } @placeholder { }`,
+ },
+ source: 'export class TestCmp { }',
+ },
+ ]);
+ const sf = getSourceFileOrError(program, fileName);
+ const component = getClass(sf, 'TestCmp');
+ const extendedTemplateChecker = new ExtendedTemplateCheckerImpl(
+ templateTypeChecker,
+ program.getTypeChecker(),
+ [deferTriggerMisconfigurationFactory],
+ {} /* options */,
+ );
+ const diags = extendedTemplateChecker.getDiagnosticsForComponent(component);
+ expect(diags.length).toBe(0);
+ });
+ });
+});