I've been trying to digest the Webdriver spec and its more friendly version. And I'm having trouble understanding what do these words mean (in the description of the 'Element Send Keys' command):
The key input state used for input may be cleared mid-way through "typing" by sending the null key, which is U+E000 (NULL)
I had several ideas of what it might mean, I mention some below as a sort of evidence of my 'prior research'*).
Could somebody, please, explain what does it mean and, if possible, give an example, preferably in JavaScript?
*Attempts to figure it out myself:
I thought, one may skip calling releaseActions() if he previously pressed, say, the Shift key, like:
await browser.performActions([
{
type: 'key',
id: 'key1',
actions: [
{ type: 'keyDown', value: '\u0010', },
],
},
]);
await browser.elementSendKeys(elemUUID, '\uE000ABC');
But no, the shift key was still pressed, when the elementSendKeys() was called.
Also I thought the null character clears text in the element, no, it doesn't.
My original idea about what the words mean was correct, only the example to test it had errors.
From an abstract point of view, one need to understand, how the spec defines the Actions API. Simplified, it's this:
In the Webdriver implementation environment there are certain input sources, like null, keyboard, pointer (probably others). And each input source has an associated input state object, which (simplified) is the current state of the source, like (for a keyboard source) which keys are now being held down, or (for a pointer source) where is the cursor now on the screen etc.
So if one performs a keyDownkeyboard action the key will remain pressed until a keyUp action for the key is performed, or the state is reset.
The null unicode code point may be used in string literals to reset the state of the keyboard input source, namely release all currently held down keys.
Here is an example:
// elemUUID can be taken from the return values
// of methods like `findElement()` etc.
await browser.performActions([
{
type: 'key',
id: 'key1',
actions: [
// \u0008 is used for a Shift key in Webdriver
// I don't actually know, where it's specified
{ type: 'keyDown', value: '\u0008', },
],
},
]);
// Here the remote end (the automated browser)
// will type a capital A, since the shift key
// is still being pressed
await browser.elementSendKeys(elemUUID, 'a');
// One can include the null char somewhere in
// the string, and all subsequent chars will be
// 'pressed' without the pressed Shift
// This will type Aa
await browser.elementSendKeys(elemUUID, 'a\uE000a');
In my question's example errors were: the wrong unicode code point value for the Shift key, and the capital letters in the text argument to the elementSendKeys() method, which made the driver use the Shift key, despite the null character had been provided, i.e. in:
await browser.elementSendKeys(elemUUID, '\uE000A');
The shift key will always be pressed, since 'A' implies it.
Related
For my test, I am expecting it to start with a value of length 39 (maxlength: 40), and when I press a key (ie: 'a'), the length should then be 40. However, anything I try or look up doesn't seem to allow me to trigger a keypress/keydown/keyup event.
My Component:
<q-input
v-model="name"
class="my-class"
maxlength="40"
\>
My Test:
it('Input should change', async() => {
let wrapper = mount(Component, { name: 'M'.repeat(39) })
let name = wrapper.find('.my-class')
console.log(name.vm.$options.propsData.value) // prints 39 M's as expected
name.trigger('click') // I try clicking the input. I've also tried triggering 'focus'
await wrapper.vm.$nextTick() // I've also tried with and without this
// I've also tried await name.vm.$nextTick()
name.trigger('keydown', { key: 'a' }) // According to their testing docs, this should press 'a'
// I've also tried name.trigger('keyup', { key: 'a' })
// and name.trigger('keypress', { key: 'a' })
await wrapper.vm.$nextTick()
console.log(name.vm.$options.propsData.value) // prints 39 M's without the additional 'a'
expect(name.vm.$options.propsData.value.length).toBe(40)
})
Why does not the value change after triggering keydown?
In short: you can't change the value of input/texarea with dispatching KeyboardEvent programmatically.
How actually do chars come into input? On MDN you can find the description of Keyboardevent sequence (assuming that preventDefault is not called):
A keydown event is first fired. If the key is held down further and the key produces a character key, then the event continues to be emitted in a platform implementation dependent interval and the KeyboardEvent.repeat read only property is set to true.
If the key produces a character key that would result in a character being inserted into possibly an <input>, <textarea> or an element with HTMLElement.contentEditable set to true, the beforeinput and input event types are fired in that order. Note that some other implementations may fire keypress event if supported. The events will be fired repeatedly while the key is held down.
A keyup event is fired once the key is released. This completes the process.
So, keydown leads to input event by default. But that is true only for trusted events:
Most untrusted events will not trigger default actions, with the exception of the click event... All other untrusted events behave as if the preventDefault() method had been called on that event.
Basically trusted events are those initiated by a user and untrusted events are initiated with a script. In most browsers, each event has an attribute isTrusted indicating if the event is trusted or not.
And how to test KeyboardEvents on inputs then?
Well, it depends on what is going on in your KeyboardEvent handler. E.g. if you call preventDefault in certain conditions then you can check if it was called or not in a test using a spy. Here is an example with sinon as a spy and chai as assertion library.
const myEvent = new KeyboardEvent('keydown', { key: 'a' })
sinon.spy(myEvent, 'preventDefault')
await wrapper.find('input').element.dispatchEvent(myEvent)
expect(myEvent.preventDefault.calledOnce).to.equal(true)
But sometimes you even don't need a KeyboardEvent handler. E.g. in your question you write about an input with maximum value length 40 characters. Actually you can just use maxlength HTML attribute for the input to prevent input of more than 40 characters. But just for a study example, we can just cut the value in input handler (it will control the length not only when a user types but also when a user copy-and-pastes a string):
onInput (event) {
event.target.value = event.target.value.substr(0, 40)
...
}
The test for this may look like this:
await wrapper.find('input').setValue('M'.repeat(41))
expect(wrapper.find('input').element.value).to.equal('M'.repeat(40))
So here, we directly change the value of input, not by KeyboardEvent.
I don't know where dialog comes from, but should you not be searching for the element inside wrapper?
wrapper.find('.my-class')
Also most examples I see use mount instead of factory
const wrapper = mount(component)
Here is an example from the docs:
it('Magic character "a" sets quantity to 13', () => {
const wrapper = mount(QuantityComponent)
wrapper.trigger('keydown', {
key: 'a'
})
expect(wrapper.vm.quantity).toBe(13)
})
})
Perhaps it's just a misunderstanding on my side, but I thought the callback for createGenericItem in the PeoplePicker (https://developer.microsoft.com/en-us/fabric#/components/peoplepicker) was used to handle input, that cannot be matched to any of the available items, and then give the possibility to create an adhoc item for this. But, whatever I tried, the callback is never called.
I made a simple pen here for the issue: https://codepen.io/anon/pen/daGPWe?editors=0010
In the example, there are two items, Peter and Maria. If you type something different (and hit enter, tab, space, whatever) I'd expect the createGenericItem callback to be called, but it isn't.
What am I doing wrong? Or is there a misunderstanding of the purpose of this callback? I'm unable to find an example anywhere.
Regarding
but I thought the callback for createGenericItem in the PeoplePicker
(https://developer.microsoft.com/en-us/fabric#/components/peoplepicker)
was used to handle input
that's correct. In order to trigger IBasePickerProps.createGenericItem function, IBasePickerProps.onValidateInput function needs to be provided with ValidationState.valid as a return value, for example:
<NormalPeoplePicker
createGenericItem={this.createGenericItem}
onValidateInput={this.handleValidateInput}
selectedItems={this.state.selectedItems}
onResolveSuggestions={this.handleResolveSuggestions}
onChange={this.handleChange}
/>
private handleValidateInput(input: string) {
return ValidationState.valid;
}
private createGenericItem(input: string, validationState: ValidationState) {
return { text: "Unknown person", state: validationState };
}
This demo demonstrates it, once tab or enter key is clicked and value cannot be resolved to any of the available items, Unknown person item is getting displayed
This looks to be answered many different times but I can't seem to get it working with my implementation. I am trying to format and limit the data in a sap.m.Input element. I currently have the following:
var ef_Amount = new sap.m.Input({
label: 'Amount',
textAlign: sap.ui.core.TextAlign.Right,
value: {
path: '/amount',
type: 'sap.ui.model.type.Currency'
}
});
The first problem is that it kind of breaks the data binding. When I inspect the raw data (with Fiddler) submitted to the server it is an array like this:
"amount": [1234.25,null]
The server is expecting a single number and as such has issues with the array.
When I use the following, the binding works as desired but no formatting is performed.
var ef_Amount = new sap.m.Input({
label: 'Amount',
textAlign: sap.ui.core.TextAlign.Right,
value: '{/amount}'
});
The second problem is that the data entered is not limited to numbers.
I have tried using sap.m.MaskedInput instead but I don't like the usage of the placeholders because I never know the size of the number to be entered.
And lastly, it would be nice if when focus is placed on the input field, that all formatting is removed and re-formatted again when focus lost.
Should I be looking into doing this with jQuery or even raw Javascript instead?
Thank you for looking.
the array output is a normal one according to documentation. So you need to teach your server to acccept this format or preprocess data before submission;
this type is not intended to limit your data input;
good feature, but ui5 does not support this, because the Type object has no idea about control and it's events like "focus" it only deals with data input-output. So you have to implement this functionality on your own via extending the control or something else.
I would suggest using amount and currency separately. It's likely that user should be allowed to enter only valid currency, so you can use a combobox with the suggestions of the available currencies.
So, after much work and assistance from #Andrii, I managed to get it working. The primary issue was that onfocusout broke the updating of the model and the change event from firing. Simply replacing onfocusout with onsapfocusleave took care of the issues.
The final code in the init method of my custom control:
var me = this;
var numberFormat = sap.ui.core.NumberFormat.getCurrencyInstance({maxFractionDigits: 2});
me.addEventDelegate({
onAfterRendering: function() {
// for formatting the figures initially when loaded from the model
me.bindValue({
path: me.getBindingPath('value'),
formatter: function(value) {
return numberFormat.format(value);
}
});
},
onfocusin: function() {
// to remove formatting when the user sets focus to the input field
me.bindValue(me.getBindingPath('value'));
},
onsapfocusleave: function() {
me.bindValue({
path: me.getBindingPath('value'),
formatter: function(value) {
return numberFormat.format(value);
}
});
}
});
I just started using dgrid, and going through the dTunes sample, I'm unable to find the id associated with each row in the list. This is pretty remedial on my part, but how would I also get the id I sent from the datasource?
define([
'require',
'dgrid/List',
'dgrid/OnDemandGrid',
'dgrid/Selection',
'dgrid/Keyboard',
'dgrid/extensions/ColumnHider',
'dojo/_base/declare',
'dojo/_base/array',
'dojo/Stateful',
'dojo/when',
'dstore/RequestMemory',
'put-selector/put',
'dojo/domReady!'
], function (require, List, Grid, Selection,
Keyboard, Hider, declare, arrayUtil, Stateful,
when, RequestMemory, put) {
var cstsNode = put(listNode, 'div#cstsCars');
...
var cstsList = new TunesList({}, cstsNode);
var dataCSTS = new RequestMemory({ target: require.toUrl('./dataCSTS.json') });
...
dataCSTS.fetch().then(function (cars) {
cstsCars = arrayUtil.map(cars, pickField('Description'));
cstsCars.unshift('All (' + cstsCars.length + ' CSTS Cars' + (cstsCars.length !== 1 ? 's' : '') + ')');
cstsList.renderArray(cstsCars);
});
...
cstsList.on('dgrid-select', function (event) {
var row = event.rows[0];
console.log(row.id); // shows row number. How do I get the real id or other fields?
console.log(row.data); // shows row text that is displayed ("sample text 1")
console.log(row.data.id); // undefined
});
Here is a snippet of sample data like I'm supplying:
[{"id":"221","Description":"sample text 1"},
{"id":"222","Description":"sample text 2"},
{"id":"223","Description":"sample text 3"}]
I'd like to see the id. Instead, row.id returns 1,2 and 3, ie the row numbers (or id dgrid created?).
You haven't really shown a complete example, but given that you're using a store anyway, you'd have a much easier time if you let dgrid manage querying the store for you. If you use dgrid/OnDemandList (or dgrid/List plus dgrid/extensions/Pagination), you can pass your dataCSTS store to the collection property, it will render it all for you, and it will properly pick up your IDs (since Memory, and RequestMemory by extension, default to using id as their identity property).
The most appropriate place to do what you're currently doing prior to renderArray would probably be in the renderRow method if you're just using List, not Grid. (The default in List just returns a div with a text node containing whatever is passed to it; you'll be passing an object, so you'd want to dig out whatever property you actually want to display, first.)
If you want a header row, consider setting showHeader: true and implementing renderHeader. (This is false in List by default, but Grid sets it to true and implements it.)
You might want to check out the Grids and Stores tutorial.
I think the problem might be that I was modeling my code based on the dTunes sample code, which has 3 lists that behave a little differently than a regular grid.
For now, I'm using the cachingStore that is available in the lists. So the way I get the id:
cstsList.on('dgrid-select', function (event) {
var row = event.rows[0];
var id = storeCSTS.cachingStore.data[row.id - 1].id; // -1 because a header was added
console.log(id);
});
I'm not sure whether this will work if I ever try to do sorting.
I have a bunch of NumberTextBoxes which I created programmatically. Now, I have to compute some values on keypress so I call a function for that... Looks something like this:
...
<head>
...
function setNumTextBox() {
...
var val = {
name: "mytextbox",
onKeyPress: keyPressFxn
}
new dijit.form.NumberTextBox(val, "mytextbox");
}
function keyPressFxn(evt) {
// do the processing here
}
...
Now, my problem is this. The user types, say, "12". When the user types in "1", dijit.byId("mytextbox").attr("value") is "". When the user types in the "2", the value is now "1". In short, the value (I've also tried the "displayedValue" attribute) that I get in the function is not the latest.
I tried to use "intermediateChanges: true" but it doesn't work for the text box for this case. Now, I went ahead and used the "onChange" event instead of "onKeyPress". I get the correct values but... I don't know what called the onChange event.
Questions:
If I am to use the "onKeyPress" event, how do I get the latest value? Meaning, if I type in "123", I'll get "123" when I access the field's value/displayedValue.
If I use "onChange", how do I get the id of the element that fired the onChange event? (it's not as simple as evt.target.id right? Tried that one and it didn't work)
Or, do I have to use a combination of the 2? Like, get the id of the caller via onKeyPress then get the updated value that the user typed via onChange?
Thanks!
This should get you close to what you wanted. You may need to track the dojo.keys.CLEAR and dojo.keys.DELETE constants as well.
function keyPressFxn(evt) {
var value = this.value;
if (dojo.keys.BACKSPACE == evt.keyCode ) {
console.log(value.substr(0, value.length - 1));
} else {
console.log(value + String.fromCharCode(evt.charCode));
}
}
I would seriously consider using the onkeyup event instead. found this in another stackoverflow answer:
Because you are using key-press event. key press has 3 phase:
Key down: when key is press
Key hold: key is hold down
Key up: key is release In your case, problem can be solved by using keyup event
Not sure if this just because the API has changed in the last 2 years - but:
mytextbox.get("value") gives the up to date value as the event fires
(BEWARE tho: mytextbox.value doesn't give the latest value until after the textbox has been validated)