From 2aa1b404c2ba00f6a92e13ddbc3a65b5f5037987 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sun, 22 Oct 2023 15:47:16 -0700 Subject: [PATCH] refactor(core): ignore `after` and `minimum` when transition between states in tests This commit updates the logic to ignore `after` and `minimum` conditions when `DeferBlockFixture.render` method is used in tests. Resolves #52313. --- packages/core/src/defer/instructions.ts | 14 +++++--- packages/core/test/defer_fixture_spec.ts | 42 ++++++++++++++++++++++++ packages/core/testing/src/defer.ts | 5 ++- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/packages/core/src/defer/instructions.ts b/packages/core/src/defer/instructions.ts index f6d4d27564fee7..16badfddb002de 100644 --- a/packages/core/src/defer/instructions.ts +++ b/packages/core/src/defer/instructions.ts @@ -430,9 +430,14 @@ function scheduleDelayedPrefetching( * @param newState New state that should be applied to the defer block. * @param tNode TNode that represents a defer block. * @param lContainer Represents an instance of a defer block. + * @param skipTimerScheduling Indicates that `@loading` and `@placeholder` block + * should be rendered immediately, even if they have `after` or `minimum` config + * options setup. This flag to needed for testing APIs to transition defer block + * between states via `DeferFixture.render` method. */ export function renderDeferBlockState( - newState: DeferBlockState, tNode: TNode, lContainer: LContainer): void { + newState: DeferBlockState, tNode: TNode, lContainer: LContainer, + skipTimerScheduling = false): void { const hostLView = lContainer[PARENT]; const hostTView = hostLView[TVIEW]; @@ -452,9 +457,10 @@ export function renderDeferBlockState( if (isValidStateChange(currentState, newState) && isValidStateChange(lDetails[NEXT_DEFER_BLOCK_STATE] ?? -1, newState)) { const tDetails = getTDeferBlockDetails(hostTView, tNode); - const needsScheduling = getLoadingBlockAfter(tDetails) !== null || - getMinimumDurationForState(tDetails, DeferBlockState.Loading) !== null || - getMinimumDurationForState(tDetails, DeferBlockState.Placeholder); + const needsScheduling = !skipTimerScheduling && + (getLoadingBlockAfter(tDetails) !== null || + getMinimumDurationForState(tDetails, DeferBlockState.Loading) !== null || + getMinimumDurationForState(tDetails, DeferBlockState.Placeholder)); if (ngDevMode && needsScheduling) { assertDefined( diff --git a/packages/core/test/defer_fixture_spec.ts b/packages/core/test/defer_fixture_spec.ts index ae29750d196b32..b5ce052a89dc8d 100644 --- a/packages/core/test/defer_fixture_spec.ts +++ b/packages/core/test/defer_fixture_spec.ts @@ -338,6 +338,48 @@ describe('DeferFixture', () => { } }); + it('should transition between states when `after` and `minimum` are used', async () => { + @Component({ + selector: 'defer-comp', + standalone: true, + imports: [SecondDeferredComp], + template: ` +
+ @defer (on immediate) { + Main content + } @loading (after 1s) { + Loading + } @placeholder (minimum 2s) { + Placeholder + } +
+ ` + }) + class DeferComp { + } + + TestBed.configureTestingModule({ + imports: [ + DeferComp, + SecondDeferredComp, + ], + providers: COMMON_PROVIDERS, + deferBlockBehavior: DeferBlockBehavior.Manual, + }); + + const componentFixture = TestBed.createComponent(DeferComp); + const deferBlock = (await componentFixture.getDeferBlocks())[0]; + + await deferBlock.render(DeferBlockState.Placeholder); + expect(componentFixture.nativeElement.outerHTML).toContain('Placeholder'); + + await deferBlock.render(DeferBlockState.Loading); + expect(componentFixture.nativeElement.outerHTML).toContain('Loading'); + + await deferBlock.render(DeferBlockState.Complete); + expect(componentFixture.nativeElement.outerHTML).toContain('Main'); + }); + it('should get child defer blocks', async () => { @Component({ selector: 'deferred-comp', diff --git a/packages/core/testing/src/defer.ts b/packages/core/testing/src/defer.ts index 2030276133e94d..055c51b84972cb 100644 --- a/packages/core/testing/src/defer.ts +++ b/packages/core/testing/src/defer.ts @@ -35,7 +35,10 @@ export class DeferBlockFixture { if (state === DeferBlockState.Complete) { await triggerResourceLoading(this.block.tDetails, this.block.lView); } - renderDeferBlockState(state, this.block.tNode, this.block.lContainer); + // If the `render` method is used explicitly - skip timer-based scheduling for + // `@placeholder` and `@loading` blocks and render them immediately. + const skipTimerScheduling = true; + renderDeferBlockState(state, this.block.tNode, this.block.lContainer, skipTimerScheduling); this.componentFixture.detectChanges(); return this.componentFixture.whenStable(); }