Testing Angular Components with Slots
Content projection is a pattern in which you insert, or project, the content
you want to use inside another component. In Angular there are three types
content projection, single-slot
, multi-slot
, and conditional
.
Like props and events, slots are part of the component's public API.
Single-slot Content Projection
The most basic form of content projection is known as single-slot content
projection. This refers to creating a component into which you can project one
component using <ng-content></ng-content>
. You can learn more about
single-slot content projection at
https://angular.io/guide/content-projection#single-slot
Below is a simple ButtonComponent that is using single-slot content projection.
import { Component } from '@angular/core'
@Component({
selector: 'app-button',
template: `
<button>
<ng-content></ng-content>
</button>
`,
})
export class ButtonComponent {}
Now we pass in the content to the ButtonComponent
using content projection:
import { ButtonComponent } from './button.component'
describe('ButtonComponent', () => {
it('can project content using a ButtonComponent template', () => {
cy.mount('<app-button>Click Me</app-button>', {
declarations: [ButtonComponent],
})
cy.get('button').contains('Click Me')
})
})
Multi-slot Content Projection
An Angular component can also have multiple slots used for content projection. Each slot can specify a CSS selector that determines which content goes into that slot. This pattern is referred to as multi-slot content projection. With this pattern you must specify where you want the projected content to appear.
Below is an example of CardComponent that is using multi-slot content
projection and its corresponding component test. Notice we create a
WrapperComponent in our spec that we use to simulate passing in content to
the CardComponent
using content projection.
import { Component, OnInit } from '@angular/core'
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<ng-content select="[cardHeader]"></ng-content>
</div>
<div class="card-body">
<!--Default Slot -->
<ng-content></ng-content>
</div>
</div>
`,
})
export class CardComponent {}
import { CardComponent } from './card.component'
describe('CardComponent', () => {
it('can project content', () => {
cy.mount(
`
<app-card>
<h1 cardHeader>My Title</h1>
<p>My text goes here...</p>
</app-card>
`,
{
declarations: [CardComponent],
}
)
cy.get('h1').contains('My Title')
cy.get('p').contains('My text goes here...')
})
})