cypress / blazor testing a confirm dialog sawned from a JsRuntime - cypress

so I am writing automated test using cypress for a blazor app and I keep running into an error while I test a confirm dialog as if cypress is trying to return a function as a value.
the exact error message is "the value 'confirm' is no a function"
here is the snippet I am trying to debug
the .razor has a button called Cancel
when the button cancel is pressed a confirmation popup shows up with the options ok and cancel
ok takes you to a previous page in the hierarchy. cancel will take you back to the current page/form.
in the .razor file
<button class="btn btn-danger" #onclick="CancelConfirmation" data-cy="buttonCancel">Cancel</button>
later in the file for the #code section
private async Task CancelConfirmation()
{
bool confirmCancel = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure you want to leave ?");
if (confirmerCancel)
{
navigation.NavigateTo("/");
}
}
My current test is the following (section that triggers the error)
cy.get('[data-cy=buttonCancel]').click();
cy.on ('window:confirm', (text) => {
expect(text).to.contains('Are you sure you want to leave ?')
return true;
});
I feel it should work but all it does is raise an error.

It looks like your #inject variable does not match the one used in #code.
Also if (confirmerCancel) should be if (confirmCancel).
This worked for me in the Blazor Fiddle
#page "/"
#inject IJSRuntime jsRuntime
<h1>Hello, world!</h1>
<button class="btn btn-danger" #onclick="CancelConfirmation" data-cy="buttonCancel">Cancel</button>
#code{
private async Task CancelConfirmation()
{
bool confirmCancel = await jsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
if (confirmCancel)
{
// I can't use navigation in Fiddle, so just alerting
await jsRuntime.InvokeAsync<string>("ShowAlert", "Confirmed!");
}
}
}
The test needs to set the event listener before invoking the alert,
cy.on ('window:confirm', (text) => {
expect(text).to.contains('Are you sure you want to leave ?')
return true;
})
cy.get('[data-cy=buttonCancel]').click();
or use a stub, better for situation where window:confirm never fires
const stub = cy.stub()
cy.on ('window:alert', stub)
cy.get('[data-cy=buttonCancel]').click()
.then(() => {
expect(stub.getCall(0)).to.be.calledWith('Are you sure you want to leave ?')
})

Related

fromEvent not fire when I put HTMLElement, and fire when I put Jquery Object

Code Here
If I put $('input') as parameter of fromEvent it will fire when I call $('input').trigger('input'). But if I put document.getElementsByTagName('input') as parameter it will not fire. It will fire only when I type, but not fire when I call $('input').trigger('input').
Can someone explain why?
Note: $('input').on('input', _ => console.log) will work in both cases.
it's because the ngOnInit hook is running before the view is built, so the document selector is querying before the element is there, but jquery selectors are actually async waiting for the document to be ready.
here's how you do it the angular way:
#ViewChild('inp')
input
// use the after view init hook
ngAfterViewInit() {
fromEvent(this.input.nativeElement, 'input') // angular way
.pipe(
tap(_ => {
console.log("tap input");
})
)
.subscribe(_ => {
console.log("subscribe input");
});
}
in your html, add this:
<input type="text" #inp>
or the even better angular way (not using fromEvent)...
<input type="text" (input)="showMe($event)">
then in component just have this:
showMe(e) {
console.log(e)
}
https://stackblitz.com/edit/angular-ivy-jfoc7d?file=src%2Fapp%2Fapp.component.html

Angular 5 - Reactive forms doesn't validate form on submit

I have a simple form as below:
some.component.html
<form class="example-form" novalidate (ngSubmit)="onSubmit()" autocomplete="off" [formGroup]="testform">
<input type="text" formControlName="name" class="form-control" placeholder="Enter name" required/>
<app-show-errors [control]="claimform.controls.name"></app-show-errors>
<button type="submit" (click)="onSubmit()">Next</button>
</form>
some.component.ts
ngOnInit() {
this.testform= new FormGroup({
name: new FormControl('', { validators: Validators.required})
}, {updateOn: 'submit'});
}
onSubmit() {
if (this.testform.valid) {
alert('saving data');
} else {
this._validationService.validateAllFormFields(this.testform);
}
}
validationService.ts
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
Reference
Problem
The form will validate on submit if left blank, but even after filling the value when I check this.testform.valid it returns false. But if I remove updateOn:'submit' on form then it validates on blur of input control and when value is entered it validates form return true. Not sure if updateOn is working fine or not or whether I've implemented this in a proper way. Could someone point me in the right direction.
in your HTML you have two calls to onSubmit() function, from submit button:
<button type="submit" (click)="onSubmit()">Next</button>
and from the form:
<form class="example-form"
ovalidate
(ngSubmit)="onSubmit()"
autocomplete="off"
[formGroup]="testform">
The first call to be triggered is the button's trigger, which actually does nothing in terms of updating your reactive form, since you set FormGroup's option to {updateOn: 'submit'}. The second call to be triggered is the form's trigger, which does actual form update.
Here is FormGroup directive config:
#Directive({
selector: '[formGroup]',
providers: [formDirectiveProvider],
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
exportAs: 'ngForm'
})
as we can see in host property DOM form's submit (triggered by hitting ENTER while focused within form or clicking form's submit button) will call onSubmit() function:
onSubmit($event: Event): boolean {
(this as{submitted: boolean}).submitted = true;
syncPendingControls(this.form, this.directives);
this.ngSubmit.emit($event);
return false;
}
which then will call syncPendingControls() function:
export function syncPendingControls(form: FormGroup, directives: NgControl[]): void {
form._syncPendingControls();
directives.forEach(dir => {
const control = dir.control as FormControl;
if (control.updateOn === 'submit' && control._pendingChange) {
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
}
});
}
which updates a model at last.
So, in your case, just remove (click)="onSubmit()" from the submit button:
<button type="submit">Next</button>
also you do not need required DOM element property on your input, since you set it using Reactive Forms API validators: Validators.required and since you set your form to novalidate which cancels HTML5 form validation.

Remote validation causes submit inputs (multiple-submit-buttons) to be null

I recently implemented remote validation in my form:
ViewModel:
[Remote("IsTagUnique", "myController", "myArea", ErrorMessage = "This tag already exists.")]
public string tag { get; set; }
Controller:
public ActionResult IsTagUnique(string tag)
{
using (db)
{
try
{
var myTag = db.ASAuftraege.Single(m => m.tag == tag);
return Json(false, JsonRequestBehavior.AllowGet);
}
}
catch (Exception)
{
return Json(true, JsonRequestBehavior.AllowGet);
}
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult myView(string Send){
// doSomething
}
View (called "myView")
#Html.TextBoxFor(m => m.tag) #Html.ValidationMessageFor(m => m.tag)
<button class="form-button-submit" type="submit" name="Send" value="Send">Send</button>
The validation works perfectly fine.
Problem is: When I click the Send-button without manually triggering the validation on the tag-field once by clicking into the field and then clicking somewhere else the "IsTagUnique" function is executed before the myView()-function. This causes my submit-inputs (I actually have multiple send-buttons, just like the one shown in the view (different name/value of course) to be null. Any idea what I can do? I tried triggering the validation manually by giving focus and blurring the tag-field, and by triggering a change event. Doesn't trigger the validation, though.
After searching for a while I found out this seems to be a known bug:
The issue happens when a form which uses the remote method to validate a field. If a submit button is pressed after the validator fires, everything is alright and the request will contain the clicked submit button name/value pair.
However, when the remote method is not fired before the submit button is pressed, then the resulting request will NOT contain the submit button value/pair.
A solution that worked for me is this one:
$(function() {
$('button[type=submit]').click(function () {
$('<input>').attr({
type: 'hidden',
name: this.name,
value: this.value
}).appendTo($(this).closest('form'));
});
});
Credit to arturoribes

Conditional v-if is working only for the first time?

I have this in my view:
<div class="already_voted" v-if="already_voted" >
<p>You already voted or your are not allowed to vote</p>
</div>
This is my method :
upvote: function(com_id) {
var comment_id = {
comment_id :com_id
}
this.$http.post('/blog/article/comment/upvote', comment_id).then(function(response){
upvote_total= response.data.upvote_value;
this.already_voted = response.data.already_voted;
this.$dispatch('child-msg', this.already_voted);
$('.upvote_class_' + com_id ).text(upvote_total);
$('.isDisabledUpvote_' + com_id).addClass('disabled');
$('.isDisabledDownvote_' + com_id).removeClass('disabled');
},function(response){
});
},
Im getting value on click and if its true it need to show this div.
Problem is that this div is showed only for first time when already_voted is true and thats it. Next time when its true nothing happend. Any suggestion?
It looks like you are mixing jQuery and Vue, which should be avoided unless you have a specific reason to do so. Instead you should bind attributes to data. As a basic version of what you are doing you could bind both the disabled attribute and the message to a voted flag:
Markup
<div id="app">
<div v-if="voted">
You have already voted!
</div>
<button v-bind:disabled="voted" #click="vote()">
Vote
</button>
<button v-bind:disabled="!voted" #click="removeVote()">
Un-Vote
</button>
</div>
View Model
new Vue({
el: '#app',
methods: {
vote(){
this.voted = true;
},
removeVote(){
this.voted = false;
}
},
data: {
voted: false
}
});
Here I'm simply binding the disabled attribute using v-bind to the voted flag to disabled the buttons and am using v-if to show a message if the voted flag is true.
Here's the JSFiddle: https://jsfiddle.net/05sbjqLL/
Also be aware that this inside an anonymous function refers to the anonymous function itself, so either assign this to something (var self = this) outside the function or use an arrow function if using ES6.
EDIT
I've updated the JSFiddle to show you how you might handle your situation based on you comments:
https://jsfiddle.net/umkvps5g/
Firstly, I've created a directive that will allow you to initiate your variable from your cookie:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
})
This can now be used as:
<div v-init:voted="{{ $request->cookie('voted') }}"></div>
I simply disabled the button to show you how to bind attributes to data, there's loads more that can be done, for example showing the message after a user clicks the button, I've just added a click counter and bound thev-if to that instead, so the message doesn't show until a user clicks the button:
<div v-if="vote_attempts">
You have already voted!
</div>
Then in vote() method:
vote() {
this.voted = true;
this.vote_attempts++;
},
Then data:
data: {
voted: false,
vote_attempts: 0
}

Action executes twice on submit

My controller action is being executed twice. Fiddler shows two requests and responses, and for the first one has an icon that indicates "Session was aborted by the client, Fiddler, or the Server."
But I can't figure out where this is happening, or why.
Here are the specifics:
I have a section of a view (ThingFinancials) that looks like this:
#{ using (Html.BeginForm("ConfirmThing", "Thing", null, FormMethod.Get, new { id = "frmGo" }))
{
#Html.HiddenFor(model => model.ThingID)
<button id="btnGo">
Thing is a Go - Notify People</button>
}
}
The javascript for btnGo looks like this:
$("#btnGo").click(function () {
var form = $("#frmGo");
form.submit();
});
The action (stripped down) looks like this:
public ActionResult ConfirmThing(int thingID)
{
[do some database stuff]
[send some emails]
var financials = GetFinancials(thingID);
return View("ThingFinancials", financials);
}
The only thing that looks unusual to me is that the URL you'd see would start out as [Website]/Thing/ThingFinancials/47, and after submission the URL would be [Website]/Thing/ConfirmThing?ThingID=47.
(If you're wondering why the Action name doesn't match the View name, it's because there are multiple form tags on ThingFinancials, and they can't all have the same action name.)
Is there a Server.Transfer happening behind the scenes, or something like that?
If you are using a submit button then you need to cancel the default behaviour when submitting with javascript, otherwise you will submit it twice. Try this:
$("#btnGo").click(function () {
var form = $("#frmGo");
// event.preventDefault(); doesn't work in IE8 so do the following instead
(event.preventDefault) ? event.preventDefault() : event.returnValue = false;
form.submit();
});
Your int thingID is a query string parameter that stays with the request. At the end of ActionResult ConfirmThing(int thingID), all you're doing is returning a view. If you'd rather see the clean URL ([Website]/Thing/ThingFinancials/47) you can make the following changes.
public ActionResult ConfirmThing(int thingID)
{
[do some database stuff]
[send some emails]
// This logic is probably in the 'ThingFinancials' action
// var financials = GetFinancials(thingID);
// I'll assume we're in the same controller here
return RedirectToAction("ThingFinancials", new { thingID });
}
This is because of your jquery event just add stopImmediatePropagation() to your jquery event.
$("#btnGo").click(function (event){
event.stopImmediatePropagation();
});

Resources