Skip to content

Commit

Permalink
fix(core): DeferBlockFixture.render should not wait for stability (#5…
Browse files Browse the repository at this point in the history
…5271)

The `DeferBlockFixture.render` function should not await the
`whenStable` promise of the fixture. This does not allow developers to
test any intermediate states that might occur between rendering the
initial content and the full app stability.

fixes #55235

PR Close #55271
  • Loading branch information
atscott authored and AndrewKushnir committed Apr 23, 2024
1 parent 9cb003a commit 826861b
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 5 deletions.
8 changes: 5 additions & 3 deletions packages/core/src/defer/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,15 +696,16 @@ export function triggerPrefetching(tDetails: TDeferBlockDetails, lView: LView, t
* @param tDetails Static information about this defer block.
* @param lView LView of a host view.
*/
export function triggerResourceLoading(tDetails: TDeferBlockDetails, lView: LView, tNode: TNode) {
export function triggerResourceLoading(
tDetails: TDeferBlockDetails, lView: LView, tNode: TNode): Promise<unknown> {
const injector = lView[INJECTOR]!;
const tView = lView[TVIEW];

if (tDetails.loadingState !== DeferDependenciesLoadingState.NOT_STARTED) {
// If the loading status is different from initial one, it means that
// the loading of dependencies is in progress and there is nothing to do
// in this function. All details can be obtained from the `tDetails` object.
return;
return tDetails.loadingPromise ?? Promise.resolve();
}

const lDetails = getLDeferBlockDetails(lView, tNode);
Expand Down Expand Up @@ -741,7 +742,7 @@ export function triggerResourceLoading(tDetails: TDeferBlockDetails, lView: LVie
tDetails.loadingState = DeferDependenciesLoadingState.COMPLETE;
pendingTasks.remove(taskId);
});
return;
return tDetails.loadingPromise;
}

// Start downloading of defer block dependencies.
Expand Down Expand Up @@ -807,6 +808,7 @@ export function triggerResourceLoading(tDetails: TDeferBlockDetails, lView: LVie
}
}
});
return tDetails.loadingPromise;
}

/** Utility function to render placeholder content (if present) */
Expand Down
38 changes: 37 additions & 1 deletion packages/core/test/defer_fixture_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
import {Component, PLATFORM_ID} from '@angular/core';
import {Component, PLATFORM_ID, ɵPendingTasks as PendingTasks} from '@angular/core';
import {DeferBlockBehavior, DeferBlockState, TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';

Expand Down Expand Up @@ -190,6 +190,42 @@ describe('DeferFixture', () => {
expect(el.querySelector('.more')).toBeDefined();
});

it('should not wait forever if application is unstable for a long time', async () => {
@Component({
selector: 'defer-comp',
standalone: true,
imports: [SecondDeferredComp],
template: `
<div>
@defer (on immediate) {
<second-deferred-comp />
}
</div>
`
})
class DeferComp {
constructor(taskService: PendingTasks) {
// Add a task and never remove it. Keeps application unstable forever
taskService.add();
}
}

TestBed.configureTestingModule({
imports: [
DeferComp,
SecondDeferredComp,
],
providers: COMMON_PROVIDERS,
deferBlockBehavior: DeferBlockBehavior.Manual,
});

const componentFixture = TestBed.createComponent(DeferComp);
const deferBlock = (await componentFixture.getDeferBlocks())[0];
const el = componentFixture.nativeElement as HTMLElement;
await deferBlock.render(DeferBlockState.Complete);
expect(el.querySelector('.more')).toBeDefined();
});

it('should work with templates that have local refs', async () => {
@Component({
selector: 'defer-comp',
Expand Down
1 change: 0 additions & 1 deletion packages/core/testing/src/defer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class DeferBlockFixture {
const skipTimerScheduling = true;
renderDeferBlockState(state, this.block.tNode, this.block.lContainer, skipTimerScheduling);
this.componentFixture.detectChanges();
return this.componentFixture.whenStable();
}

/**
Expand Down

0 comments on commit 826861b

Please sign in to comment.