How to properly test vuetify form validation error with cypress? - vue.js

I have two text input fields in my vuetify form and I want to test validation errors for each of them separately. but I can't find a way to make sure which error element belongs to which input. I mean I can't find the proper selector.
This is a pseudo form:
<v-text-field
...
:error-messages="emailErrors"
data-cy="email"
></v-text-field>
<v-text-field
...
:error-messages="passwordErrors"
data-cy="password"
></v-text-field>
<v-btn type="submit" >Login</v-btn>
And this is the result produced when form has some validation errors for password field:
<div class="v-input v-input--has-state">
<div class="v-input__control">
<div class="v-input__slot">
<div class="v-text-field__slot">
<input data-cy="password" id="input-29" type="text" />
</div>
</div>
<div class="v-text-field__details">
<div class="...." role="alert">
<div class="...">
<div class="v-messages__message">password is required</div>
</div>
</div>
</div>
</div>
</div>
Notice how data-cy is acting as an attribute for input field only, therefor can not be used to find error element related to password, I can create cypress test to check if there are any validation errors in the form like this:
it('shows password validation error', () => {
cy.visit(loginUrl)
cy.cyElement('email').type('test#email.com')
// do not fill password
cy.get('button').submit()
cy.get('.v-messages__message').should('not.be.empty')
})
but I can't make sure that this validation element is really related to the password! it just checks if there are any validation errors in the form and asserts ok if yes.
One way to do it would be wrapping all vuetify components inside but it is not perfect at all.
Thank you so much in Advance!

It seems like a traversal task. You can use parents() to navigate to the common parent, and then find() the children with the specific class 'v-messages__message'.
cy.get("[data-cy=email]")
.parents(".v-input__control")
.find(".v-messages__message")
.should("contain.text", "email is required")
Here is a handy cheatsheet with all the commands available in traversing the dom: https://example.cypress.io/commands/traversal

While Igor's answer is technically correct, you don't need to know so much about the structure of the app.
Since contains works on the element specified and it's children, you can assert the message exists somewhere on the form.
cy.get('[data-cy="email"]')
.parents('form')
.should('contain', 'password is required')
or if you have data-cy="login-form" on the <v-form>,
cy.get('[data-cy="login-form"]')
.should('contain', 'password is required')

Related

Validating buttons and calling API & checking with the status of it

I am new to Vuejs and I wanna know certain things about validating buttons.
I have used two inputs one is for API key and the other one is for SECRET Key, So initially user has to enter the Keys in both the field.
My first question is how do I validate this:
<div class="vx-form-group">
<label class="Inpu1">API Key</label>
<div class="col">
<vs-input v-validate="'required|apiKey'" placeholder="API Key" name="apiKey" />
</div>
</div>
<div class="vx-form-group" style="margin-bottom: 5%">
<label class="Input2">Secret Key</label>
<div class="col">
<vs-input v-validate="'required|secretKey'" placeholder="Secret Key" name="secretKey" />
</div>
</div>
My second question, I have used two buttons:
First in order to save it
Second one for CHECKING those keys entered. So whenever I click the CHECK button it has to call my API and check the status of it, If it returns a 200 then I want to display "connection is OK". If that is a 401 then I want to display "Connection not Ok".
Here s how I tried it:
<vs-button class="btn " #click.prevent="apiform"
style="margin-right: 2%; ">
Check
</vs-button>
<vs-button class="btn">Save</vs-button>
<vs-button class="close"
#click="$refs.modalName.closeModal()" style="margin-left: 2%">Cancel
</vs-button>
//in my script
<script>
methods: {
async apiform() {
const response = await this.$http.post('https://my-api-goes-here',
{
check: this.check
})
console.log(response)
this.keys.push(response)
}
}
// If it returns a 200 then it means connection is OK.
// If it returns 401 then it means connection is Not ok.
</script>
I am new to Vuejs, I have no idea. Please anyone who knows the answer please try explaining me practically by sending the code so that I can understand it clearly.
Thank You.
Welcome!
For your first question, I see input fields, but their values are not bound to anything. Best thing to do is bind your inputs to variables using v-model as so:
<div class="vx-form-group">
<label class="Inpu1">API Key</label>
<div class="col">
<vs-input v-model="apiKey" v-validate="'required|apiKey'" placeholder="API Key" name="apiKey" />
</div>
</div>
<div class="vx-form-group" style="margin-bottom: 5%">
<label class="Input2">Secret Key</label>
<div class="col">
<vs-input v-model="secretKey" v-validate="'required|secretKey'" placeholder="Secret Key" name="secretKey" />
</div>
</div>
and in your script
<script>
export default{
name:'myComponentName',
data(){
return{
apiKey:'',
secretKey:''
}
}
That way you can check the values and validate them in your methods by using this.apiKey and this.secretKey.
For your second question, I use axios library, it makes http requests easy.I will assume $http.post returns a response with data and status, if that's the case (someone could correct me) you could check if response.status === 401 and handle it in whatever way you would like.
I hope this helps, happy coding!

b-field value is getting updated only #select and not #input?

I am using the Buefy UI components in my VueJS project. I have a drop-down in a page:
<b-field label="Business Unit">
<b-autocomplete
:data="dataBusinessUnit"
placeholder="select a business unit..."
field="businessUnit"
:loading="isFetching"
:value="this.objectData.businessUnit"
#typing="getAsyncDataBusinessUnit"
#select="(option) => {updateValue(option.id,'businessUnit')}"
>
<template slot-scope="props">
<div class="container">
<p>
<b>ID:</b>
{{props.option.id}}
</p>
<p>
<b>Description:</b>
{{props.option.description}}
</p>
</div>
</template>
<template slot="empty">No results found</template>
</b-autocomplete>
</b-field>
As you can see from the above code, the updateValue function is responsible for updating the value, but it will currently be called only when the user selects something from the drop-down suggestions. I want the value to be updated even when the user starts to type something. Example: #input="(newValue)=>{updateValue(newValue,'businessUnit')}". However, there is already a debounce function called getAsyncDataBusinessUnit that I am calling to fetch the filtered autocomplete results based on what the user has typed during the #typing event.
According to the Buefy Autocomplete API documentation found here, you could probably use v-model instead of using value directly.
Alternatively you could actually implement the #input like you wrote yourself, the #typing event shouldn't interfere with it.
Or you could just handle the value updating in #typing:
#typing="onTyping"
// then later in JS...
methods: {
onTyping(value) {
this.updateValue(value, 'businessUnit')
this.getAsyncDataBusinessUnit(value)
},
}

VeeValidate - check validated input field to enable another input

Its my first time using VeeValidate. How can I enable/disable a form field just when another is valid. For example, just enable password field after veevalidate checks the user field as valid.
Wrap both relevant fields in a ValidationObserver and use it's scoped prop errors to tell you when the one field is invalid. Something like this (untested):
<ValidationObserver v-slot="{ errors }">
<ValidationProvider vid="item1" rules="required">
<input v-model="item1" />
</ValidationProvider>
<ValidationProvider vid="item2" rules="required">
<input v-model="item2" :disabled="errors.item1"/>
</ValidationProvider>
</ValidationObserver>

Remove validation in angular 4 when it's not render in NgIf

I want to remove validation for which control is not rendered by using NgIf. I was try to use directive to remove with hidden control but cannot do the same because it not render in template. So I can not check formControlName with ElementRef in directive. Here is ts file
this.form = this._fb.group({
text1: ['', Validators.required],
text2: ['', Validators.required]
});
and template
<form[formGroup]="form">
<input type="text" formControlName="text1">
<div *ngIf="false">
<input type="text" formControlName="text2">
</div>
I want to remove Validation of text2 dynamically and globally. Not remove validator in ts file.
This Angular source GitHub Issue comment by Kara seems extremely relevant, and illustrates how you might solve the problem by treating the reactive model as "source of truth" and create your ngIf expression off of that source of truth, instead of the reverse. This shows it's by design and you have to make some effort not to mix up template-driven and reactive form ideas.
https://github.com/angular/angular/issues/7970#issuecomment-228624899
Thanks for taking the time to describe the problem. I took a look at
your example code, and it seems that you are using the reactive form
directives (a.k.a "model-driven" directives: ngFormModel, etc), but a
template-driven strategy. The fact that the ngIf does not remove the
control from the form's serialization and validation is actually by
design, and here's why.
In each form paradigm - template-driven and reactive - there can only
be one source of truth for the list of active controls. In the
template-driven paradigm, the source of truth is the template. In the
reactive equivalent, the source of truth is the form model created in
the parent component. The DOM does not dictate the state of your form.
For this reason, if you remove form control elements from the DOM
while using a reactive approach, the form controls are not necessarily
changed in the source of truth unless you want them to be. You can
choose to update the controls imperatively by calling
this.form.removeControl('controlName'), or you can choose to keep the
controls in your form. This flexibility allows you to add or remove
inputs from the DOM temporarily while keeping their form values
serialized (e.g. if you have a number of collapsible sections to your
form, you can remove sections on collapse without impacting the value
of your form). We don't want to restrict this flexibility and
complicate ownership by forcing the model to always match the DOM.
So in your case, if you choose a reactive strategy, you'll want to
invert your logic to rely on the source of truth - the model.
Specifically, this means removing the control imperatively in the
model by calling this.form.removeControl('name') when the button is
clicked. Then, the ngIf should depend on the control's presence in the
model with *ngIf="form.contains('name')", rather than the other way
around. See example plunker here:
http://plnkr.co/edit/V7bCFLSIEKTuxU9jcp6v?p=preview
It's worth noting that if you're still using beta.14 (as in your
plunker), you'll need to call this.form.updateValueAndValidity()
manually. This requirement was removed in #9097, so versions after
RC.2 don't require the call.
Another option is to convert to a template-driven strategy (no
ngFormModel), which will remove the control from the form when it's
destroyed from the template. Example:
http://plnkr.co/edit/s9QWy9T8azQoTZKdm7uI?p=preview
I'm going to close this issue as it works as intended, but I think we
could make the experience a lot friendlier. A good start would be some
more cookbooks and guides in the documentation.
When the condition property is changed then call the method dynamically to set and remove the validation. for example,
whenConditionChanges(condition:boolean){
if(!condition){
this.form.controls["text2"].setValidators([Validators.required]);
this.form.controls["text2"].updateValueAndValidity();
} else {
this.form.controls["text2"].setValidators(null);
this.form.controls["text2"].updateValueAndValidity();
}
}
Since, your formcontrol text2 is dependent on some condition. it should not be as required control. So you reactive form control should be
this.form = this._fb.group({
text1: ['', Validators.required],
text2: ['',]
});
If there is scenario, where you want to ensure that text should be required whenever it's present in dom then use custom validators in angular. Refer documentation of the same for your implementation.
Here the Example: on runtime you can update validators based on checkbox value.you can set field as required and remove also.
http://plnkr.co/edit/YMh0H61LxPGCFtm9Yl13?p=preview
What i did (and work for me), create an alternative formgroupcontrol with another button [disabled], manage the *ngIf for the button and for the form.
<mat-step [stepControl]="listBrandFormGroup">
<form [formGroup]="listBrandFormGroup">
<ng-template matStepLabel>Define tu marca</ng-template>
<div class="heading">¡ Haber ! Definamos tu marca</div>
<div class="subheading">Estamos a punto de hacer magia, solo necesitamos lo siguiente:</div>
<div class="content" fxLayout="column" fxLayoutGap="8px" *ngIf="listBrand.length > 0">
<mat-form-field fxFlex="auto">
<mat-select name="brand_id" formControlName="brand_id" placeholder="Selecciona una marca existente" (selectionChange)="setBrand($event.value);">
<mat-option [value]="0">Crear una nueva marca</mat-option>
<mat-option *ngFor="let marca of listBrand" [value]="marca.id">{{marca.display_name}}</mat-option>
</mat-select>
<mat-hint>{{descripBrand}}</mat-hint>
</mat-form-field>
</div>
</form>
<form [formGroup]="brandFormGroup">
<div class="content" fxLayout="column" fxLayoutGap="8px" *ngIf="idBrand === 0">
<mat-form-field>
<mat-label>Marca</mat-label>
<input matInput formControlName="display_name" required>
<mat-hint>Ese increíble y único nombre, ¡ tú sabes !</mat-hint>
</mat-form-field>
<mat-form-field fxFlex="grow">
<mat-label>Descripción</mat-label>
<textarea matInput formControlName="descrip" required></textarea>
<mat-hint>¿ Cuéntanos de que se trata ?</mat-hint>
</mat-form-field>
<mat-label>Logo</mat-label>
<input type="file" name="photo" ng2FileSelect required formControlName="display_logo" />
</div>
<div class="actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button type="button" (click)="stepper.reset()" [disabled]="brandFormGroup.pristine"
color="primary">RESET
</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="brandFormGroup.invalid" *ngIf="idBrand === 0">SIGUIENTE</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="listBrandFormGroup.invalid" *ngIf="idBrand > 0">SIGUIENTE</button>
<button mat-raised-button matStepperNext color="primary" [disabled]="listBrandFormGroup.invalid" *ngIf="idBrand > 0" (click)="launch();"><i class="material-icons">launch</i>LANCÉMONOS</button>
</div>
</form>
</mat-step>

Validate a form with fields wrapped in another components in Aurelia

I'm wondering if I can validate a form with input fields that are wrapped in another components.
Something like this:
<template>
<require from="./inpWrap"></require>
<form submit.delegate="submit()">
<div class="form-group">
<inp-wrap value.bind="firstName & validate"></inp-wrap>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
Here's the full gist: https://gist.run/?id=f94dd7502141f865468465ef25c9e5a1
Visually, in this example, the validation will run only on running the .validate() method (by clicking on the submit button) for the wrapped element.
Would it be possible to have the same validation behaviour for the component-wrapped elements?