How to simulate ionBlur in tests - ionic-framework

Maybe I'll start with what I want to achieve: I have a form with a required field. By default it should not display any error. The error should be displayed if a user touches the field. So my field looks more or less like this:
<ion-input .... (ionBlur)="updateDispayedErrors()"></ion-input>
But I don't know how to test it because:
Running fixture.debugElement.nativeElement.blur() does not triggers ionBlur handler (the same with ....dispatchEvent(new Event('blur')))
Plain angular (blur) does not work (i.e. if I change the code to (blur)="updateDisplayErrors()" then it does not work)
It seems that calling blur() method on native <input .../> element that is created in the browser would work but... the problem is that when I run the tests fixture.debugElement.nativeElement.childNodes is empty... So the native <input .../> is not created
Please let me know if you would like to see a full example to illustrate it.

If you add a selector to ion-input like:
<ion-input .... (ionBlur)="updateDisplayedErrors()" id="specialInput"></ion-input>
Then you can use fixture.debugElement.triggerEventHandler:
import { By } from '#angular/platform-browser';
...
it('should emit ionBlur', () => {
const ionDe = fixture.debugElement.query(By.css('#specialInput'));
const ionBlurResult = spyOn(component, 'updateDisplayedErrors');
ionDe.triggerEventHandler('ionBlur', {});
expect(ionBlurResult).toHaveBeenCalled();
});

Related

render() - function is not updating the DOM - lit-html

I have question regarding the lit-html render function.
Currently I have the problem that sometimes the render function does not update the DOM.
My render function looks like this:
public renderLootbag = (_characterItems: Object): void => {
//some other code
const lootbagInventoryTemplate: TemplateResult[] = [];
for (let i: number = 0; i < numberOfInventorySpaces; i++) {
lootbagInventoryTemplate.push(html`
<section class="lootbag__inventoryItem">
//some other code
</section>
`)
};
const lootbagTemplate: TemplateResult = html`
//some other code
<section class="lootbag__inventory">${lootbagInventoryTemplate}
</section>
`;
console.log(lootbagInventoryTemplate);
render(lootbagTemplate, getLootbagContainer);
}
This function gets triggerd everytime I click on a button called "open Lootbag"
Now the problem is: if the value of the parameter in the renderLootbag() function changes, the DOM doesn´t get updated, eventhough the console.log(lootbagInventoryTemplate) shows me the new values ... :/.
img of the console.log
img of the dom
Note: in this case the Element with the class "lootbag__itemData" shouldn´t be filled with content.
Is there a way to completly force a new render?, I have already tried to empty my container with innerHTML and then call the render function but that didn´t work.
Thanks in advance
I had a similar problem with an Input that didnt update all the time.
The undelying problem was that i converted all inputs from the user to number and if the user typed some letters it would fall back to zero.
Since the values didn't change always (zero changed to zero), lit html wouldn't rerender.
The Solution was using a directive.
import { html, render, directive } from "lit-html";
const value = store.savedValueFromInput;
const forceWrite = directive((someValue) => (part) => {
part.setValue(someValue);
});
<input type="number" #change="${setValueFromInput}" .value="${forceWrite(value)}" />
That should force a re-render.
Best regards,
Tristan

How can I test if a html input type radio is checked

I have this HTML in my component.html:
<input type="radio" [checked]="selected" (change)="select()" />
How can I make a Spectator query and expect to test if this input element is checked or not?
I have tried with:
expect(spectator.query('input')).toHaveAttribute('checked');
But I get the error:
Error: Expected element to have attribute 'checked', but had 'undefined'
And I have tried with:
expect(spectator.query('input')).toBeChecked();
But then I get the error:
Error: Expected element to be checked
How can I test this simple HTML input element?
Thank you
Søren
expect(spectator.query('input')).toBeChecked(); is the correct usage.
It looks like selected property is false due to which radio button is not selected and you are getting this error. Simple fix you binding in test (by setting selected to true) or update assertion to check if radio button is not selected:
expect(spectator.query("input[type=radio]")).not.toBeChecked();
Take a look at this stackblitz code sample where I have 2 bound radio buttons one selected and another not selected and I have tests for it.
it("should be checked", () => {
spectator = createComponent();
expect(spectator.query("#r1[type=radio]")).not.toBeChecked();
});
it("should not be checked", () => {
spectator = createComponent();
expect(spectator.query("#r2[type=radio]")).toBeChecked();
});
Also, take a look at this guide to see available custom matchers.

TextField is not getting updated on re-render with different intialValues

I'm using redux-form-material-ui for the forms. I have simple form with two text fields. This form will show the initalValues passed to it and user can also update the values of TextField. These TextFields working fine with validations and all but I'm facing problem while re-rendering. On route change, I'm passing the different intialValues to that form component but even though proper values passed to it, TextFields are not getting updated. It just shows the first time passed initialValues.
Here is my code:
From parent component:
<ReduxForm {...initialValues} />
ReduxForm component:
class ReduxForm extends React.Component {
render () {
return (
<div>
<form onSubmit={() => console.log('Submitted')} >
<Field
className='input-field'
name='name'
component={TextField}
floatingLabelText='Full Name'
/>
<Field
className='input-field'
name='email'
component={TextField}
floatingLabelText='Email'
/>
</form>
</div>
)
}
}
export default reduxForm({
form: 'test-form',
validate
})(ReduxForm)
I Know that, this is the same problem with defaultValue in material-ui's TextField. Is this expected behavior in redux-form-material-ui also? If so, is there way I can solve this issue without setting the state and all?
Thank you!
Haven't used redux-form-material-ui but try adding enableReinitialize: true to your reduxForm decorator
export default reduxForm({
form: 'test-form',
validate,
enableReinitialize: true
})(ReduxForm);
And see if that works.

Trying to think about how to build a multi step form in angular 2

I am trying to build a small, 3 step form. It would be something similar to this:
The way I did this in react was by using redux to track form completion and rendering the form body markup based on the step number (0, 1, 2).
In angular 2, what would be a good way to do this? Here's what I am attempting at the moment, and I'm still working on it. Is my approach fine? Is there a better way to do it?
I have a parent component <app-form> and I will be nesting inside it <app-form-header> and <app-form-body>.
<app-form>
<app-header [step]="step"></app-header>
<app-body [formData]="formData"></app-body>
</app-form>
In <app-form> component I have a step: number and formData: Array<FormData>. The step is just a index for each object in formData. This will be passed down to the header. formData will be responsible the form data from user. Each time the form input is valid, user can click Next to execute nextStep() to increment the index. Each step has an associated template markup.
Is there a better way to do something like this?
don't overdo it, if it is a simple form you don't need to use the router or a service to pass data between the steps.
something like this will do:
<div class="nav">
</div>
<div id="step1" *ngIf="step === 1">
<form></form>
</div>
<div id="step2" *ngIf="step === 2">
<form></form>
</div>
<div id="step3" *ngIf="step === 3">
<form></form>
</div>
It's still a small template, and you kan keep all of the form and all the data in one component, and if you want to you can replace the ngIf with something that switches css-classes on the step1,2,3 -divs and animate them as the user moves to the next step
If you want to keep things extensible, you could try something like this:
<sign-up>
<create-account
[model]="model"
[hidden]="model.createAccount.finished">
</create-account>
<social-profiles
[model]="model"
[hidden]="model.socialProfiles.finished">
</social-profiles>
<personal-details
[model]="model"
[hidden]="model.personalDetails.finished">
</personal-details>
</sign-up>
export class SignUpVm {
createAccount: CreateAccountVm; //Contains your fields & finished bool
socialProfiles: SocialProfilesVm; //Contains your fields & finished bool
personalDetails: PersonalDetailsVm; //Contains your fields & finished bool
//Store index here if you want, although I don't think you need it
}
#Component({})
export class SignUp {
model = new SignUpVm(); //from sign_up_vm.ts (e.g)
}
//Copy this for personalDetails & createAccount
#Component({})
export class SocialProfiles {
#Input() model: SignUpVm;
}

Adding Bootstrap 3 popover breaks JQuery Validation Plugin

I have a form, which I'm validating using JQuery Validation plugin. Validation works file until I add a Bootstrap 3 popover to the text field with name "taskName" (the one being validated) (please see below) . When I add the popover to this text field, error messages are repeatedly displayed every time the validation gets triggered. Please see the code excerpts and screenshots below.
I've been trying to figure out what is happening, with no success so far. Any help will be greatly appreciated. Thanks in advance!
HTLM Excerpt
The popover content
<div id="namePopoverContent" class="hide">
<ul>
<li><small>Valid characters: [a-zA-Z0-9\-_\s].</small></li>
<li><small>Required at least 3 characters.</small></li>
</ul>
</div>
The form
<form class="form-horizontal" role="form" method="post" action="" id="aForm">
<div class="form-group has-feedback">
<label for="taskName" class="col-md-1 control-label">Name</label>
<div class="col-md-7">
<input type="text" class="form-control taskNameValidation" id="taskName" name="taskName" placeholder="..." required autocomplete="off" data-toggle="popover">
<span class="form-control-feedback glyphicon" aria-hidden="true"></span>
</div>
</div>
...
</form>
JQuery Validate plugin setup
$(function() {
//Overwriting a few defaults
$.validator.setDefaults({
errorElement: 'span',
errorClass: 'text-danger',
ignore: ':hidden:not(.chosen-select)',
errorPlacement: function (error, element) {
if (element.is('select'))
error.insertAfter(element.siblings(".chosen-container"));
else
error.insertAfter(element);
}
});
//rules and messages objects
$("#aForm").validate({
highlight: function(element) {
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-ok').addClass('glyphicon-remove');
},
success: function(element) {
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-remove').addClass('glyphicon-ok');
}
});
$('.taskNameValidation').each(function() {
$(this).rules('add', {
required: true,
alphanumeric: true,
messages: {
required: "Provide a space-separated name."
}
});
});
});
Bootstrap 3 popover setup
$('[data-toggle="popover"]').popover({
trigger: "focus hover",
container: "body",
html: true,
title: "Name Tips",
content: function() { return $('#namePopoverContent').html();}
});
The screenshots
First Edit
It seems I did not make my question clear, so here it goes my first edit.
I'm not using the popover to display the error messages of the validation. The error messages are inserted after each of the fields that fail validation, which is precisely what I want. Hence, this question does not seem to be a duplicate of any other question previously asked.
Regarding the popover, I just want to add an informative popover that gets displayed whenever the user either clicks the text field "taskName" or hovers the mouse over it. Its role is completely independent of the validation.
The question is, then, why adding the (independent) popover is making the validation plugin misbehave, as shown in the screenshots.
I had the very same issue a few days ago and the only solution I found was to use 'label' as my errorElement:.
Change the line errorElement: 'span' to errorElement: 'label' or simply removing the entire line will temporarily fix the issue. ('label' is the default. )
I am not completely sure what the JQ validate + BS popover conflict is, but I will continue to debug.
After some debugging I think I found the issue.
Both jQuery validate and bootstrap 3 popovers are using the aria-describedby attribute. However, the popover code is overwriting the value written by jQuery validate into that attribute.
Example: You have a form input with an id = "name", jQuery validate adds an aria-describedby = "name-error" attribute to the input and creates an error message element with id = "name-error" when that input is invalid.
using errorElement:'label' or omitting this line works because on line 825 of jquery.validate.js, label is hard-coded as a default error element selector.
There are two ways to fix this issue:
Replace all aria-describedby attributes with another attribute name like data-describedby. There are 4 references in jquery.validate.js. Tested.
or
Add the following code after line 825 in jquery.validate.js. Tested.
if ( this.settings.errorElement != 'label' ) {
selector = selector + ", #" + name.replace( /\s+/g, ", #" ) + '-error';
}
I will also inform the jQuery validate developers.
The success option should only be used when you need to show the error label element on a "valid" element, not for toggling the classes.
You should use unhighlight to "undo" whatever was done by highlight.
highlight: function(element) {
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-ok').addClass('glyphicon-remove');
},
unhighlight: function(element) {
$(element).closest('.form-group').removeClass('has-error').addClass('has-success');
$(element).parent().find('.form-control-feedback').removeClass('glyphicon-remove').addClass('glyphicon-ok');
}
(The success option could also be used in conjunction with the errorPlacement option to show/hide tooltips or popovers, just not to do the styling, which is best left to highlight and unhighlight.)
Also, I recommend letting the Validate plugin create/show/hide the error label element, rather than putting it the markup yourself. Otherwise, the plugin will create its own and ignore the one you've created.
In case you were unaware, you cannot use the alphanumeric rule without including the additional-methods.js file.