I'm fairly new to the MochaJS testing framework and I'm trying to validate a dropdown menu in response to user input. I have a component with the following method:
inside my Search component:
createQueryInputEl() {
const inputEl = document.createElement('input');
Object.assign(inputEl, {
type: 'search',
name: 'query',
autocomplete: 'off',
});
inputEl.addEventListener('input', event =>
this.onQueryChange(this, event.target.value));
The onQueryChange() function creates a dropdown by creating an li element for each result like this:
createResultsEl(results) {
const fragment = document.createDocumentFragment();
results.forEach((result) => {
const el = document.createElement('li');
Object.assign(el, {
className: 'result',
textContent: result.text,
});
// Pass the value to the onSelect callback
el.addEventListener('click', (event) => {
const { onSelect } = this.options;
if (typeof onSelect === 'function') onSelect(result.text);
this.displaySelected(this.inputEl, result.text)
});
fragment.appendChild(el);
});
return fragment;
}
In MochaJS, I'm trying to test the population of that dropdown like this:
it('should populate input on value selection', () => {
const input = document.querySelector('input');
input.value = "New"
input.dispatchEvent(new Event('input'));
const results = document.querySelectorAll('.result');
expect(true).toBeTruthy();
})
The problem is that results keeps coming up empty. When I enter that code into the console, results is a NodeList with each li element of the result.
How do I get MochaJS to simulate this behavior?
Related
As the title suggest I am trying to apply a class/styling to a specific element (in this case an SVG path) based on its corresponding button.
I have a map of the UK that is split up into regions and corresponding buttons to those regions, and I am trying to style a region based on hovering over a button.
So far I have a list of buttons and regions:
// England Buttons
const grBtn = document.querySelector(".eng-gr");
const seBtn = document.querySelector(".eng-se");
const eeBtn = document.querySelector(".eng-ee");
const emBtn = document.querySelector(".eng-em");
const yhBtn = document.querySelector(".eng-yh");
const neBtn = document.querySelector(".eng-ne");
const nwBtn = document.querySelector(".eng-nw");
const wmBtn = document.querySelector(".eng-wm");
const swBtn = document.querySelector(".eng-sw");
// England Region
const grRgn = document.querySelector(".greater-london");
const seRgn = document.querySelector(".south-east-england");
const eeRgn = document.querySelector(".east-england");
const emRgn = document.querySelector(".east-midlands");
const yhRgn = document.querySelector(".yorkshire-humber");
const neRgn = document.querySelector(".north-east-england");
const nwRgn = document.querySelector(".north-west-england");
const wmRgn = document.querySelector(".west-midlands");
const swRgn = document.querySelector(".south-west-england");
I know I can manually use a function for each button/region like so:
grBtn.addEventListener("mouseover", function () {
grRgn.classList.add("rgn-hover");
});
grBtn.addEventListener("mouseout", function () {
grRgn.classList.remove("rgn-hover");
});
But I am trying to figure out how I can do it with one (or a few) functions instead of each button/region (i will eventually be adding the rest of the UK).
Codepen of Project: https://codepen.io/MartynMc/pen/OJZWWer
Just an idea but if you can edit the HTML part and trasform this:
Greater London
Into this
Greater London
So for each button you specify the SVG class to highlight, you can just use event delegation on the buttons container like this:
let container = document.querySelector(".buttons");
container.addEventListener("mouseover", function (e) {
let ref = e.target.dataset.highlight;
if (ref) {
document.querySelector("." + ref).classList.add("rgn-hover");
}
});
container.addEventListener("mouseout", function (e) {
let ref = e.target.dataset.highlight;
if (ref) {
document.querySelector("." + ref).classList.remove("rgn-hover");
}
});
And that's all the JS code you need.
The parent receives the event from its children and if the child contains a data-highlight attribute, it finds a node with that class name and adds/removes the css class.
try this:
let arr = [
{ btn: grBtn, rgn: grRgn },
{ btn: seBtn, rgn: seRgn },
{ btn: eeBtn, rgn: eeRgn },
{ btn: emBtn, rgn: emRgn },
{ btn: yhBtn, rgn: yhRgn },
{ btn: neBtn, rgn: neRgn },
{ btn: nwBtn, rgn: nwRgn },
{ btn: wmBtn, rgn: wmRgn },
{ btn: swBtn, rgn: swRgn },
];
arr.forEach((item) => {
item.btn.addEventListener("mouseover", function () {
item.rgn.classList.add("rgn-hover");
});
item.btn.addEventListener("mouseout", function () {
item.rgn.classList.remove("rgn-hover");
});
});
When I add "Item" to localStorage, and when I refresh the page, everything is fine. But, when I want to add new item, everything starts from the beginning in "ToDo" localStorage.
let ToDo = [];
let addNew = (event) => {
let onelist = {
id: Date.now(),
title: title.value,
description: desc.value,
cat: [],
}
for (var checkbox of checkboxes) {
if (checkbox.checked)
onelist['cat'].push(checkbox.value);
}
ToDo.push(onelist);
window.localStorage.setItem('ToDo', JSON.stringify(ToDo));
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('AddItem').addEventListener('click', addNew);
});
you need to call your localStorage item and edit it
ex:
const ToDo = JSON.parse(localStorage.getItem("ToDo"))
// now work on the ToDo like ToDo.push(...)
// after done save it again to the storage
localStorage.setItem('ToDo', JSON.stringify(ToDo));
what your doing is you're recreating the storage all over again every addNew call
Converting my comment to answer because it is a little more complex
You need to see if there is something in localStorage and if there is, use it.
You cannot JSON.parse undefined or null so a test is needed
const lcTodo = localStorage.getItem("ToDo");
ToDo = lcTodo ? JSON.parse(lcTodo) : [];
It can live in the load
let ToDo;
let addNew = (event) => {
let onelist = {
id: Date.now(),
title: title.value,
description: desc.value,
cat: [],
}
for (var checkbox of checkboxes) {
if (checkbox.checked)
onelist['cat'].push(checkbox.value);
}
ToDo.push(onelist);
window.localStorage.setItem('ToDo', JSON.stringify(ToDo));
}
window.addEventListener('load', () => {
const lcTodo = localStorage.getItem("ToDo");
ToDo = lcTodo ? JSON.parse(lcTodo) : [];
document.getElementById('AddItem').addEventListener('click', addNew);
});
You never read from local storage. Maybe you could do something like this:
// ... your other code
document.addEventListener('DOMContentLoaded', () => {
ToDo = JSON.parse(window.localStorage.getItem('ToDo'));
document.getElementById('AddItem').addEventListener('click', addNew);
});
I've been trying to create a custom widget for Netlify CMS to allow for key-value pairs to be inserted. However there are a few things going wrong, I'm thinking they might be related so I'm making a single question about them.
This is my first custom widget, and I’m mostly basing this on the official tutorial: https://www.netlifycms.org/docs/custom-widgets/
I’m using a Map as the value, but when I add a new element to the map, and then call the onChange(value) callback, nothing seems to happen. However, if I change it to onChange(new Map(value)) it does update. It seems that the onChange callback requires a new object?
Secondly, the value doesn’t seem to be actually saved. When I fill in other widgets and refresh the page, it then asks to restore the previous values. However it doesn’t restore the map, while restores the other values just fine.
And lastly, I get uncaught exception: Object like a second after I change anything to the map. My guess is that Netlify CMS is trying to save the map (debouncing it for a second so it doesn’t save every letter I type), but fails and throws that exception. That would explain the previous problem (the non-saving one).
My complete code for the custom widget currently is:
var IngredientsControl = createClass({
getDefaultProps: function () {
return {
value: new Map()
};
},
addElement: function (e) {
var value = this.props.value;
value.set("id", "Description");
//is.props.onChange(value);
this.props.onChange(new Map(value));
},
handleIdChange: function (oldId, newId) {
console.log(oldId, newId);
var value = this.props.value;
var description = value.get(oldId);
value.delete(oldId);
value.set(newId, description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
handleDescriptionChange: function (id, description) {
console.log(id, description);
var value = this.props.value;
value.set(id.toLowerCase(), description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
render: function () {
var value = this.props.value;
var handleIdChange = this.handleIdChange;
var handleDescriptionChange = this.handleDescriptionChange;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('input', { type: 'text', value: id, onChange: function (e) { handleIdChange(id, e.target.value); } }),
h('input', { type: 'text', value: description, onChange: function (e) { handleDescriptionChange(id, e.target.value); } })
);
items.push(li);
}
return h('div', { className: this.props.classNameWrapper },
h('input', {
type: 'button',
value: "Add element",
onClick: this.addElement
}),
h('ul', {}, items)
)
}
});
var IngredientsPreview = createClass({
render: function () {
var value = this.props.value;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('span', {}, id),
h('span', {}, ": "),
h('span', {}, description)
);
items.push(li);
}
return h('ul', {}, items);
}
});
CMS.registerWidget('ingredients', IngredientsControl, IngredientsPreview);
What am I doing wrong?
Thanks!
I solved this by using immutable-js's map: https://github.com/immutable-js/immutable-js
I have the following code which sets a custom handler to each element (which can be a button or a li) click.
componentWillReceiveProps(nextProps) {
this._setCallbackToItemLinks(nextProps);
}
_setCallbackToItemLinks(props) {
const items = props && props.items;
items.forEach(item => {
item.links.forEach(link => {
this._setCallbackToLink(link);
});
});
}
_setCallbackToLink(link) {
link.onClickFromProps = link.onClick;
link.onClick = e => this._onItemClick(e, link.onClickFromProps);
}
onItemClick(e, onClickFromProps) {
console.log("Custom click event handler called");
if (onClickFromProps)
onClickFromProps(e);
}
I want to unit test the above functionality using Jest.
const component= mount(<Comp />);
const mock2 = jest.fn();
component.setProps({ items: [{links: [{ name: 'Test', key: 'Test', url: 'Test', onClick: mock2 }]}] });
const link= component.find('.nav-link');
navLink.simulate('click');
expect(mock2.mock.calls.length).toBe(1);
the test is passing, however I am not getting the console.log statement written in onItemClick method. When I try to debug this test, it is not even going into onItemClick after the simulate click is called.
the navLink does show an <a> tag.
when I log navLink.props().onClick, it shows [Function: bound]
I have this form in my component :
this.state.customFieldsArray.length > 0 &&
(
<Form
ref="customForm"
type={this.customForm}
options={this.customFormOptions}
/>
)}
I want to add more options to the form (so it render more fields) when i click on a button.
This is the methode that handle the click on the button:
addCustomField = () => {
let newFieldName = this.state.customFieldLabel;
let newField = { newFieldName: "" };
this.customFormOptions.fields[newFieldName] = {
label: newFieldName
};
tempCustomFieldsArray.push(newField);
this.setState({
customFieldsArray: tempCustomFieldsArray
});
};
i have tried this but it didn't work.
this code works for me, create struct form model that call function, and user your condition there
formModel: t.struct({
customFields: this.getCustomFields(),
}),
then you create customFields function like,
getCustomFields = () => {
const customFields = {}
//write your condition here
return t.enums(customFields)
}