Unit testing dialog componentInstance event emitter in entry component - javascript

I have an entry Dialog component(EntryDialog) and an actual Dialog component(MyDialog). I have a subscriber in entry component which is subscribing to an event emitter in my main dialog component. Struggling with testing this below part, would appreciate your help.
Part to test
this.dialogRef.componentInstance.myEventEmitter.subscribe((type: string) => {
if (type) {
this.assignSample(type);
}
});
Entry Component
#Component({
selector: 'app-entry-dialog',
})
#Component({template: ''})
export class EntryDialog {
dialogRef: MatDialogRef<MyDialog>;
constructor(
private readonly dialog: MatDialog,
) {
this.dialogRef = this.dialog.open(MyDialog, {
data: someData,
disableClose: true,
});
this.dialogRef.componentInstance.myEventEmitter.subscribe((type: string) => {
if (type) {
this.assignSample(type);
}
});
}
private assignSample(type: string) {
// some code here
}
Main Dialog Component
#Component({
selector: 'app-my-dialog',
templateUrl: './my_dialog.ng.html',
})
export class MyDialog {
#Output() myEventEmitter = new EventEmitter<string>(true);
constructor(
#Inject(MAT_DIALOG_DATA) readonly sample: string,
public dialogRef: MatDialogRef<MyDialog>,
) {
merge(
this.dialogRef.backdropClick(),
this.dialogRef.keydownEvents().pipe(filter(
(keyboardEvent: KeyboardEvent) => keyboardEvent.key === 'Escape')))
.pipe(take(1))
.subscribe(() => {
this.dialogRef.close();
});
}
emitEvent() {
this.myEventEmitter.emit("data");
this.dialogRef.close();
}

you can use a mock for it, and you don't need even TestBed here.
it('', () => {
// creating mocked dependencies
const mockDialogRef: any = {
componentInstance: {
myEventEmitter: new Subject(),
},
};
const mockMatDialog: any = {
open: jasmine.createSpy(),
};
mockMatDialog.open.and.returnValue(mockDialogRef);
// action
const instance = new EntryDialog(mockMatDialog);
// checking open call
expect(mockMatDialog.open).toHaveBeenCalledWith(MyDialog, {
data: someData,
disableClose: true,
});
mockDialogRef.componentInstance.myEventEmitter.next('typeToTest');
// assertions
});

Related

How to write testcase for a method in a component that dispatches an action and calls an selector using Jasmine in Angular?

So I have made a component that takes input from parent component and emits an string[] output to its parent component.
So within this component there is an method GetDataKey() which takes an string input and dispatches an action which updates the store with some values. And then an selector is called which sends only the string[] data from store and then we assign it to 'ResultData' variable. And after that we emit the 'ResultData' variable.
export interface OptionArrayinterface {
options: string[]
}
searchable-dropdown.component.ts
export class SearchableDropdownComponent implements OnInit, OnChanges {
#Input() searchKey! : string
#Output() searchResult = new EventEmitter()
constructor(private store : Store< { GetOptionsStore : OptionArrayinterface }>) { }
ngOnInit(): void {
this.GetDataKey(this.searchKey)
}
ngOnChanges(changes: SimpleChanges): void {
this.GetDataKey(this.searchKey)
}
ResultData : string[] = []
GetDataKey(SearchKey : string)
{
this.store.dispatch(GetOptionsKeyAction( { searchkey : SearchKey } ))
this.store.select(GetOptionsSelector).subscribe(
(res) => {
this.ResultData = res
}
)
this.searchResult.emit(this.ResultData)
}
}
searchable-dropdown.component.spec.ts
describe('SearchableDropdownComponent', () => {
let component: SearchableDropdownComponent;
let fixture: ComponentFixture<SearchableDropdownComponent>;
const initialState : OptionArrayinterface = {
options: [
"abc",
"xyz",
]
}
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
provideMockStore( { initialState } )
],
declarations: [ SearchableDropdownComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SearchableDropdownComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('testing GetDataKey(SearchKey : string)', () => {
// let resultdata : string[] = []
// expect(component.ResultData).toEqual(resultdata)
})
});
So I was confused about how I should tackle writing testcases for 'GetDataKey()' method since within the method there is an action and an selector that is called.
Any help or advice would be appreciated.

Angular: How to kinda refresh the ngOnInit method

I have a sidebar with different redirects of specific products categories, when these buttons are clicked it redirects to a component that gets the URL params and makes a consult to service and retrieves the data of that specific category, the thing is, when a click it the first time, it works, but the second time it does not, it only changes the URL but does not refresh the data
sidebar.component.html
<div class="list-group">
<a [routerLink]="['products/category']" [queryParams]="{name:category.name}" class="list-group-item"
*ngFor="let category of categories">{{category.name}}</a>
</div>
And the component that makes the magic
export class ViewAllProductsByCategoryComponent implements OnInit {
searchCategory: any;
products: Product;
constructor(private activatedRoute: ActivatedRoute, private productsService: ProductsService) {
}
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(res => {
this.searchCategory = res.name;
});
this.productsService.searchCategoryProducts(this.searchCategory).subscribe(res => {
this.products = res;
console.log(this.products);
});
}
}
So, how do I refresh the data?
Angular by default doesn't re-initialize an already loaded component.
But there is a way to bypass that feature:
let newLocation = `/pathName/5110`;
// override default re use strategy
this.router
.routeReuseStrategy
.shouldReuseRoute = function () {
return false;
};
this.router
.navigateByUrl(newLocation)
.then(
(worked) => {
// Works only because we hooked
// routeReuseStrategy.shouldReuseRoute
// and explicitly told it don't reuse
// route which forces a reload.
// Otherwise; the url will change but new
// data will not display!
},
(error) => {
debugger;
}
);
Just set the .shouldReuseRoute function to return false, that way the component will reload.
Here's more detail on that topic.
https://dev.to/jwp/angular-s-naviation-challenges-20i2
You can also configure the router to reuse the route.
I've modified a bit john's answer, this is how I fixed it
export class ViewAllProductsByCategoryComponent implements OnInit, OnDestroy {
searchCategory: any;
products: Product;
mySubscription: any;
constructor(private activatedRoute: ActivatedRoute,
private productsService: ProductsService,
private router: Router,
) {
this.router.routeReuseStrategy.shouldReuseRoute = () => {
return false;
};
this.mySubscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.router.navigated = false;
}
});
}
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(res => {
this.searchCategory = res.name;
console.log(this.searchCategory);
});
this.productsService.searchCategoryProducts(this.searchCategory).subscribe(res => {
this.products = res;
console.log(this.products);
});
}
ngOnDestroy(): void {
if (this.mySubscription) {
this.mySubscription.unsubscribe();
}
}
}

Unsubscribing in angular

How can I unsubscribe in this function after dialog's closeAll method? Is it possible to turn subscription to a variable and then somehow apply it to 'onProjectAdded' instance?
this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
})
this.dialog.closeAll()
}
There are 2 ways:
1. Auto unsubscribe after component destroy:
export class ExampleComponentComponent implements OnInit, OnDestroy {
readonly _destroy$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
constructor(
private readonly dialog: MatDialog
) { }
ngOnInit() {
this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded
.pipe(takeUntil(this._destroy$))
.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
})
this.dialog.closeAll();
}
ngOnDestroy(): void {
this._destroy$.next(null);
this._destroy$.complete();
}
}
Unsubscribe when you need it(in my case afret destroying component but you can do it everywhere):
export class ExampleComponentComponent implements OnInit, OnDestroy {
public subscriptions: Subscription[] = [];
constructor(
private readonly dialog: MatDialog
) { }
ngOnInit() {
const subscription = this.dialog
.open(GridAddDialogComponent)
.componentInstance.onProjectAdded
.subscribe((projectData) => {
this._gridApi.setRowData([...this.rowData1, projectData]);
});
this.subscriptions.push(subscription);
this.dialog.closeAll();
}
ngOnDestroy(): void {
this.subscriptions.forEach((sub) => sub.unsubscribe());
}
}
Also you can use single subscription instead of array of it.

pipe operator not behaving as expected RXJS

Please look at my component below the purpose to is to listen on changes to an input, which it does and then emit the value to the parent component. I created a pipe to only emit every so often and therby minimize the calls to the api, for some reason even though I can see through various console.log statements that it goes in the pipe, it emits the value on every change. What is it that I am missing:
import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output, KeyValueDiffers, DoCheck, KeyValueDiffer} from '#angular/core';
import {BehaviorSubject, Observable, of} from "rxjs";
import {debounceTime, distinctUntilChanged, map, skip, switchMap, takeUntil, tap} from "rxjs/operators";
#Component({
selector: 'core-ui-typeahead-filter',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './typeahead-filter.component.html',
})
export class TypeaheadFilterComponent implements DoCheck {
#Input() id: string;
#Input() name: string;
#Input() caption: string;
#Input() placeholder: string;
#Input() cssClass: string;
#Input() cssStyle: string;
#Input() function: any;
#Input() data: Observable<string[]>;
differ: any;
detectChange: string = '';
// term$ = new BehaviorSubject<string>('');
text$ = new Observable<string>();
#Output() onTypeahead: EventEmitter<any> = new EventEmitter<any>();
#Output() onSelect: EventEmitter<any> = new EventEmitter<any>();
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
handleTypeahead = (text$: Observable<string>) =>
text$.pipe(
distinctUntilChanged(),
debounceTime(500),
).subscribe((value) => {
this.onTypeahead.emit(of(value))
})
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$ = of(item.currentValue);
this.handleTypeahead(this.text$);
}
});
}
}
}
More background: There is an ngModel on the input linked to detectChange when it changes then the ngDoCheck is called and executes. Everything is done in observables so in the parent I can subscribe to the incoming events.
EDIT -------------------------------------------------------------------
Tried the following solution based on my understanding of #ggradnig answer, sadly it skips over my pipe something seems wrong with it, really not sure what:
handleTypeahead = (text$: Observable<string>) => {
this.test.subscribe(this.text$);
this.test.pipe(
distinctUntilChanged(),
debounceTime(500),
// switchMap(value => text$)
).subscribe((value) => {
tap(console.log('im inside the subscription',value))
this.onTypeahead.emit(value)
})
}
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$ = of(item.currentValue);
this.handleTypeahead(this.test);
}
});
}
}
}
You can do the following -
export class TypeaheadFilterComponent implements DoCheck {
#Input() id: string;
#Input() name: string;
#Input() caption: string;
#Input() placeholder: string;
#Input() cssClass: string;
#Input() cssStyle: string;
#Input() function: any;
#Input() data: Observable<string[]>;
differ: any;
detectChange: string = '';
// term$ = new BehaviorSubject<string>('');
text$ = new BehaviorSubject<string>('');
serachTerm$: Observable<string>;
#Output() onTypeahead: EventEmitter<any> = new EventEmitter<any>();
#Output() onSelect: EventEmitter<any> = new EventEmitter<any>();
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
// handleTypeahead = (text$: Observable<string>) =>
// text$.pipe(
// distinctUntilChanged(),
// debounceTime(500),
// ).subscribe((value) => {
// this.onTypeahead.emit(of(value))
// })
ngOnInit() {
this.serachTerm$ = this.text$
.pipe(
distinctUntilChanged(),
debounceTime(500),
//filter(), //use filter operator if your logic wants to ignore certain string like empty/null
tap(s => this.onTypeahead.emit(s))
);
}
handleSelectItem(item) {
this.onSelect.emit(item);
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
if (item.key === 'detectChange'){
console.log('item changed', item)
this.text$.next(item.currentValue);
}
});
}
}
}
Now, at the bottom of your template put the following line -
<ng-container *ngIf="searchTerm$ | async"></ng-container>
Having this line will keep your component code free form managing the subscription [i.e. need not to subscribe/unsubscribe]; async pipe will take care of it.

Subscribe to EventEmitter in Angular2 not working

I'm learning Angular 2. I'm trying to send data from a component to other on the click of the first one.
Both components are siblings.
This is my code so far:
First Component:
#Component({
selector: 'jsonTextInput',
templateUrl: '../templates/jsonTextInput.html',
directives: [Card, CardTitle, CardDescription, Icon],
providers: [JsonChangeService]
})
export class JsonTextInput {
json: string = '';
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
}
process () {
this.jsonChangeService.jsonChange(this.json)
}
}
This is the service:
import {Injectable, EventEmitter} from '#angular/core';
#Injectable()
export default class JsonChangeService {
public jsonObject: Object;
stateChange: EventEmitter<any> = new EventEmitter<any>();
constructor (){
this.jsonObject = {};
}
jsonChange (obj) {
console.log('sending', obj)
this.jsonObject = obj
this.stateChange.emit(this.jsonObject)
}
}
The call from the first component to the service is working, since the sending is being printed.
This is the second Component
#Component({
selector: 'jsonRendered',
templateUrl: '../templates/jsonrendered.html',
directives: [Card, CardTitle],
providers: [JsonChangeService]
})
export class JsonRendered {
private jsonObject: Object
constructor (private jsonChangeService: JsonChangeService) {
this.jsonChangeService = jsonChangeService
this.jsonObject = jsonChangeService.jsonObject
this.jsonChangeService.stateChange.subscribe(json => { this.jsonObject = json; console.log('Change made!') })
}
ngOnInit () {
console.log(1)
}
ngOnChanges () {
console.log(2)
}
renderJson () {
console.log(3)
}
}
The function inside the subscribe to stateChange never runs. What am I missing?
EDIT
This is the content of my stateChange EventEmitter:
_isAsync: true
_isScalar: false
destination: undefined
dispatching: false
hasCompleted: false
hasErrored: false
isStopped: false
isUnsubscribed: false
observers: Array[0]
source:undefined
You have two different instances of JsonChangeService. That's why you don't receive message between components. You need to have one instance service, i.e. on parent component or on top level like this:
bootstrap(AppComponent, [JsonChangeService])

Categories

Resources