Angular
Table of Contents
Testing with Jest#
Jest uses jsdom and supports ShadowDOM since Version 12.2.0.
However, it doesn't support JavaScript modules as described in this
issue.
Also, it doesn't support CSSStyleSheet.replace()
, Intersection Observer
, Element.prototype.scrollTo
and others.
As a workaround we provide a polyfill as part of the @porsche-design-system/components-angular
package.
To apply the polyfill, simply import it in your setupTest.{js|ts}
file.

Certain modern browser APIs are not supported in the jsdom environment. See Unsupported APIs for more information.
Setup file
import '@porsche-design-system/components-angular/jsdom-polyfill';
Example component
import { ChangeDetectionStrategy, Component } from '@angular/core';
import type { TabsBarUpdateEventDetail } from '@porsche-design-system/components-angular';
@Component({
selector: 'single-component',
template: `
<p-tabs-bar [activeTabIndex]="tabIndex" (update)="onUpdate($event)">
<button data-testid="button1" type="button">Tab One</button>
<button data-testid="button2" type="button">Tab Two</button>
<button data-testid="button3" type="button">Tab Three</button>
</p-tabs-bar>
<div data-testid="debug">Active Tab: { tabIndex + 1 }</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SingleComponent {
tabIndex: number = 0;
onUpdate(e: CustomEvent<TabsBarUpdateEventDetail>) {
this.tabIndex = e.detail.activeTabIndex;
}
}
Test example component
import { componentsReady } from '@porsche-design-system/components-angular';
import { render } from '@testing-library/angular';
import userEvent from '@testing-library/user-event';
import '@porsche-design-system/components-angular/jsdom-polyfill';
it('should render Tabs Bar from Porsche Design System and use its events', async () => {
const { getByTestId } = await render(SingleComponent);
await componentsReady();
const debug = getByTestId('debug');
const button1 = getByTestId('button1');
const button2 = getByTestId('button2');
const button3 = getByTestId('button3');
expect(debug.innerHTML).toBe('Active Tab: 1');
await userEvent.click(button2);
expect(debug.innerHTML).toBe('Active Tab: 2');
await userEvent.click(button3);
expect(debug.innerHTML).toBe('Active Tab: 3');
await userEvent.click(button1);
expect(debug.innerHTML).toBe('Active Tab: 1');
});
More test examples can be found at the componentsReady() documentation.
Testing with Karma#
Config
Since the getInitialStyles() partial is mandatory for using Porsche Design System, the
partial needs to be included within test environments. With help of @angular-builders/custom-webpack
and its
indexTransform
option, the partial can be injected into the index.html
. See
getInitialStyles() for a manual how to integrate the partial within build
and serve
environment.
Since Karma does not use the index.html
, but instead relies on karma-context.html
the transformation of index.html
is not applicable for testing with Karma
.
There are different approaches to inject the partial into karma-context.html
:
karma.conf.js
option
customContextFile would be
easiest, but this is overridden by @angular-devkit/build-angular/plugins/karma
karma.conf.js
custom middleware which injects the getInitialStyles()
partial on the fly
manipulation of karma-context.html
via post-install
or within karma.conf.js
<!-- karma.conf.js -->
const path = require('path');
const fs = require('fs');
const { globSync } = require('glob');
const injectPartials = require('./scripts/injectPartials');
const injectPartialsIntoKarmaContextHtml = () => {
const packagePath = path.resolve(require.resolve('@angular-devkit/build-angular'), '..');
const [filePath] = globSync(packagePath + '/**/karma-context.html');
const backupFilePath = filePath.replace(/\.html$/, '-original$&');
if (fs.existsSync(backupFilePath)) {
fs.copyFileSync(backupFilePath, filePath);
fs.rmSync(backupFilePath);
}
fs.copyFileSync(filePath, backupFilePath);
const fileContent = fs.readFileSync(filePath, 'utf8');
const modifiedFileContent = injectPartials({}, fileContent);
fs.writeFileSync(filePath, modifiedFileContent);
};
injectPartialsIntoKarmaContextHtml();
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
});
}
More test examples can be found at the componentsReady() documentation.
We also provide test examples in our
sample integration project.
Unsupported APIs#
Certain modern browser APIs are not supported in the jsdom environment.
Dialog API
Affected Components: p-modal
, p-flyout
, p-flyout-multilevel
Due to the lack of native support in jsdom, the Dialog API needs to be either manually polyfilled or mocked. You can use
the available dialog-polyfill package or create a custom mock
implementation.
Example mock:
HTMLDialogElement.prototype.show = jest.fn();
HTMLDialogElement.prototype.showModal = jest.fn();
HTMLDialogElement.prototype.close = jest.fn();
Element Internals API
Affected Components: p-textarea
Current polyfills for the Element Internals API are incompatible with Stencil. Therefore, the API must be mocked within
the test setup.
Example mock:
HTMLElement.prototype.attachInternals = jest.fn(
() =>
({
setFormValue: jest.fn(),
setValidity: jest.fn(),
}) as ElementInternals
);