angular/packages/core/testing/src/defer.ts
Jessica Janiuk 06bbc2fc4e refactor(core): Add defer block testing fixture (#51698)
This adds a fixture for being able to access and test defer blocks.

PR Close #51698
2023-09-13 10:47:04 -07:00

90 lines
3.1 KiB
TypeScript

/**
* @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 {ɵCONTAINER_HEADER_OFFSET as CONTAINER_HEADER_OFFSET, ɵDeferBlockDetails as DeferBlockDetails, ɵDeferBlockState as DeferBlockState, ɵgetDeferBlocks as getDeferBlocks, ɵrenderDeferBlockState as renderDeferBlockState, ɵtriggerResourceLoading as triggerResourceLoading} from '@angular/core';
import type {ComponentFixture} from './component_fixture';
/**
* Represents an individual `{#defer}` block for testing purposes.
*
* @publicApi
* @developerPreview
*/
export class DeferBlockFixture {
/** @nodoc */
constructor(
private block: DeferBlockDetails, private componentFixture: ComponentFixture<unknown>) {}
/**
* Renders the specified state of the defer fixture.
* @param state the defer state to render
*/
async render(state: DeferBlockState): Promise<void> {
if (!hasStateTemplate(state, this.block)) {
const stateAsString = getDeferBlockStateNameFromEnum(state);
throw new Error(
`Tried to render this defer block in the \`${stateAsString}\` state, ` +
`but there was no \`{:${stateAsString.toLowerCase()}}\` section defined in a template.`);
}
if (state === DeferBlockState.Complete) {
await triggerResourceLoading(this.block.tDetails, this.block.lView);
}
renderDeferBlockState(state, this.block.tNode, this.block.lContainer);
this.componentFixture.detectChanges();
return this.componentFixture.whenStable();
}
/**
* Retrieves all nested child defer block fixtures
* in a given defer block.
*/
getDeferBlocks(): Promise<DeferBlockFixture[]> {
const deferBlocks: DeferBlockDetails[] = [];
// An LContainer that represents a defer block has at most 1 view, which is
// located right after an LContainer header. Get a hold of that view and inspect
// it for nested defer blocks.
const deferBlockFixtures = [];
if (this.block.lContainer.length >= CONTAINER_HEADER_OFFSET) {
const lView = this.block.lContainer[CONTAINER_HEADER_OFFSET];
getDeferBlocks(lView, deferBlocks);
for (const block of deferBlocks) {
deferBlockFixtures.push(new DeferBlockFixture(block, this.componentFixture));
}
}
return Promise.resolve(deferBlockFixtures);
}
}
function hasStateTemplate(state: DeferBlockState, block: DeferBlockDetails) {
switch (state) {
case DeferBlockState.Placeholder:
return block.tDetails.placeholderTmplIndex !== null;
case DeferBlockState.Loading:
return block.tDetails.loadingTmplIndex !== null;
case DeferBlockState.Error:
return block.tDetails.errorTmplIndex !== null;
case DeferBlockState.Complete:
return true;
default:
return false;
}
}
function getDeferBlockStateNameFromEnum(state: DeferBlockState) {
switch (state) {
case DeferBlockState.Placeholder:
return 'Placeholder';
case DeferBlockState.Loading:
return 'Loading';
case DeferBlockState.Error:
return 'Error';
default:
return 'Main';
}
}