Vue Object isn't returning updated value despite seeing the value updated - vue.js

I'm writing tests for Vue.js and I'm trying to write the test to ensure that when some props are changed for pagination, the resulting values within the component are updated properly.
So when I console.log the component, I see the correctly updated values, but then when I try to literally grab that attribute it gives me the old and outdated value. Look at rangeList in the following screenshot to see what I mean:
Here is my code so that you see how what is generating this output.
pagComp.$refs.component.limit = 10;
pagComp.$refs.component.pages = 145;
console.log(pagComp.$refs.component);
console.log('RangList: ' + pagComp.$refs.component.rangeList.length);
Here is the code that modifies rangeList:
createListOfRanges() {
let range = [];
for(let i = 0; i < this.pages; i++) {
range.push(i);
}
this.rangeList = [];
while(range.length > 0) {
this.rangeList.push(range.splice(0, this.rangeLength));
}
this.correctLastRange();
},
Finally, there are two places this function is called: when the component is being created, and when the pages attribute changes. Here is the watcher:
watch: {
pages(val) {
this.createListOfRanges();
}
},

I see some issues with this part of your code:
while(range.length > 0) {
this.rangeList.push(range.splice(0, this.rangeLength));
}
range.splice(..) returns an array, which is getting pushed into this.rangeList
Forget about that for a minute. Look at the following example:
x = [1, 2, 3, 4]
x.splice(0, 2) // result: [1, 2]
As you can see above, splice returns an array, not an element. Now, in the same example above:
x = [1, 2, 3, 4]
y = [10, 11]
y.push(x.splice(0, 2))
Check the value of y. It will be [10, 11, [1, 2] ]. It is an array within another array. It does not look very meaningful here. You can do the above x and y experiments directly in your developer console.
In your case, your x happens to be the local range array within createListOfRanges method, and your y is this.rangeList that belongs to your Vue component.
Can you check your app logic at around that area, and see if that is really what you want to do?
For debugging Vue.js apps, you also need Vue devtools: https://github.com/vuejs/vue-devtools - it is much better than console.log()

While #Mani is right on the line of code giving you issues is your push to rangeList.
createListOfRanges() {
let range = [];
for(let i = 0; i < this.pages; i++) {
range.push(i);
}
this.rangeList = [];
while(range.length > 0) {
this.rangeList.push(range.splice(0, this.rangeLength));
}
this.correctLastRange();
},
pushing the result of the splice just makes a single element with all the elements of range in it.
try changing it to
this.rangeList.push(range.shift());
Though your function could be simplified by pushing the for(let i = 0; i < this.pages; i++) { i value directly to rangeList unless that's a simplification.
This JSFiddle shows what I'm talking about.

I appreciate the answers above, however they aren't what the issue was.
The problem was with Vue's lifecycle. I'm not 100% sure why, but when the page and limit variables are changed it takes another moment for the page watcher (shown above) to get executed and update the component. So thus it wouldn't show up in my tests. So what I did was use nextTick like so, which fixed the problem.
pagVue.limit = 10; // limit and pages change together every time automatically
pagVue.pages = 145;
Vue.nextTick(() => {
expect(pagination.rangeList.length).toBe(25);
})

Related

columnSummary is not added

I am trying to add columnSummary to my table using Handsontable. But it seems that the function does not fire. The stretchH value gets set and is set properly. But it does not react to the columnSummary option:
this.$refs.hot.hotInstance.updateSettings({stretchH: 'all',columnSummary: [
{
destinationRow: 0,
destinationColumn: 2,
reversedRowCoords: true,
type: 'custom',
customFunction: function(endpoint) {
console.log("TEST");
}
}]
}, false);
I have also tried with type:'sum' without any luck.
Thanks for all help and guidance!
columnSummary cannot be changed with updateSettings: GH #3597
You can set columnSummary settings at the initialization of Handsontable.
One workaround would be to somehow manage your own column summary, since Handsontable one could give you some headeache. So you may try to add one additional row to put your arithmetic in, but it is messy (it needs fixed rows number and does not work with filtering and sorting operations. Still, it could work well under some circumstances.
In my humble opinion though, a summary column has to be fully functionnal. We then need to set our summary row out of the table data. What comes to mind is to take the above mentioned additional row and take it away from the table data "area" but it would force us to make that out of the table row always looks like it still was in the table.
So I thought that instead of having a new line we could just have to add our column summary within column header:
Here is a working JSFiddle example.
Once the Handsontable table is rendered, we need to iterate through the columns and set our column summary right in the table cell HTML content:
for(var i=0;i<tableConfig.columns.length;i++) {
var columnHeader = document.querySelectorAll('.ht_clone_top th')[i];
if(columnHeader) { // Just to be sure column header exists
var summaryColumnHeader = document.createElement('div');
summaryColumnHeader.className = 'custom-column-summary';
columnHeader.appendChild( summaryColumnHeader );
}
}
Now that our placeholders are set, we have to update them with some arithmetic results:
var printedData = hotInstance.getData();
for(var i=0;i<tableConfig.columns.length;i++) {
var summaryColumnHeader = document.querySelectorAll('.ht_clone_top th')[i].querySelector('.custom-column-summary'); // Get back our column summary for each column
if(summaryColumnHeader) {
var res = 0;
printedData.forEach(function(row) { res += row[i] }); // Count all data that are stored under that column
summaryColumnHeader.innerText = '= '+ res;
}
}
This piece of code function may be called anytime it should be:
var hotInstance = new Handsontable(/* ... */);
setMySummaryHeaderCalc(); // When Handsontable table is printed
Handsontable.hooks.add('afterFilter', function(conditionsStack) { // When Handsontable table is filtered
setMySummaryHeaderCalc();
}, hotInstance);
Feel free to comment, I could improve my answer.

Can We Get Vue to 'Detect Changes' to an Array If We Use `Filter()`?

First, I had this: parts = parts.filter(part => part.id !== change.doc.id);
So, data is an Array and it gets 'clobbered' with a new 'filtered' Array.
Vue didn't seem keen on detecting the change and updating my DOM.
So, I saw this. Specifically: To deal with caveat 2, you can use splice:
I refactored (or is it 'de-factored' b/c my code 'grew'?) to this:
// Get index of part removed
const index = parts.forEach((part, i) => {
if (part.id === change.doc.id) {
return i;
}
});
parts.splice(index, 1);
She works...but really? Do I have to do this way? 😬
parts.splice(parts.findIndex(part => part.id === change.doc.id), 1);
I got the splice in there while still keeping as 1 line, so m happy! 🤓

Vue.js: error setting a computed property

in the following code (jsbin available here) I have two input elements, a range and a text, bound together via a computed property.
var vm = new Vue({
el: '#main-container',
data: {
sliderValue: 100,
},
computed: {
actualValue: {
get: function() {
if (this.sliderValue <= 100) {
return this.sliderValue;
} else {
return Math.round(this.sliderValue * 12.5 - 1150);
}
},
/* set won't work for val > 100*/
set: function(val) {
if (val <= 100) {
this.sliderValue = val;
} else {
this.sliderValue = Math.round((val + 1150)/12.5);
}
}
}
},
methods: {
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="main-container">
<input type="range" v-model="sliderValue" min=1 max=132>
<input type="text" v-model="actualValue">
<p>Slider: {{sliderValue}}</p>
<p>Actual: {{actualValue}}</p>
</div>
The range goes from 1 to 132, and its range is mapped [1..500] in the text input, with a simple transformation (basically it's a linear mapping with two different slopes for [1..100] and [101..132]) using the actualValue computed property.
Getting actualValue works as expected: dragging the slider correctly updates the input text with appropriate values in the range [1..500].
I'm not able to find a way to set actualValue, though. I'd like to be able to type a value in the text input, and make the slider's thumb update accordingly to the inverse transformation (val + 1150)/12.5.
It works as long as the typed number is in the range [1..100], but it "explodes" for numbers >100, e.g. 101 makes the sliderValue jump at 80892 and actualValue is then re-calculated as 1010000. As far as I understand, it's a looping-feedback scenario.
I've tried also alternative approaches (watch; v-on:change in the text input; using a third variable) to no avail.
Thanks in advance for any suggestion!
It's an amazing puzzle, and challenged me for a long time!
Look at the screenshot below. Your sliderValue and actualValue are strings, not integers. When you set actualValue as 101, you are actually setting it as a string value of "101"
Now, your sliderValue = ((actualValue + 1150)/12.5)
"101" + 1150 = "1011150" (another string!, try it in the developer console)
That messes up your entire calculation. Now you know how to fix it :-)
And you need to get that Vue devtools from here: https://github.com/vuejs/vue-devtools
EDIT: Response to comment #3
Here is the modified jsBin: http://jsbin.com/mahejozeye/1/edit?html,js,output
The only difference is introduction of two console.log statements in your map2 function. This helps you identify if your non-linear mapping function is working correctly or not. If you keep your developer console open, you will see what is happening in this function.
Case 1: When you set the value radius = 25 using the text box, your sliderRadius gets set to 111.55518394648828 and your radius again gets re-calculated as 25. So it comes around in a full circle and everything is stable here.
Case 2: When you set the value radius = 55, your sliderRadius gets set to 173.03607214428857 through your non-linear map2() function, which resets radius to 51.29869180420927
Clearly there is a circular dependency issue. Your sliderRadius and radius are very much dependent on each other and therefore radius is unable to take the value between 51 and 58.
You need to evaluate why it happens, as it has a lot to do with the non-linear mapping function that you have defined. The moment radius can take stable values at 55 (through the map2 function), then your current problem will be resolved.
The simplest fix is to set your input type to number:
<input type="number" v-model="actualValue">
or you can convert your value to an integer with something like:
set: function(val) {
var intVal = parseInt(val, 10);
if (!isNaN(intVal)) {
if (intVal <= 100) {
this.sliderValue = Math.max(1, intVal);
} else {
this.sliderValue = Math.min(132, Math.round((intVal + 1150) / 12.5));
}
}
}

What is the opposite of _.remove

_.remove($scope.posts, post);
I', using _.remove to remove items from an array using lodash. But how do I add objects again? So what is the opposite of _.remove.
_.remove removes an item from an array, now since you want an opposite of the remove which can be push, there is no _.push available. So, i think it's better to use native push function.
here are few things you can consider:
var posts = [{a:1},{b:3},{f:3}];
var post = {a:1};
_.remove(posts, post); // posts = [{b:3},{f:3}]
to add object at 0 index
posts.unshift(post);//posts = [{a:1},{b:3},{f:3}]
to add object at last index
posts.push(post);//posts = [{b:3},{f:3},{a:1}]
to insert object at an index
posts.splice(1, 0, {g:8}); // posts = [{a:1},{g:8},{b:3},{f:3}]
and you can use _.mixin of course.
_.mixin({
push: function(arr,obj){
return arr.push(obj);
}
});
and you can use it like
_.push(posts,post);
JsFiddle for mixin example
Please try _.fill, fills elements of array with value from start up to
_.fill(array, value, [start=0], [end=array.length])
_.fill([4, 6, 8, 10], '*', 1, 3);
// → [4, '*', '*', 10]

How to clear a date from input type="date" with WebdriverIO

I'm now using WebdriverIO and developing a web app.
these days I tried to set a date from input type="date", I got errors
invalid element state: Element must be user-editable in order to clear
it.
and found that
I could get rid of the errors by using addValue() but still the value won't be cleared by any API.
client.clearElement('#deadline')
Also get
invalid element state: Element must be user-editable in order to clear
it.
How can I remove the value from the form?
You can run in browser script to clear it
browser.execute(function () {
document.querySelector('#deadline').value = '';
}, null);
OR give it some value
var date = '2020-03-28';
browser.execute(function (date) {
document.querySelector('#deadline').value = date';
}, date);
reference: https://github.com/webdriverio/webdriverio/issues/386
A more elegant way is to create a custom command and put this piece of code inside
this one worked for me:
client.selectorExecute("#dateInput", function(inputs, value) {
// you can run over the inputs
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].type == 'date') // any condition
inputs[i].value = "1973-12-09";
}
// or just do that:
inputs[i].value = "1973-12-09";
return;
})