How to mock a static function with Jasmine? - javascript

How can I spy on the focus() and select() functions within Jasmine (Unit testing)?
My function:
static nextFieldOnAlt(e) {
if (e.keyCode === 18) {
const focusableFields = Array.prototype.slice.call(document.querySelectorAll('input, textarea'));
const indexFocus = (focusableFields.indexOf(document.activeElement) + 1) % focusableFields.length;
const input = focusableFields[indexFocus];
input.focus();
input.select();
}
}
The unit test:
describe('nextFieldOnAlt function', function() {
it('check', function() {
const event = {
type: 'keypress',
keyCode: 18
};
const focusableFields = angular.element(['<input type="number">22</input>', '<textarea>Test</textarea>']);
spyOn(document, 'querySelectorAll').and.returnValue(focusableFields);
Utilities.nextFieldOnAlt(event);
expect(event.keyCode).toEqual(13);
});
});
I get the error
focus is not a function
Tried to add spy like the querySelectorAll, but that didn't work.
Any ideas, suggestions?

I'm guessing a bit, but this looks wrong to me:
const focusableFields = angular.element(['<input type="number">22</input>', '<textarea>Test</textarea>']);
I think you want an array of Angular elements, so I suspect if you change it to this:
const focusableFields = [angular.element('<input type="number">22</input>'), angular.element('<textarea>Test</textarea>')]);
Then, I suspect this code will return a proper array of Angular elements, and this code in your component will return valid objects with a focus field:
const focusableFields = Array.prototype.slice.call(document.querySelectorAll('input, textarea'));
I would strongly recommend stepping through code with a debugger to help address what is occuring.

Related

events with addEventListener

I'm tried to something like : Catch all events in my addEventListeners and then call my function calculateBill. I have a problem my events are lost. It is possible to pass the parameters for each addeventlister separately. I try to do it since few hours and i have no idea what's happening here.
const billInput = document.querySelector('#bill');
const percentageButton = document.querySelectorAll('.tip__values--item');
const numberOfPeople = document.querySelector('#people');
const tipAmount = document.querySelector('.result__amount--price');
const totalBill = document.querySelector('.result__total--price');
const reset = document.querySelector('button');
const catchBill = (e) => {
return e.target.value;
};
const catchPeople = (e) => {
return e.target.value;
};
const handleButtons = (e) => {
return e.target.textContent;
};
const calculateBill = (catchBill, catchPeople, handleButtons) => {
console.log(
'catchBill:',
catchBill,
'catchPeople:',
catchPeople,
'handleButtons:',
handleButtons
);
};
billInput.addEventListener('keyup', function (e) {
calculateBill(catchBill(e), catchPeople, handleButtons);
});
numberOfPeople.addEventListener('keyup', function (e) {
calculateBill(catchBill, catchPeople(e), handleButtons);
});
percentageButton.forEach((btn) => {
btn.addEventListener('click', function (e) {
calculateBill(catchBill, catchPeople, handleButtons(e));
});
});
I know that i can do this FrontedMentor challenge in other way but I'd like to know is this possible to do it that way. I know that problem is with parameters that i call in addeventlistener. How can i get my parameters in my calculateBill function with all events?
You'll need to cache the result of each event somewhere so that you can retrieve them later, or retrieve the value in every input each time any event takes place. It looks like calculateBill expects arguments to be strings, not functions, so passing catchBill, catchPeople, or handleButtons (which are functions) to it doesn't make sense. Consider something like:
// assign default values here if you want
let billValue;
let numPeople;
let percValue;
const handleBillChange = (e) => {
billValue = e.target.valueAsNumber;
calculateBill();
};
const handlePeopleChange = (e) => {
numPeople = e.target.valueAsNumber;
calculateBill();
};
const handlePercentageChange = (e) => {
percValue = e.target.textContent;
calculateBill();
};
billInput.addEventListener('keyup', handleBillChange);
numberOfPeople.addEventListener('keyup', handlePeopleChange);
billInput.addEventListener('keyup', handleBillChange);
// This is a NodeList, not a button. Consider naming the variable more precisely.
// percentageButtons, perhaps?
percentageButton.forEach((btn) => {
btn.addEventListener('click', handlePercentageChange);
});
While it'd be technically possible to store the previous events instead, and then do something like
billInput.addEventListener('keyup', function (e) {
calculateBill(catchBill(e), catchPeople(previousPeopleEvent), handleButtons(previousButtonEvent));
});
for all the inputs and buttons, that'd be extremely strange. Better to store just the values.

Run the action only when the selected text is stable

I would like to set a timer for the action function. The rule is: when the selected text does not change for 3 seconds, we run the function action with the selected text.
I tried the following code in the playground https://microsoft.github.io/monaco-editor/playground.html, it did not work. When I changed the selected text very quickly, their actions were not cancelled.
Could anyone help?
const editor = monaco.editor.create(document.getElementById('container'), {
value: '1+2+3+4+5+6+7',
language: 'javascript'
});
function action(x) {
console.log("action", x)
}
let myTimeout
editor.onDidChangeCursorSelection((e) => {
clearTimeout(myTimeout)
console.log("cleared", myTimeout)
let selectedText = editor.getModel().getValueInRange(editor.getSelection())
if (selectedText != "") { // if we are already in this event, the selected text must change
myTimeout = setTimeout(action(selectedText), 3000);
console.log("set", myTimeout, selectedText)
}
})
Edit 1: My component is a class component, so I cannot use hook calls.
You can use lodash's debounce to achieve the effect you want. And use useCallback to make sure you get the same instance of function. I think an implementation like this might work:
import _ from 'lodash';
...
const debouncedAction = _.debounce(action, 3000).bind(this);
editor.onDidChangeCursorSelection((e) => {
let selectedText = editor.getModel().getValueInRange(editor.getSelection())
if (selectedText != "") { // if we are already in this event, the selected text must change
debouncedAction(selectedText);
}
})
I used this as a reference.
setTimeout takes function as input, while you have called it. I think the following code works well.
const editor = monaco.editor.create(document.getElementById('container'), {
value: '1+2+3+4+5+6+7',
language: 'javascript'
});
function action(x) {
console.log("action", x)
}
let myTimeout
editor.onDidChangeCursorSelection((e) => {
clearTimeout(myTimeout)
let selectedText = editor.getModel().getValueInRange(editor.getSelection())
if (selectedText != "") {
// "action" is called inside a function
myTimeout = setTimeout(() => action(selectedText), 3000);
}
})

`e.getModifierState is not a function` error on triggered keyup event

I am trying to detect whether the client has their CapsLock enabled by temporarily creating a hidden input field, triggering a keyup event on that input, and then checking the boolean value of e.getModifierState('CapsLock').
The function I've written is below, which currently throws the error Uncaught TypeError: e.getModifierState is not a function on the line which includes e.getModifierState and I'm struggling to diagnose why this is occurring. I'm open to any help here!
let checkCaps = () => {
let tempInput = document.createElement('input');
let keyupEvent = new Event('keyup');
tempInput.type = 'hidden';
tempInput.id = 'check-caps';
let capsOn = false;
tempInput.addEventListener('keyup', e => {
e.which = 65;
capsOn = e.getModifierState('CapsLock');
})
tempInput.dispatchEvent(keyupEvent);
tempInput.remove();
return capsOn;
}
checkCaps(); // testing the function
The class Event is the base class for all specific event classes.
If you want to generate a keyboard event, then you must use the KeyboardEvent class like this:
let checkCaps = () => {
let tempInput = document.createElement('input');
let keyupEvent = new KeyboardEvent('keyup');
tempInput.type = 'hidden';
tempInput.id = 'check-caps';
let capsOn = false;
tempInput.addEventListener('keyup', e => {
e.which = 65;
capsOn = e.getModifierState('CapsLock');
})
tempInput.dispatchEvent(keyupEvent);
tempInput.remove();
return capsOn;
}
checkCaps(); // testing the function
If tested this and the code runs, but I always get false for getModifierState('CapsLock')
It seems that for you must specify the current value self-generated KeyboardEvents. But this isn't what you want.
It seems that other people had the same issue and there is no solution without real user input.
See: How to detect Caps Lock state on page load (that is, not on keypress) with JavaScript?

JavaScript: Quick return if function returns null

Is there a way to shorten this fragment of code?
const result = getResult();
if (!result) {
return;
}
// Work with result
I keep having lots of these in my code and would love to do something like:
const result = getResult() || return;
// Work with result
EDIT:
I only want convertable inputs to be persisted.
const parseInput = (input: string): void => {
const convertedInput = convert(input);
if (!convertedInput) {
return;
}
persist(convertedInput);
}
I know I could call the converter twice. But I want to avoid that:
const parseInput = (input: string): void => {
if (!convert(input)) {
return;
}
persist(convert(input));
}
Your code is as good as it gets, however, if you want to experiment a bit with the functional style, you can wrap the value into a "monad", which would invoke attached functions only if the value is non-zero. Here's a toy implementation:
function maybe(x) {
return {
value: x,
apply(fn) {
if (this.value)
this.value = fn(this.value)
return this;
}
}
}
With this maybe, your example would look like:
const parseInput = input => maybe(convert(input)).apply(persist)
See the Oliver's answer for a more serious approach.
You can do this
const result = "default value" || getResult();
If getResult is null or not defined then you'll get result as "default value". If that's what you want
function getResult() {
return null;
}
const result = "okay" || getResult();
console.log(result)
And when getResult is not defined you get
const result = "okay" || getResult();
console.log(result)
Basically, the syntax is
null || undefined || null || 0 || "okay" || "defined" // "okay"
It goes from left to right and picks the most relevant value
I don't really know if this answer will give you something that you'll be happy with, but it seems to me to present a potential solution to the problem of handling unknown results.
Maybes are structures which have this kind of checking built-in. the .map() below will not be called if there is no value in the Maybe, so the code which consumes it does not need to check whether a value is present.
This does mean that you have to change the way in which you handle these values however, and, unless you want to write your own, it means using a library. As such this is hardly an ideal solution, but I hope it gives an option at least.
const { None, Some } = Monet;
const getResult = () => Math.random() > 0.5
? None()
: Some(1);
const test = getResult()
.map(x => x + 2);
console.dir(test.val);
<script src="https://cdn.jsdelivr.net/npm/monet#0.9.0/dist/monet.min.js"></script>

Is there a Javascript equivalent of Ruby's andand?

In trying to make my Javascript unobtrusive, I'm using onLoads to add functionality to <input>s and such. With Dojo, this looks something like:
var coolInput = dojo.byId('cool_input');
if(coolInput) {
dojo.addOnLoad(function() {
coolInput.onkeyup = function() { ... };
});
}
Or, approximately equivalently:
dojo.addOnLoad(function() {
dojo.forEach(dojo.query('#cool_input'), function(elt) {
elt.onkeyup = function() { ... };
});
});
Has anyone written an implementation of Ruby's andand so that I could do the following?
dojo.addOnLoad(function() {
// the input's onkeyup is set iff the input exists
dojo.byId('cool_input').andand().onkeyup = function() { ... };
});
or
dojo.byId('cool_input').andand(function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
I don't know Dojo, but shouldn't your first example read
dojo.addOnLoad(function() {
var coolInput = dojo.byId('cool_input');
if(coolInput)
coolInput.onkeyup = function() { ... };
});
Otherwise, you might end up trying to access the element before the DOM has been built.
Back to your question: In JavaScript, I'd implement andand() as
function andand(obj, func, args) {
return obj && func.apply(obj, args || []);
}
Your example could then be written as
dojo.addOnLoad(function() {
andand(dojo.byId('cool_input'), function() {
this.onkeyup = function() { ... };
});
});
which isn't really that much shorter than using the explicit if statement - so why bother?
The exact syntax you want is not possible in JavaScript. The way JavaScript executes would need to change in a pretty fundamental fashion. For example:
var name = getUserById(id).andand().name;
// ^
// |-------------------------------
// if getUserById returns null, execution MUST stop here |
// otherwise, you'll get a "null is not an object" exception
However, JavaScript doesn't work that way. It simply doesn't.
The following line performs almost exactly what you want.
var name = (var user = getUserById(id)) ? user.name : null;
But readability won't scale to larger examples. For example:
// this is what you want to see
var initial = getUserById(id).andand().name.andand()[0];
// this is the best that JavaScript can do
var initial = (var name = (var user = getUserById(id)) ? user.name : null) ? name[0] : null;
And there is the side-effect of those unnecessary variables. I use those variables to avoid the double lookup. The variables are mucking up the context, and if that's a huge deal, you can use anonymous functions:
var name = (function() {return (var user = getUserById(id)) ? user.name : null;})();
Now, the user variable is cleaned-up properly, and everybody's happy. But wow! what a lot of typing! :)
You want dojo.behavior.
dojo.behavior.add({
'#cool_input': {
onKeyUp: function(evt) { ... }
}
});
How about something like this:
function andand(elt, f) {
if (elt)
return f(elt);
return null;
}
Call like this:
andand(dojo.byId('cool_input'), function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
As far as I know there isn't a built-in JavaScript function that has that same functionality. I think the best solution though is to query by class instead of id and use dojo.forEach(...) as you will be guaranteed a non-null element in the forEach closure.
You could always use the JavaScript equivalent:
dojo.byId('cool_input') && dojo.byId('cool_input').whateverYouWantToDo(...);
I've never used dojo, but most javascript frameworks (when dealing with the DOM) return the calling element when a method is called from the element object (poor wording, sorry). So andand() would be implicit.
dojo.addOnLoad(function() {
dojo.byId('cool_input').onkeyup(function(evt) { /*event handler code*/
});
});
For a list:
Array.prototype.andand = function(property, fn) {
if (this.filter(property).length > 0) this.map(fn);
}

Categories