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...')
  })
})