I don't know how to write unit-test for this directive. Can you help me?
import { Directive, ElementRef, Inject, Input, Renderer2 } from enter code here'#angular/core';
#Directive({
// tslint:disable-next-line:directive-selector
selector: '[data-cy]'
})
export class DataCyDirective {
#Input('data-cy') dataCy: string;
constructor(
private el: ElementRef,
private renderer: Renderer2,
#Inject('production') isProd: boolean
) {
if (isProd) {
renderer.removeAttribute(el.nativeElement, 'data-cy');
}
}
}
I'm found a solution
#Component({
template: `
<span id="expected" data-cy="elementCy"></span>
`
})
class DataCyDirectiveTestComponent implements OnInit {
constructor(#Inject('production') prod: boolean) {}
}
describe('DataCyDirective test version', () => {
let component: DataCyDirectiveTestComponent;
let fixture: ComponentFixture<DataCyDirectiveTestComponent>;
configureTestSuite();
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DataCyDirectiveTestComponent, DataCyDirective],
providers: [{ provide: 'production', useValue: false }]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DataCyDirectiveTestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should NOT delete the data-cy attribute if production is not enabled', () => {
const el: ElementRef<HTMLElement> = fixture.debugElement.query(By.css('#expected'));
expect(el.nativeElement.getAttribute('data-cy')).not.toBeNull();
});
});
Related
I'm not sure how can I use a custom component if it's wrapper under another component.
Like:
ComponentA_withForm
|
--ComponentA1_withWrapperOfCustomInput
|
--ComponentA11_withCustomInput
if I have a structure like this:
ComponentA_withForm
|
--ComponentA11_withCustomInput
Everything's fine
But for my case (tons of async data) I need a wrapper... Is it possible somehow to do this?
Here is my fiddle code:
ComponentA:
import { Component } from '#angular/core';
import { FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
form = this.fb.group({
someInput: [],
});
get formVal() {
return this.form.getRawValue();
}
constructor(private fb: FormBuilder) { }
}
ComponentA1:
import { Component } from '#angular/core';
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input></custom-input>',
})
export class CustomInputWrapperComponent {
constructor() { }
}
ComponentA11:
import { Component, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'custom-input',
template: `Hey there! <button (click)="inc()">Value: {{ value }}</button>`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true,
}],
})
export class CustomInputComponent implements ControlValueAccessor {
private value = 0;
writeValue(value: number): void {
this.value = value;
}
registerOnChange(fn: (_: any) => void): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: any): void {
}
inc() {
this.value = this.value + 1;
this.onChangeFn(this.value);
}
onChangeFn = (_: any) => { };
}
And here I have a working sample:
https://stackblitz.com/edit/angular-qmrj3a
so: basically removing & refactoring code not to use CustomInputWrapperComponent makes my code working. But I need this wrapper and I'm not sure how to pass formControlName then.
I don't want a dirty solution with passing parent formGroup :)
Since you don't want a dirty solution ;) , you could just implement ControlValueAccessor in the CustomInputWrapperComponent also. That way any change in the parent will be reflected in the child, any change in the child will be reflected in the parent as well with just few lines of code.
Wrapper Component
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="value"></custom-input>',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputWrapperComponent),
multi: true,
}]
})
export class CustomInputWrapperComponent implements AfterViewInit, ControlValueAccessor {
public value = new FormControl();
constructor() { }
ngAfterViewInit() {
this.value.valueChanges.subscribe((x) => {
this.onChangeFn(x);
});
}
writeValue(value: number): void {
this.value.setValue(value);
}
registerOnChange(fn: (_: any) => void): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: any): void {
}
onChangeFn = (_: any) => { };
}
Parent Template
<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>
I have made a stackbitz demo here - https://stackblitz.com/edit/angular-csaxcz
you cannot use formControlName on custom-input-wrapper because it doesn't implement ControlValueAccessor. implementing ControlValueAccessor on custom-input-wrapper might be a solution but it seems to be overkill. Instead pass the control from formGroup to custom-input-wrapper as an #Input() and pass the inputed formControl to custom-input
app.component
#Component({
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper [formCtrl]="form.get('someInput')"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
form = this.fb.group({
someInput: [],
});
get formVal() {
return this.form.getRawValue();
}
constructor(private fb: FormBuilder) { }
}
custom-input-wrapper.component
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="formCtrl"></custom-input>',
})
export class CustomInputWrapperComponent {
#Input() formCtrl: AbstractControl;
constructor() { }
}
here is a working demo https://stackblitz.com/edit/angular-3lrfqv
I am writing tests for a component that is initialized dynamically as a modal (entryComponent). Inputs for this component are retrieved via injector.
I need a way to provide these inputs in my component creation step in beforeEach.
this.modalService.create({
component: sampleComponent, inputs: {
test: 'testMsg'
}
});
SampleComponent:
#Modal()
#Component({
selector: 'sample-component',
templateUrl: './sample.component.html',
styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements OnInit {
test: string;
constructor(private injector: Injector) {
}
ngOnInit() {
this.test= this.injector.get('test');
}
}
Test for sampleComponent:
describe('sampleComponent', () => {
let component: SampleComponent;
let fixture: ComponentFixture<SampleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SampleComponent],
imports: [
ModalModule,
BrowserAnimationsModule,
],
providers: [
ModalService,
]
})
.compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(SampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.ngOnInit();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
test fails with:
Error: StaticInjectorError(DynamicTestModule)[test]:
StaticInjectorError(Platform: core)[test]:
NullInjectorError: No provider for test!
How do provide value for 'test' in this case?
In providers, provide values for the injected values that component is expecting:
providers: [
{ provide: 'test', useValue: 'valueOfTest' }
]
i'm having problems testing the logic inside ActivatedRoute queryParams subscription.
constructor(private router: Router, private route: ActivatedRoute, private auth: AuthService) {}
ngOnInit(): void {
this.route.queryParams.subscribe((params:any) => {
if(params['data']) {
this.handle(params['data']);
} else {
if (this.auth.isAuthenticated()) {
this.router.navigate(['/home']);
}
}
});
}
I would like to test:
If this.handle() is triggered when mocked params['data'] is supplied
If there is no params and this.auth.isAuthenticated() returns true that this.router.navigate is called
I have tried multiple things and i'm running out of ideas.
My test file:
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: jasmine.createSpy('queryParams')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
providers: [
{ provide: Router, useValue: mockService },
{ provide: ActivatedRoute, useValue: mockService },
{ provide: AuthService, useValue: mockService }
]
}).compileComponents();
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
mockService.navigate.calls.reset();
}));
it('should create the test component', () => {
expect(component).toBeTruthy();
});
it('should navigate away when authenticated', () => {
mockService.isAuthenticated.and.returnValue(true);
mockService.queryParams.and.callFake((data, params) => new Observable(o => o.next({ params: {} })));
component.ngOnInit();
expect(mockService.navigate).toHaveBeenCalledWith(['/home']);
});
});
But with that i get TypeError: this.route.queryParams.subscribe is not a function. I know that mockService.isAuthenticated.and.returnValue(true); is working correctly because before using subscription to params i had only this if statement inside ngOnInit().
I have tried to change the mockService to:
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: {
subscribe: jasmine.create('subscribe')
}
};
I also tried with:
const mockService = {
navigate: jasmine.createSpy('navigate'),
isAuthenticated: jasmine.createSpy('isAuthenticated'),
queryParams: {
subscribe: Observable.of({ params: {} })
}
};
But no success, for those last two i get Expected spy navigate to have been called with [ [ '/home' ] ] but it was never called.
So does someone know how to correctly test logic inside querParams subscription?
You can use rxjs observable in useValue to mock it.
const router = {
navigate: jasmine.createSpy('navigate')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([]),
RouterTestingModule,
PlanPrimengModule
],
declarations: [YourComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
provideMockStore(),
{
provide: ActivatedRoute,
useValue: {
queryParams: of({
param1: "value1",
param1: "value2"
})
}
},
{ provide: Router, useValue: router }
]
}).compileComponents(); }));
I can't say if it is too late to answer this or not, but maybe it will help new googlers...
I manage to solve that this way:
class ActivatedRouteMock {
queryParams = new Observable(observer => {
const urlParams = {
param1: 'some',
param2: 'params'
}
observer.next(urlParams);
observer.complete();
});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
HttpClient,
HttpHandler,
ControlContainer,
{
provide: ActivatedRoute,
useClass: ActivatedRouteMock
}
]
}).compileComponents();
injector = getTestBed();
httpMock = injector.get(HttpTestingController);
}));
That allows you to assert the logic inside your .subscribe(() => {}) method..
Hope it hepls
I'm trying to test pieces of jquery code that are set on ngOnInit() of my angular app.These code adds and removes certain classes based on conditions that are identified using jquery functions.
Below is my component:
import { Component, OnInit, AfterContentInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() {
if ($('#js-drawer') !== undefined && $('#js-drawer').hasClass('pxh-drawer--narrow#lg')) {
$('#gf-header').removeClass('pxh-view-header--animate-narrow pxh-view-header--narrow#lg');
} else if ($('#js-drawer') !== undefined && $('#js-drawer').hasClass('pxh-drawer--wide#lg')) {
$('#gf-header').addClass('pxh-view-header--animate-narrow pxh-view-header--narrow#lg');
}
}
}
and below is the test case that I tried.
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
declare var $;
fdescribe('AppComponent Test', () => {
let component: any;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
TestBed.configureTestingModule({
declarations: [AppComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('test onInit function', () => {
const case1 = $('#js-drawer');
const case2 = $('#js-drawer').hasClass('pxh-drawer--narrow#lg');
spyOn($('#gf-header'), 'removeClass');
component.ngOnInit();
if (case1 && case2) {
expect($('#gf-header')).toHaveBeenCalledWith('removeClass');
}
});
});
Do let me know if this is the right way to test..Thanks
I have sample TestComp with ngOnInit and ngOnDestroy methods and ActivatedRoute subscription.
#Component({
selector: 'test',
template: '',
})
export class TestComp implements OnInit, OnDestroy {
constructor (
private route: ActivatedRoute
) {
}
ngOnInit() {
this.subscription = this.route.data
.subscribe(data => {
console.log('data', data.name);
})
;
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
I am getting "Cannot read property 'unsubscribe' of undefined" when I call ngOnDestroy method from spec file (or when I am running multiple tests).
My spec file:
describe('TestComp', () => {
let comp: TestComp;
let fixture: ComponentFixture<TestComp>;
beforeEach(async(() => {
TestBed
.configureTestingModule({
declarations: [TestComp],
imports: [RouterTestingModule],
providers: [
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
name: 'Stepan'
})
}
}
}
// { //Also tried this
// provide: ActivatedRoute,
// useValue: {
// params: Observable.of({name: 'Stepan'})
// }
// }
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestComp);
fixture.detectChanges();
})
}));
it('TestComp successfully initialized', () => {
fixture.componentInstance.ngOnInit();
expect(fixture.componentInstance).toBeDefined()
fixture.componentInstance.ngOnDestroy();
});
});
I am passing ActivatedRoute value based on answers here, but I am getting error. So my question is - what should I pass as ActivatedRoute to make it possible to subscribe and unsubscribe?
Example Plunker.
Just use observer when mock your service:
{
provide: ActivatedRoute,
useValue: { data: Observable.of({ name: 'Stepan' }) }
}
You should import Subscription first.
import { Subscription } from 'rxjs/Subscription';