ngx-cva-test-suite
provides an extensive set of test cases, ensuring your custom controls behave as intended. Package is designed and tested to work properly with both Jest and Jasmine test runners.
It provides various configurations, that allows even the most non-standard components to be properly tested.
Among the main features:
- ensures the correct amount of calls for the
onChange
function (incorrect usage may result in extra emissions ofvalueChanges
of formControl) - ensures correct triggering of
onTouched
function (is needed fortouched
state of the control andupdateOn: 'blur'
strategy to function properly) - ensures that no extra emissions are present when control is disabled
- checks for control to be resettable using
AbstractControl.reset()
In the repository you can also find few simple CVA components, that are configured properly along with ngx-cva-test-suite
setup for them.
npm i ngx-cva-test-suite --save-dev
See config below for the details on each property.
import { runValueAccessorTests } from `ngx-cva-test-suite`;
import { ComboboxComponent } from './combobox.component';
runValueAccessorTests({
/** Component, that is being tested */
component: ComboboxComponent,
/**
* All the metadata required for this test to run.
* Under the hood calls TestBed.configureTestingModule with provided config.
*/
testModuleMetadata: {
declarations: [ComboboxComponent],
},
/** Whether component is able to track "onBlur" events separately */
supportsOnBlur: true,
/**
* CSS selector for the element, that should dispatch `blur` event.
* Required and used only if `supportsOnBlur` is set to true.
*/
nativeControlSelector: 'input.combobox-input',
/**
* Tests the correctness of an approach that is used to set value in the component,
* when the change is internal. It's optional and can be omitted by passing "null"
*/
internalValueChangeSetter: (fixture, value) => {
fixture.componentInstance.setValue(value, true);
},
/** Function to get the value of a component in a runtime. */
getComponentValue: (fixture) => fixture.componentInstance.value,
});
This type of configuration might become handy, if your CVA component relies on projected content or specific layout to function correctly. A good example of such would be a select component, that gets it's options as projected content.
import { runValueAccessorTests } from 'ngx-cva-test-suite';
import { Component, ViewChild } from '@angular/core';
import { CustomCheckboxControlValueAccessor } from './support/standard-value-accessors-directives';
@Component({
template: `
<app-select>
<app-select-option [value]="1">Opt 1</app-select-option>
<app-select-option [value]="2">Opt 2</app-select-option>
<app-select-option [value]="3">Opt 3</app-select-option>
</app-select>
`,
})
export class SelectWrapperComponent {
@ViewChild(AppSelectComponent) ctrl: AppSelectComponent;
}
runValueAccessorTests<AppSelectComponent, SelectWrapperComponent>({
// <= if host template is used, it should be marked explicitly as a type
component: AppSelectComponent, // <= using actual AppSelectComponent as a test target
testModuleMetadata: {
declarations: [SelectWrapperComponent],
imports: [AppSelectModule], // <= importing the module for app-select
},
hostTemplate: {
// specify that "AppSelectComponent" should not be tested directly
hostComponent: SelectWrapperComponent,
// specify the way to access "AppSelectComponent" from the host template
getTestingComponent: (fixture) => fixture.componentInstance.ctrl,
},
supportsOnBlur: false,
internalValueChangeSetter: (fixture, value) => {
// "setValue" is a function that is being called
// when user selects any "app-select-option"
fixture.componentInstance.ctrl.setValue(value, true);
},
getComponentValue: (fixture) => fixture.componentInstance.ctrl.value,
getValues: () => [1, 2, 3], // <= setting the same values as select options in host template
});
All the metadata required for this test to run. Under the hood calls TestBed.configureTestingModule
with provided config
Is used in a root describe
statement of the test suite.
Should be the name of the component, that is being tested. By default will use the name of testing component.
Allows to define custom wrapper template for this set of tests. Is useful if CVA cannot be tested in complete isolation.
See details below
Component, that is being tested
Function to get the value of a component in a runtime. Is used to ensure component applies provided value correctly.
Set to null
if you want to skip this check.
Example usage:
// supposed your component has "getValue()" method
getComponentValue: (fixture) => fixture.componentInstance.getValue();
This is related to the ability to track blur events in order to set emitOn: 'blur'
when used in reactive form.
If set to true, component will be tested to not call onTouched
event when value changed.
Instead of this, it will be expected to trigger this function
by html blur event using native control (see nativeControlSelector
in this config).
CSS selector for the element, that should dispatch blur
event. Required and used only if supportsOnBlur
is set to true.
Provided selected will be used to programmatically dispatch blur
event.
Example:
<input class="combobox-input" />
For the CVA with HTML as above the following should be provided:
nativeControlSelector: 'input.combobox-input';
Tests the approach that is used to set value in the component, when the change is internal (e.g. by clicking on an option of the select or typing in the input field). When value is set, "onChange" (and "onTouched" depending on the "blur" behavior) methods are expected to be invoked
Set to null
if you want to skip this check.
Example: if in your component you have something like this in html
<input (input)="onSearchChange($event.target.value) />
then provide here
internalValueChangeSetter: (fixture, value) => {
fixture.componentInstance.onSearchChange(value);
};
Gives an ability to add any additional logic for the setup process. It will be called before running each test.
Make sure you are calling done
callback after setup is finished.
After setting the value, each test waits for the given amount of time before going further with checks. Defaults to 100ms
Customizer for plain values. By default will use ['a', 'b', 'c']
This test suite applies up to 3 different values on the component. If strings are not supported in your component,
replace it with whatever is needed. It's recommended to NOT use consecutive same values (like )[1, 1, 1]
Example:
getValues: () => [1, 2, 3]
// or
getValues: () => [true, false, true]
Component will be tested for correct behavior, when FormControl
's reset()
method is called.
After simulating this call, it will check if value of the component is reset internally.
If your component is not supposed to work with null
as a value, specify what you're expecting to have as a value internally
Set this to true, if component cannot be disabled.
If set to true, ControlValueAccessor.setDisabledState()
function will not be checked for existance and correct behavior.
Test suite will automatically detect whether it's Jest or Jasmine environment. If needed, this can be overriden
List of steps to be excluded from execution. Cannot be specified along with includeSteps
List of steps to be included in execution. Cannot be specified along with excludeSteps
Wrapper, that hosts testing component. For example, to test app-select-component
the following wrapper is used
@Component({
selector: 'app-test-component-wrapper',
template: `
<app-select label="Label Value" #ctrl>
<app-select-option [value]="1" label="Opt 1"></app-select-option>
<app-select-option [value]="2" label="Opt 2"></app-select-option>
<app-select-option [value]="3" label="Opt 3"></app-select-option>
</app-select>
`,
})
class TestWrapperComponent {
@ViewChild('ctrl') ctrl: AppSelectComponent;
}
Getter for the actual component that is being tested
Using the hostComponent above, the following function should be used:
(fixture) => fixture.componentInstance.ctrl;