How to spy the element twice to cover the code in karma -jasmine - jasmine

ts code:-
this.shared.refreshPageObeservable().subscribe(data => {
if (data && Object.keys(data).length) {
this.userDetails.fullName = `${data.firstName} ${data.lastName}`;
this.userDetails.lastLogin = `${data.lastLoginDt}`;
} else {
this.userDetails.fullName = '';
this.userDetails.lastLogin = '';
}
});
spec file :-
spyOn(shared, 'refreshPageObeservable').and.returnValue(of({firstName: 'john', lastName: 'doe', lastLoginDt: '12345'}));
fixture.detectChanges();
this code overs the if statement, but how to cover the else part.

Give an empty object so the Object.keys(data).length in the if statement is false so it goes to the else block
it('should set fullName and lastLogin to an empty string', () => {
spyOn(shared, 'refreshPageObeservable').and.returnValue(of({}));
// call the function again that is responsible for subscribing to that stream
expect(component.userDetails.fullName).toBe('');
expect(component.userDetails.lastLogin).toBe('');
});

Related

Cypress: I need to exit the for loop if the condition is satisfied

Below is the code which I am using.
I am working with Cypress + Cucumber + Typescript.
Scenario: I need to get a list of unique values using a for loop. Then I am passing this value to an API to verify some condition and if the condition is met I want to exit the loop.
To exit the loop I somewhere read a solution that if I use "return false" as first-line in if condition then loop will exit which seems to work fine.
The issue here is, when I try to set a flag from inside the for-if loop to the instance variable then the value read by if condition (for exiting the loop) is not picking the updated value of instance variable. And the loop continues to run.
Below is the code snippet:
class test {
static isVinavailable: boolean = false;
static setEligibleVehicleVinTest() {
cy.xpath(eligibleForSaleVehicleVin).then((esv) => {
const listingCount = Cypress.$(esv).length;
for (let i = 0; i < listingCount; i++) {
let text123 = esv.eq(i).text();
genericAction.getAuthenticationKey();
cy.fixture("authResp.json")
.then((authResp) => {
cy.request({
method: "GET",
url: vehicleCheckEligibility + text123,
headers: {
Authorization: authResp.access_token,
},
});
})
.then((response: any) => {
cy.wait(5000);
let responseDataelig = response.body;
if (
(responseDataelig.val1 =
"Y" &&
responseDataelig.val2 === "N" &&
responseDataelig.val3 === "N")
) {
this.isVinavailable = true;
}
});
if (this.isVinavailable) {
return false;
}
}
});
}
}
class test {
static isVinavailable = false;
static setEligibleVehicleVinTest(): Cypress.Chainable<boolean> {
return cy.xpath(eligibleForSaleVehicleVin).each(($el) => {
let text123 = $el.text();
cy.fixture('authResp.json')
.then((authResp) => {
return cy.request({
// your code block
});
})
.then((response: any) => {
// your code block
if (condition) {
this.isVinavailable = true;
return false;
};
});
}).then(() => {
return this.isVinavailable;
});
}
}

Triggering side effects when a value is filtered using filter() operator

I'm working on a username search input that fires after a debounceTime(). I also filter out values. I want to trigger some side effects if values are filtered (such as setting an error message and stopping loaders).
I'm achieving this right now by using tap() and checking the same predicate that I later check in a filter() function. I feel this is bad/there is a more proper way to achieve this.
private setUserSearchObservable() {
this.userSearch = userSearch$.pipe(
tap(() => this.loading = true),
debounceTime(500),
this.filterValuesWithErrorMessages(),
......
);
}
private filterValuesWithErrorMessages() {
return pipe(
tap((val: string) => { if (this.usernamesMatch(val)) { this.errorMessage = 'You will be added to the frame automatically'; this.loading = false; }}),
tap((val: string) => { if (this.usernameInArray(val)) { this.errorMessage = 'User is already added'; this.loading = false; }}),
tap((val: string) => { if (val.length < 2) { this.errorMessage = ''; this.loading = false; }}),
filter((val: string) => (val.length >= 2 && !this.usernamesMatch(val) && !this.usernameInArray(val))),
);
}
As you can see, I explicitly check the exact same conditionals using tap() right before using them on filter(). Is there an operator/different pattern that will allow me to achieve this in a more concise way?
Refactored your code a little bit, there is no specific operator to deal with error checking, you can use switchMap and inner observable e.g of, never to control whether the result should go through.
private setUserSearchObservable() {
this.userSearch = userSearch$.pipe(
tap(() => this.loading = true),
debounceTime(500),
map((value) => this.filterValuesWithErrorMessages(value)),
swtichMap(msg => {
if (msg !== false) {
this.errorMessage = result
this.loading = false
return never()
}
return of(true)
})
);
}
private filterValuesWithErrorMessages(val) {
if (this.usernamesMatch(val)) return 'You will be added to the frame automatically'
if (this.usernameInArray(val)) return 'User is already added'
if (val.length < 2) return ''
return false
}

RxJS Filter Array of Arrays

I'm trying to perform a filter on a array of arrays in rxjs. Consider the following:
function guard1(): boolean | Observable<boolean> {}
function guard2(): boolean | Observable<boolean> {}
function guard3(): boolean | Observable<boolean> {}
const routes = [
{ name: 'Foo', canActivate: [guard1, guard2] },
{ name: 'Bar', canActivate: [guard3] },
{ name: 'Moo' }
];
I want to filter the routes array to only routes that return true
from the combination of results of inner array canActivate or if it doesn't have canActivate, I want it to be NOT filtered out.
Lets say guard1 returned true and guard2 returned false, I'd expect route Foo to not be in the filtered list.
I took a stab at this but its not quite doing what I expect:
this.filteredRoutes = forkJoin(of(routes).pipe(
flatMap((route) => route),
filter((route) => route.canActivate !== undefined),
mergeMap((route) =>
of(route).pipe(
mergeMap((r) => r.canActivate),
mergeMap((r) => r()),
map((result) => {
console.log('here', result, route);
return route;
})
)
)));
If I were writing this outside of RXJS, the code might look something like this:
this.filteredRoutes = [];
for (const route of this.routes) {
if (route.canActivate) {
let can = true;
for (const act of route.canActivate) {
let res = inst.canActivate();
if (res.subscribe) {
res = await res.toPromise();
}
can = res;
if (!can) {
break;
}
}
if (can) {
this.filteredRoutes.push(route);
}
} else {
this.filteredRoutes.push(route);
}
}
Thanks!
I'm sure there's other (and likely better ways to handle this, but it works...
from(routes).pipe(
concatMap((route) => {
// handle if nothing is in canActivate
if (!route.canActivate || route.canActivate.length === 0) {
// create an object that has the route and result for filtering
return of({route, result: true})
};
const results = from(route.canActivate).pipe(
// execute the guard
switchMap(guard => {
const result: boolean | Observable<boolean> = guard();
if (result instanceof Observable) {
return result;
} else {
return of(result);
}
}),
// aggregate the guard results for the route
toArray(),
// ensure all results are true
map(results => results.every(r => r)),
// create an object that has the route and result for filtering
map(result => ({route, result})),
);
return results;
}),
// filter out the invalid guards
filter(routeCanActivateResult => routeCanActivateResult.result),
// return just the route
map(routeCanActivateResult => routeCanActivateResult.route),
// turn it back into an array
toArray()
)
// verify it works
.subscribe(routes => routes.forEach(r => console.log(r.name)));
Also, here is a working example in stackblitz.

Microsoft Bot Framework

I am trying to build A CONVERSATIONAL BOT. when I am trying to pass the response with next , its not getting reflected din the next functions .
bot.dialog('Barcode',
(session, args, next) => {
var intent = args.intent;
var id = builder.EntityRecognizer.findEntity(intent.entities, 'Report.Id');
if (id) {
next({ response: id.entity });
} else {
builder.Prompts.text(session, 'Please enter your id');
}
session.endDialog();
} ,
(session,results) => {
var id = results.response;
session.send(id.toString()); -- i want the value to be passed here
}
).triggerAction({
matches: 'Barcode'
})
If you want to implement a workflow in a dialog, you can set IDialogWaterfallStep|IDialogWaterfallStep[] in the second parameter in dialog() function.
In your code, you forget to cover [] outside the steps.
Try:
bot.dialog('Barcode',[
(session, args, next) => {
var intent = args.intent;
var id = builder.EntityRecognizer.findEntity(intent.entities, 'Report.Id');
if (id) {
next({ response: id.entity });
} else {
builder.Prompts.text(session, 'Please enter your id');
}
session.endDialog();
} ,
(session,results) => {
var id = results.response;
session.send(id.toString()); -- i want the value to be passed here
}]
).triggerAction({
matches: 'Barcode'
})

How to list folders and files in a directory using ReactiveX

When using Observables for certain tasks that involve a lot of chaining and a lot of asynchronous operations, such as listing all the items in a folder and checking all of the folders in it for a specific file, I often end up either needing to build the complex chain for each task (return Observable.of(folder)...) or having some kind of special value that gets forwarded to the end to signal the end of a batch (every operator starts with if(res === false) return Observable.of(false)).
Sort of like that stick that you put between your groceries and those of the person in front of you at the checkout.
It seems like there should be a better way that doesn't involve forwarding a stop value through all kinds of callbacks and operators.
So what is a good way to call a function that takes a folder path string and returns a list of all the files and folders in it. It also specifies whether the files are HTML files or not, and whether or not the folders contain a file called tiddlywiki.json.
The only requirement is that it can't return anything like Observable.of(...).... It should probably have a subject at the top of the chain, but that is not a requirement.
function listFolders(folder) {
return [
{ type: 'folder', name: 'folder1' },
{ type: 'datafolder', name: 'folder2' }, //contains "tiddlywiki.json" file
{ type: 'folder', name: 'folder3' },
{ type: 'htmlfile', name: 'test.html' },
{ type: 'other', name: 'mytest.txt' }
]
}
Here is one that does not follow the rules I layed out (see below for one that does), but it took about ten minutes, using the first one as a guide.
export function statFolder(subscriber, input: Observable<any>) {
return input.mergeMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { return Observable.of({ error: err }) as any; }
else return Observable.from(files).mergeMap(file => {
return obs_stat([file,folder])(path.join(folder, file as string));
}).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry]);
}, 20).map((res) => {
if (res[0] === true) return (res);
let [err, files, [entry]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry]);
}).reduce((n, [dud, entry]) => {
n.push(entry);
return n;
}, []).map(entries => {
return { entries, folder, tag };
}) as Observable<{ entries: any, folder: any, tag: any }>;
}).subscribe(subscriber);
}
Original: This took a few hours to write...and it works...but...it uses concatMap, so it can only take one request at a time. It uses a custom operator that I wrote for the purpose.
export function statFileBatch(subscriber, input: Observable<any>) {
const signal = new Subject<number>();
var count = 0;
//use set timeout to fire after the buffer recieves this item
const sendSignal = (item) => setTimeout(() => { count = 0; signal.next(item); });
return input.concatMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).lift({
call: (subs: Subscriber<any>, source: Observable<any>) => {
const signalFunction = (count) => signal.mapTo(1), forwardWhenEmpty = true;
const waiting = [];
const _output = new Subject();
var _count = new Subject<number>()
const countFactory = Observable.defer(() => {
return Observable.create(subscriber => {
_count.subscribe(subscriber);
})
});
var isEmpty = true;
const sourceSubs = source.subscribe(item => {
if (isEmpty && forwardWhenEmpty) {
_output.next(item);
} else {
waiting.push(item)
}
isEmpty = false;
})
const pulse = new Subject<any>();
const signalSubs = pulse.switchMap(() => {
return signalFunction(countFactory)
}).subscribe(count => {
//act on the closing observable value
var i = 0;
while (waiting.length > 0 && i++ < count)
_output.next(waiting.shift());
//if nothing was output, then we are empty
//if something was output then we are not
//this is meant to be used with bufferWhen
if (i === 0) isEmpty = true;
_count.next(i);
_count.complete();
_count = new Subject<number>();
pulse.next();
})
pulse.next(); //prime the pump
const outputSubs = Observable.create((subscriber) => {
return _output.subscribe(subscriber);
}).subscribe(subs) as Subscription;
return function () {
outputSubs.unsubscribe();
signalSubs.unsubscribe();
sourceSubs.unsubscribe();
}
}
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { sendSignal(err); return Observable.empty(); }
return Observable.from(files.map(a => [a, folder, files.length, tag])) as any;
}).mergeMap((res: any) => {
let [file, folder, fileCount, tag] = res as [string, string, number, any];
return obs_stat([file, folder, fileCount, tag])(path.join(folder, file))
}, 20).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder, fileCount, tag]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry, fileCount, tag])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry, fileCount, tag]);
}, 20).map((res) => {
//if (res === false) return (false);
if (res[0] === true) return (res);
let [err, files, [entry, fileCount, tag]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry, fileCount, tag]);
}).map(([dud, entry, fileCount, tag]) => {
count++;
if (count === fileCount) {
sendSignal([count, tag]);
}
return entry;
}).bufferWhen(() => signal).withLatestFrom(signal).map(([files, [sigResult, tag]]: any) => {
return [
typeof sigResult !== 'number' ? sigResult : null, //error object
files, //file list
typeof sigResult === 'number' ? sigResult : null, //file count
tag //tag
];
}).subscribe(subscriber);
}

Resources