How to trigger change event on slate.js when testing with Selenium or Cypress - selenium

I'm trying to find a way to simulate a "change" event when doing E2E testing (with selenium or cypress) and slate.js
In our UI, when the user clicks on a word, we pop-up a modal (related to that word). I've been unable to make this happen as I can't get the change event to trigger

The Cypress input commands (e.g. cy.type() and cy.clear()) work by dispatching input and change events - in the case of cy.type(), one per character. This mimics the behavior of a real browser as a user types on their keyboard and is enough to trigger the behavior of most application JavaScript.
However, Slate relies almost exclusively on the beforeinput event (see here https://docs.slatejs.org/concepts/xx-migrating#beforeinput) which is a new browser technology and an event which the Cypress input commands don’t simulate. Hopefully the Cypress team will update their input commands to dispatch the beforeinput event, but until they do I’ve created a couple of simple custom commands which will trigger Slate’s input event listeners and make it respond.
// commands.js
Cypress.Commands.add('getEditor', (selector) => {
return cy.get(selector)
.click();
});
Cypress.Commands.add('typeInSlate', { prevSubject: true }, (subject, text) => {
return cy.wrap(subject)
.then(subject => {
subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'insertText', data: text }));
return subject;
})
});
Cypress.Commands.add('clearInSlate', { prevSubject: true }, (subject) => {
return cy.wrap(subject)
.then(subject => {
subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'deleteHardLineBackward' }))
return subject;
})
});
// slateEditor.spec.js
cy.getEditor('[data-testid=slateEditor1] [contenteditable]')
.typeInSlate('Some input text ');
cy.getEditor('[data-testid=slateEditor2] [contenteditable]')
.clearInSlate()
.typeInSlate('http://httpbin.org/status/409');
If you need to support other inputTypes, all of the inputTypes supported by Slate are listed in the source code for editable.tsx

Found a solution:
1) Add a ref to the Editor
<Editor
ref={this.editor}
/>
2) Add a document listener for a custom event
componentDidMount() {
document.addEventListener("Test_SelectWord", this.onTestSelectWord)
}
componentWillUnmount() {
document.removeEventListener("Test_SelectWord", this.onTestSelectWord)
}
3) Create a handler that creates a custom select event
onTestSelectWord(val: any) {
let slateEditor = val.detail.parentElement.parentElement.parentElement.parentElement
// Events are special, can't use spread or Object.keys
let selectEvent: any = {}
for (let key in val) {
if (key === 'currentTarget') {
selectEvent['currentTarget'] = slateEditor
}
else if (key === 'type') {
selectEvent['type'] = "select"
}
else {
selectEvent[key] = val[key]
}
}
// Make selection
let selection = window.getSelection();
let range = document.createRange();
range.selectNodeContents(val.detail);
selection.removeAllRanges();
selection.addRange(range)
// Fire select event
this.editor.current.onEvent("onSelect", selectEvent)
}
4) User the following in your test code:
arr = Array.from(document.querySelectorAll(".cl-token-node"))
text = arr.filter(element => element.children[0].innerText === "*WORD_YOU_ARE_SELECTING*")[0].children[0].children[0]
var event = new CustomEvent("Test_SelectWord", {detail: text})
document.dispatchEvent(event, text)

Cypress can explicitly trigger events: https://docs.cypress.io/api/commands/trigger.html#Syntax
This may work for you:
cy.get(#element).trigger("change")

Related

react-select Creatable: override default behavior upon pressing Enter key

I'm using react-select Creatable for my project, and I need to override its default behavior. Currently whenever the user types something in and presses Enter key, it gets added as a new option.
Is there a way to change it to "whenever the user types something in and presses Space bar, it gets added as a new option"?
If you just do
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
}}
...in your component, that'll stop the Enter key from doing anything.
To get the space bar to create a new entry, I think you might want to make a KeyDown handler:
function handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
if (e.key === "Space") {
handleChange();
}
// in component:
onKeyDown={handleKeyDown}
But I have not tried it yet.
Edit: Solution to keep Enter key from submitting a form the CreatableSelect is embedded within, while still allowing Enter to do react-select's default "create a new entry" behavior:
export function TagEditor({data, handleSave}) {
const [newValues, setNewValues] = useState(...our data mgr);
const [lastKey, setLastKey] = useState('');
const handleChange = (newValue) => {
setNewValues(newValue);
};
function handleKeyDown(e) {
/* React Select hack for pages where tags are inside a form element:
Default CreatableSelect functionality is that Enter key creates
the new tag. So we let that happen, but if they hit Enter again,
we kill it so the form doesn't submit. Yes, it's hack-y.
*/
switch (e.key) {
case 'Enter':
if (lastKey === 'Enter') {
e.preventDefault();
e.stopPropagation();
}
setLastKey('Enter');
break;
default:
setLastKey('');
}
}
return <CreatableSelect
aria-label="Add Tags"
autoFocus={true}
blurInputOnSelect={false}
defaultValue={...our data manager function}
inputId="tagmanager"
isClearable={false}
isMulti
name="my_field_name"
onBlur={(e) => {
e.preventDefault();
e.stopPropagation();
handleSave(newValues);
}}
onChange={handleChange}
onKeyDown={handleKeyDown}
openMenuOnFocus={true}
options={data}
placeholder="Select or type new..."
searchable
styles={our styles...}
/>;
}
Note this hack is needed because we're using stateless functional components, and react-select is still a state-full class component.

capture popup feature attribute in pop action

I've got an action function for my popup and I need to access the feature attributes from the pop up within the action function. In the code below I'd like to access {SAWID} -- I dont see it in the event parameter sent to the function.
var ContactsAction = {
title: "Get Contacts",
id: "contacts-this",
};
var template = {
// autocasts as new PopupTemplate()
title: "{Name}",
content: "{SAWID}",
actions: [ContactsAction]
};
// Event handler that fires each time an action is clicked.
view.popup.on("trigger-action", lang.hitch(this, this.Contacts));
// Executes when GetContacts is clicked in pop ups
Contacts: function (event) {
if (event.action.id === "contacts-this") {
//grab SAWID
}
}
Thanks
Pete
I found something that works, although its probably not the best way to do it:
there is an innerText property on the even.target object that includes all the text in the pop up. If I parse the innerText property I can get what I need: If anyone knows of a cleaner way please let me know. Thanks
// Executes when GetContacts is clicked in pop ups
Contacts: function (event) {
if (event.action.id === "contacts-this") {
var str = event.target.innerText;
var start = str.indexOf("Close") + 6;//"Close" always precedes my SAWID
var end = str.indexOf("Zoom") - 1;//"Zoom" is always after my SAWID
var SAWID = str.substring(start, end);
alert(SAWID);
}
}

Asynchrounous call issue in Angular 5

I am working with Angular5 , I have a big issue (please see the below code)
In simple words it is a asynchronous execution issue, how to reduce this
let confirmData = {
dlgHeader: 'Add',
msgTxt: 'Are you sure to Add Language!!',
eventName: 'locationmanagement_languages_assign'
};
this.confirmService.confirmData = confirmData;
(1) this.confirmationService.setShowConfirm(true);
(2) this.confirmAnchor.createConfirmation(ConfirmationComponent);
this.confirmService.getReturnValue()
.subscribe(
suc => {
if (suc.eventName == 'locationmanagement_languages_assign') {
this.assignLanguage(suc);
}
});
in above (1) line of code is responsible to create confirmation.(i have created custom confirmation component)
inside custom confirmation user can click CONFIRM/CANCEL buttons.
I want to stop (2) line code execution until user click CONFIRM/CANCEL buttons in custom confirmation component.
Now i am using as below in language.component.ts ,, but i am calling the getReturnValue() in ngOnInit().
I dont want to use ngOnInit() to get action from custom confirmation component either it is CONFIRM/CANCEL action
ngOnInit() {
this.getReturnValueEvent();
}
assignLanguageEvent() {
debugger;
this.requestedData = [];
for (let data of this.selectedAssignLanguages) {
this.requestedData.push({
id: data.value.id,
set_value: data.value.setValue
});
}
console.log('Requested Data::', this.requestedData);
let confirmData = {
dlgHeader: 'Add',
msgTxt: 'Are you sure to Add Language!!',
eventName: 'locationmanagement_languages_assign'
};
this.confirmService.confirmData = confirmData;
this.confirmationService.setShowConfirm(true);
this.confirmAnchor.createConfirmation(ConfirmationComponent);
}
getReturnValueEvent() {
this.subscription1 = this.confirmService.getReturnValue()
.subscribe(
suc => {
if (suc.eventName == 'locationmanagement_languages_assign') {
this.assignLanguage(suc);
}
}
);
}

Data table on click on dynamic controls

I have a jquery data table that I am populating from a drop down on change event. I have two check boxes in the data table and I am running an onclick on the check boxes. But on the first click the jquery does not fire only when I click it a second time does the jquery fire, also happens on switching pages.I added the .on() for the click, because I researched and saw that dynamic controls would work that way. Is there something I'm missing also to get this click function to work on first click? Below is some of my code.
data table click on check box control no jquery click event on first click
data table click on check box control on second click
$('#my-table').on('click', function () {
var i = -1;
$("input[id*='secondary']:checkbox").on("click", function () {
if ($(this).is(':checked')) {
i = selectedIds.indexOf($(this).val());
if (i === -1) {
selectedIds.push($(this).val());
}
CheckedSecondary(this);
}
else {
jQuery(this).closest("tr").css("background-color", "");
if (selectedIds.length > 0) {
i = selectedIds.indexOf($(this).val());
if (i != -1) {
selectedIds.splice(i, 1);
}
}
if (!primaryChecked)
$(this).closest('tr').find('input[type="checkbox"]').not(this).attr('disabled', false);
}
});
$("#my-table").find("input[id*='primary']:checkbox").on("click", function () {
if ($(this).is(':checked')) {
primaryChecked = true;
primaryID = this.value;
CheckedPrimary(this);
}
else {
primaryID = "";
primaryChecked = false;
$(this).closest('tr').find('input[type="checkbox"]').not(this).attr('disabled', false);
$('input:checkbox[id^="primary"]').each(function () {
if (!$(this).closest('tr').find('input[type="checkbox"]').is(':checked'))
$(this).attr('disabled', false);
});
jQuery(this).closest("tr").css("background-color", "");
}
});
});
You're attaching click handler inside another click handler which doesn't make sense.
Remove first click handler:
$('#my-table').on('click', function () {
});
Attach the click handler to the checkboxes as follows:
$('#my-table').on('click', "input[id*='secondary']:checkbox", function () {
});
and
$('#my-table').on('click', "input[id*='primary']:checkbox", function () {
});

Can we implement On key up filter option in Yii's cGridview?

I am currently trying to implement automatic filtering in Yii cGridview, By default it filters 'onclick', or 'enter' key press, But I need to change that event to "onkeyup"|
my code is like this
Yii::app()->clientScript->registerScript('search',"
$('.filters > td >input').keyup(function(){
$('#grid-id').yiiGridView('update', {
data: $(this).serialize()
});
return false;
});
");
?>
when I entered the first letter filtering occured, but after filtering and rendering the code fails.. please give me a solution.. Is there any php yii gridview extension which has filtering onkeyup
You need to change the way you attach the keyup listeners. After the gridview refreshed through AJAX, all elements inside the grid are replaced. So there's no keyup attached anymore. You can try something like:
$('body').on('keyup','.filters > td > input', function() {
$('#grid-id').yiiGridView('update', {
data: $(this).serialize()
});
return false;
});
#Michael Härtl's answer is right. But 2 Problem occur when you use this code.
1) When User Search in filter at that time, every time grid will be refresh so focus of input box will be lost.
2) When you search in one filter input and if you go to second input field field at that time first input box will be lost.
So now I have got the solution for that.
Set this java script code on your grid view.
Yii::app()->clientScript->registerScript('search', "
$('body').on('keyup','.filters > td > input', function() {
$(document).data('GridId-lastFocused',this.name);
data = $('#GridId input').serialize();
$('#GridId').yiiGridView('update', {
data: data
});
return false;
});
// Configure all GridViews in the page
$(function(){
setupGridView();
});
// Setup the filter(s) controls
function setupGridView(grid)
{
if(grid==null)
grid = '.grid-view tr.filters';
// Default handler for filter change event
$('input,select', grid).change(function() {
var grid = $(this).closest('.grid-view');
$(document).data(grid.attr('id')+'-lastFocused', this.name);
});
}
// Default handler for beforeAjaxUpdate event
function afterAjaxUpdate(id, options)
{
var grid = $('#'+id);
var lf = $(document).data(grid.attr('id')+'-lastFocused');
// If the function was not activated
if(lf == null) return;
// Get the control
fe = $('[name=\"'+lf+'\"]', grid);
// If the control exists..
if(fe!=null)
{
if(fe.get(0).tagName == 'INPUT' && fe.attr('type') == 'text')
// Focus and place the cursor at the end
fe.cursorEnd();
else
// Just focus
fe.focus();
}
// Setup the new filter controls
setupGridView(grid);
}
// Place the cursor at the end of the text field
jQuery.fn.cursorEnd = function()
{
return this.each(function(){
if(this.setSelectionRange)
{
this.focus();
this.setSelectionRange(this.value.length,this.value.length);
}
else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', this.value.length);
range.moveStart('character', this.value.length);
range.select();
}
return false;
});
}");
Add this line to your gridview widget code.
'afterAjaxUpdate'=>'afterAjaxUpdate',
For example:
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'GridId',
'afterAjaxUpdate'=>'afterAjaxUpdate',
));