Vue Component Unit Testing - unit-testing

I have a counter component - pretty straight forward.
<div> + </div>
<input type="text" v-model="countData" />
<div> - </div>
Detailed code is here - https://github.com/Shreerang/Vue-Nuggets/blob/master/src/components/QuantitySelector/QuantitySelector.vue
I am trying to unit test this component.
it('Renders a default quantity selector with the max count set to 6', () => {
const wrapper = shallowMount(QuantitySelector);
wrapper.find('input[type="text"]').setValue('1');
expect(wrapper.find('input[type="text"]').element.value).toBe('1'); // notice here that the value is a String, whereas I expect it to be a Number
wrapper.findAll('div').at(2).trigger('click');
expect(wrapper.vm.countData).toBe('2'); // This fails as countData becomes "11" instead of doing a 1 + 1 = 2 and then becoming Number 2.
expect(wrapper.find('input[type="text"]').element.value).toBe(2); // You can notice the same thing as above.
wrapper.find('input[type="text"]').setValue(wrapper.vm.countData); // Do I have to do this? This does not seem right to me :frowning:
});
I am not able to get this unit test to work! Any help with this is appreciated!

Text fields contain text values. Note that you even specified a text value: setValue('1'). If you change the value in the input manually, (say, to 3) and press the increment button, it becomes 31. Your test is telling you the truth.
You need to change your functions to convert to number. [Update] As your comment informed me, Vue has a .number modifier for v-model for this very purpose
new Vue({
el: '#app',
name: 'quantity-selector',
props: {
count: {
type: Number,
default: 1,
}, // Makes sense to have default product count value
maxCount: {
type: Number,
default: 6,
}, // maxCount makes sense when you have a restriction on the max quantity for a product
iconDimensions: {
type: Number,
default: 15,
},
minusIconFillColor: {
type: String,
default: '#000',
},
plusIconFillColor: {
type: String,
default: '#000',
},
isCountEditable: {
type: Boolean,
default: true,
},
},
data() {
return {
countData: this.count,
};
},
computed: {
minusIconColor: function() {
return this.countData === this.count ? '#CCC' : this.minusIconFillColor;
},
plusIconColor: function() {
return this.countData === this.maxCount ? '#CCC' : this.plusIconFillColor;
},
},
methods: {
decrement: function() {
if (this.countData > this.count) {
this.countData -= 1;
}
},
increment: function() {
if (this.countData < this.maxCount) {
this.countData += 1;
}
},
adjustCount: function() {
if (this.countData > this.maxCount) {
this.countData = this.maxCount;
} else if (this.countData < this.count) {
this.countData = this.count;
} else {
if (isNaN(Number(this.countData))) {
this.countData = this.count;
}
}
},
}
});
.nugget-quantity-counter {
display: inline-flex;
}
.nugget-quantity-counter div:first-child {
border: solid 1px #ccc;
border-radius: 5px 0px 0px 5px;
}
.nugget-quantity-counter div:nth-child(2) {
border-top: solid 1px #ccc;
border-bottom: solid 1px #ccc;
display: flex;
flex-direction: column;
justify-content: center;
}
.nugget-quantity-counter input[type='text'] {
border-top: solid 1px #ccc;
border-bottom: solid 1px #ccc;
border-left: none;
border-right: none;
text-align: center;
width: 20px;
padding: 12px;
margin: 0;
font-size: 16px;
}
.nugget-quantity-counter div:last-child {
border: solid 1px #ccc;
border-radius: 0px 5px 5px 0px;
}
.nugget-quantity-counter > div {
cursor: pointer;
padding: 12px;
width: 20px;
text-align: center;
}
.nugget-quantity-counter > div > svg {
height: 100%;
}
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div #click="decrement">
<svg viewBox="0 0 24 24" :width="iconDimensions" :height="iconDimensions">
<g>
<path d='M64 0 M2 11 L2 13 L22 13 L22 11 Z' :fill="minusIconColor" />
</g>
</svg>
</div>
<input v-if="isCountEditable" type="text" v-model.number="countData" #blur="adjustCount" />
<div v-else>{{countData}}</div>
<div #click="increment">
<svg viewBox="0 0 24 24" :width="iconDimensions" :height="iconDimensions">
<g>
<path d="M 11 2 L 11 11 L 2 11 L 2 13 L 11 13 L 11 22 L 13 22 L 13 13 L 22 13 L 22 11 L 13 11 L 13 2 Z" :fill="plusIconColor" />
</g>
</svg>
</div>
</div>

Related

Align Foundation 5 clearing lightbox items to the center

I am trying to align the items in a clearing lightbox to the center. The lightbox aligns the items to the left for default. Here is the current situation:
According to this answer you can align them in the tabs component using
float: none !important;
display: inline-block;
But for the lightbox it just produces an erratic behavior where the thumbnails are in an apparently random position yet center aligned horizontally:
So i wonder how to make the items aligned to the center and not all to the left.
Here is the relevant code:
CSS:
[class*="clearing-thumbs"]
{
/* top right bottom left*/
margin: 5% 0 0 0;
text-align: center;
padding: 0.25 0 0.5rem 0;
}
[class*="clearing-thumbs"] > li
{
padding: 0.025 0 0.5rem 0;
/*
float: none !important;
display: inline-block;
top:0%;
*/
}
HTML/JS:
function load(listaSucursales.data)
{
var output = '<div class="row"><div class="small-11 small-centered columns" >';
output += '<ul class="clearing-thumbs" data-clearing>';
for(var x in listaSucursales.data)
{
output+='<li> <div class="square">'+ listaSucursales.data[x].NOMBRE +"</div></li> ";
}
output+= '</ul> </div></div>';
document.getElementById("mainContent").innerHTML=output;
}
Thanks to Niloct suggestion this code allowed me to align the items as i needed:
[class*="clearing-thumbs"]
{
margin: 5% 0 0 0;
text-align: center;
padding: 0.25 0 0.5rem 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
[class*="clearing-thumbs"] > li
{
padding: 0.025 0 0.5rem 0;
}

time_ago_in_words is cut short

I face a minor problem in Chapter 10 of the Rails Tutorial. My spec tests all pass but the list of the user microposts look a bit different than shown in the tutorial - namely, the time stamp is displayed without the "ago" word - so "Posted 4 days ago" becomes "Posted 4 days":
I think I followed all instructions correctly. Here is the code for the micropost partial:
<li>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %>
</span>
</li>
And here is the stylesheet:
/* microposts */
.microposts {
list-style: none;
margin: 10px 0 0 0;
li {
padding: 10px 0;
border-top: 1px solid #e8e8e8;
}
}
.content {
display: block;
}
.timestamp {
color: $grayLight;
}
.gravatar {
float: left;
margin-right: 10px;
}
aside {
textarea {
height: 100px;
margin-bottom: 5px;
}
}
What am I doing wrong?
time_ago_in_words will not add ago word.You should be adding it maually.
This should work.
<li>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago. #here
</span>
</li>

"ReferenceError: edit is not defined" in Hierarchical template knockoutjs?

I'm going to create a hierarchical template html tags with knockoutjs :
Json Data :
{
"BenchmarkGroups": [{
"Id": 43,
"Title": "Display",
"PersianTitle": "Display",
"ObjectId": 12,
"ParentId": 0,
"OrderNumber": 0,
"ImagePath": "/File/Get/14971.jpg.ashx",
"Attachments": [],
"ChildrenBenchmarkGroup": [{
"Id": 44,
"Title": "Screen measurements",
"PersianTitle": "Screen measurements",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
},
{
"Id": 45,
"Title": "Viewing angles",
"PersianTitle": "Viewing angles",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
},
{
"Id": 46,
"Title": "Color charts",
"PersianTitle": "چارت رنگ ها",
"ObjectId": 12,
"ParentId": 43,
"OrderNumber": 0,
"ImagePath": "",
"Attachments": [],
"ChildrenBenchmarkGroup": [],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
}],
"ParentBenchmarkGroup": null,
"Object": null,
"BenchmarkItems": []
}]
}
Html :
<script id="BenchmarkGroups-template" type="text/html">
<li>
<!-- ko if: $index() > 0 -->
<hr style="width: 98%; margin: 10px auto;" />
<!-- /ko -->
<div data-name="benchmarkgroup-header" style="background: #E4E45B; padding: 10px; margin: 0 10px;" data-bind="attr: { 'data-groupId': Id }">
<div style="float: right; margin: 0 20px 0 0;">
<h3 style="direction: rtl; margin: 0; cursor: pointer;" data-bind="html: PersianTitle, event: { click: edit }" title="عنوان گروه بنچمارک به فارسی"> </h3>
</div>
<!-- ko if: ImagePath() != '' -->
<img data-bind="attr: { src : ImagePath() + '?maxwidth=50&maxheight=50' }" src="#" alt="" style="max-width: 50px; max-height: 50px; float: left; margin: 0 10px 0 0;" />
<!-- /ko -->
<div style="float: left; margin: 0 0 0 20px;">
<h3 style="direction: ltr; margin: 0; cursor: pointer;" data-language="en" data-bind="html: Title, event: { click: edit }" title="عنوان گروه بنچمارک به انگلیسی"> </h3>
</div>
<div class="clear-fix"></div>
</div>
//`ReferenceError: edit is not defined` at leaf
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: ChildrenBenchmarkGroup }"></ul>
</li>
</script>
<div style="width: 70%; margin: 10px auto;" id="BenchmarkGroupsDiv" data-bind="visible: BenchmarkGroups().length > 0">
<fieldset>
<legend>#Html.DisplayNameFor(model => model.BenchmarkGroups)</legend>
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: BenchmarkGroups }"></ul>
</fieldset>
</div>
It will be OK if I remove the following line from template but it doesn't show ChildrenBenchmarkGroup :
<ul style="width: 100%;" data-bind="template: { name: 'BenchmarkGroups-template', foreach: ChildrenBenchmarkGroup }"></ul>
But with above line knockoutjs throw an error at the leaf objects at the line.
I've found the problem:
I wrote a mapper option as the following :
var mappingOptionsBenchmarkGroups = {
create: function(options) {
return (new (function() {
var self = this,
$target = undefined,
$acceptButton = $('<input type="button" title="ثبت" style="margin:0 5px 0 0; width:auto; height:auto; border:1px solid #4651D8; background: auto; border-radius:0; background-image:none; line-height:20px; box-shadow:none;" value="✓" />'),
$rejectButton = $('<input type="button" title="انصراف" style="margin:0 5px 0 0; width:auto; height:auto; border:1px solid #4651D8; background: auto; border-radius:0; background-image:none; line-height:20px; box-shadow:none;" value="×" />'),
$textBox = $('<input type="text" style="width:150px; box-shadow: none; padding: 2px; border-radius: 0;" />');
$acceptButton.click(function() {
hideEdit();
});
$rejectButton.click(function() {
hideEdit();
});
$textBox.keyup(function(e) {
if (e.keyCode == 27) {
hideEdit();
}
});
self.edit = function(arg1, arg2) {
if ($target) hideEdit();
$target = $(arg2.target);
if ($target.attr('data-language') == 'en') {
$textBox.css('direction', 'ltr');
$textBox.attr('placeholder', 'نام انگلیسی');
} else {
$textBox.css('direction', 'rtl');
$textBox.attr('placeholder', 'نام فارسی');
}
showEdit();
};
function showEdit() {
$textBox.val($target.text());
$target.hide()
.before($textBox.show())
.before($acceptButton.show())
.before($rejectButton.show());
$textBox.show().focus();
}
function hideEdit() {
$target.show();
$textBox.hide();
$acceptButton.hide();
$rejectButton.hide();
$target = undefined;
}
ko.mapping.fromJS(options.data, {}, this);
})());
}
};
it applies to the root and I had to use $root.edit instead of edit.

Scrollable Foundation Section headers

Looking through http://foundation.zurb.com/docs/components/section.html, is there anyway I can add horizontal scroll for Section headers ( Tabs) . I am looking something like http://www.seyfertdesign.com/jquery/ui.tabs.paging.html in foundation sections with horizontal scroll and continue to use accordion in small screen
I found a solution for those interested : https://codepen.io/gdyrrahitis/pen/BKyKGe
.tabs {
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
.tabs-title {
float: none;
display: inline-block;
}
}
if someone needs an angularjs with jquery implementation, below code can help you, for pure jquery replace angularjs directive method with a native js method with respective attributes.
I tried to search for similar implementation but found nothing, so I have written a simple angular directive which can transform a foundation CSS tabs to scrollable tabs
angular.module("app.directives.scrollingTabs", [])
.directive("scrollingTabs", ScrollingTabsDirective);
//#ngInject
function ScrollingTabsDirective($timeout, $window) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if(attr.scrollingTabs == "true"){
element.addClass('scrolling-tabs-container');
element.find('.nav-buttons').remove();
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-left"></div>');
element.append('<div class="scrolling-tabs nav-buttons nav-buttons-right"></div>');
let scrolledDiv = $(element).find('.tabs');
let scrolled;
let scrolling;
let scrollFn = (step, animationTime, cb) => {
scrolled = Math.max(scrolled + step, 0);
scrolledDiv.animate({
scrollLeft: scrolled
}, animationTime, ()=>{
if (scrolling) {
scrollFn(step, animationTime, cb)
}else{
if(cb){cb()}
}
});
};
let checkActiveNavButtonsClasses = () => {
scrolled = scrolledDiv.scrollLeft();
let scrollWidth = scrolledDiv.get(0).scrollWidth;
let scrolledDivWidth = scrolledDiv.get(0).clientWidth;
if(scrollWidth > scrolledDivWidth){
element.addClass('nav-active');
scrollWidth = scrolledDiv.get(0).scrollWidth;
if(scrolled == 0){
element.removeClass('nav-active-left').addClass('nav-active-right')
}else if(scrolled > 0 && scrolled + scrollWidth < scrolledDivWidth){
element.addClass('nav-active-left').addClass('nav-active-right');
}else if(scrolled > 0 && scrolled + scrollWidth >= scrolledDivWidth){
element.addClass('nav-active-left').removeClass('nav-active-right');
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right')
}
}else{
element.removeClass('nav-active-left').removeClass('nav-active-right').removeClass('nav-active');
}
};
let scrollToActiveTab = () => {
let activeDD = scrolledDiv.find('dd.active');
let tabsOffset = scrolledDiv.offset();
let activeTaboffset = activeDD.offset();
let activeTabwidth = activeDD.width();
let scrolledStep = activeTaboffset.left - tabsOffset.left - scrolledDiv.width() + activeTabwidth;
scrollFn(scrolledStep, 100, checkActiveNavButtonsClasses);
};
element.find(".nav-buttons.nav-buttons-left")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(-100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(-2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
element.find(".nav-buttons.nav-buttons-right")
.off("click.scrolling")
.on("click.scrolling", (event)=>{
event.preventDefault();
scrolling = false;
scrollFn(100, 100, checkActiveNavButtonsClasses);
})
.off("mouseover.scrolling")
.on("mouseover.scrolling", function (event) {
scrolling = true;
scrollFn(2, 1, checkActiveNavButtonsClasses);
})
.off("mouseout.scrolling")
.on("mouseout.scrolling", function (event) {
scrolling = false;
});
$timeout(()=>{
checkActiveNavButtonsClasses();
scrollToActiveTab()
},1000);
$($window).off('resize.scrolling').on('resize.scrolling', _.debounce(()=> {
checkActiveNavButtonsClasses();
}, 500));
scope.$on('$destroy', function() {
$($window).off('resize.scrolling');
});
}
}
}}
css:
.scrolling-tabs-container {
position: relative;
.tabs {
overflow-x: hidden;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
display: block;
margin-right: 18px;
dd {
display: inline-block;
float: none;
margin: 0px -3px 0px 0px;
}
.tabs-title {
float: none;
display: inline-block;
}
}
.scrolling-tabs {
&.nav-buttons {
display: none;
position: absolute;
width: 19px;
height: 38px;
border: 1px solid #c1c1c1;
top: 1px;
background-color: rgba(255, 255, 255, 0.5);
opacity: 0.4;
cursor: pointer;
&:hover {
opacity: 1;
&:before {
color: #444;
}
}
&:before {
position: absolute;
left: 7px;
top: 8px;
color: #777;
}
&.nav-buttons-left {
left: 0;
&:before {
content: '<';
}
}
&.nav-buttons-right {
right: 18px;
&:before {
content: '>';
}
}
}
}
&.nav-active{
.tabs{
margin-right: 36px;
margin-left: 18px;
}
.scrolling-tabs {
&.nav-buttons {
display: inline-block !important;
}
}
}
&.nav-active-left{
.scrolling-tabs{
&.nav-buttons-left{
opacity: 0.8;
}
}
}
&.nav-active-right{
.scrolling-tabs{
&.nav-buttons-right{
opacity: 0.8;
}
}}}
HTML: Foundation Tabs template.
<tabset class="list-tabs" scrolling-tabs="true">
<tab heading="tab1"></tab>
<tab heading="tab2"></tab>
<tab heading="tab2"></tab>
</tabset>
Before you start you'll want to verify that both jQuery (or Zepto) and foundation.js are available on your page. These come with foundation package so just uncomment them in your footer or include them accordingly.
<div class="section-container auto" data-section>
<section class="active">
<p class="title" data-section-title>Section 1</p>
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
</section>
<section>
<p class="title" data-section-title>Section 2</p>
<div class="content" data-section-content>
<p>Content of section 2.</p>
</div>
</section>
</div>
The foundation documentation has all of the information for this :
http://foundation.zurb.com/docs/components/section.html#panel2
This will get you your section tabular headers. You then want to manage the content to be scrollable.
<div class="content" data-section-content>
<p>Content of section 1.</p>
</div>
This content here will be the area to work on, try adding a new class called .scrollable
Within this class use something like:
.scrollable{
overflow:scroll;
}
You may want to add some more to this however this will get you started. Your HTML should now look like this :
<div class="content scrollable" data-section-content>
<p>Content of section 1. This content will be scrollable when the content has exceeded that of the div size. </p>
</div>
This this is what you are looking for.

selected list item in MVC 2.0

i inherited a menu based on lists that was used before i started and that needs going into MVC.
The list needs to show a white box for the selected item and a standard grey box for the rest. up to now, all that gets shown is a grey box for all. We have been looking around for a solution for this but we fail to get to the bottom to this. The list would be extended as time goes by
<ul id="headerBarMenu" class="horizontalMenu">
<li class="fontstyle01a" >
<%: Html.ActionLink("Manage Payment Run", "ManagePaymentRun", "Home")%></li>
<li class="fontstyle01a" >
<%: Html.ActionLink("About", "About", "Home")%></li>
</ul>
ul.horizontalMenu li
{
list-style: none;
padding: 0;
float: left;
border-top: 1px solid #bbb;
border-right: 1px solid #bbb;
border-bottom: 0px;
border-left: 1px solid #bbb;
margin: 0;
}
ul.horizontalMenu a
{
padding: .6em 1.5em 1em 1.5em;
display: block;
background: #cccccc;
}
ul.horizontalMenu a.selected
{
position: relative;
top: 1px;
background: white;
color: black;
font-weight: bold;
}
.fontstyle01a /*bold_dark*/
{
font-family: Verdana, Arial, Helvetica, sans-serif;
text-align: center;
font-size: 7pt;
font-style: normal;
font-weight: bold;
color:#666666;
text-decoration: none;
margin: 0;
padding: 0;
width: 140px;
}
.fontstyle01a a, a:link, a:visited
{
color:#666666;
text-decoration: none;
}
.fontstyle01a a:activea:hover
{
color:#9f117a;
}
Ive been looking at the following to try and change it this, but i have not yet found a solution.
Thanks for the time
Here's a html helper method you might try. It sets the classname based on the current action:
public class Link
{
public string Text { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public object RouteValues { get; set; }
public object HtmlAttributes { get; set; }
}
public static class HtmlExtensions
{
public static MvcHtmlString Menu(this HtmlHelper htmlHelper, IEnumerable<Link> links)
{
var currentAction = (string)htmlHelper.ViewContext.RouteData.Values["action"];
var currentController = (string)htmlHelper.ViewContext.RouteData.Values["controller"];
var ul = new TagBuilder("ul");
ul.GenerateId("headerBarMenu");
ul.AddCssClass("horizontalMenu");
links = links ?? Enumerable.Empty<Link>();
var sb = new StringBuilder();
foreach (var link in links)
{
var li = new TagBuilder("li");
if (string.Equals(currentAction, link.Action, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentController, link.Controller, StringComparison.OrdinalIgnoreCase))
{
li.AddCssClass("white");
}
else
{
li.AddCssClass("grey");
}
li.InnerHtml = htmlHelper.ActionLink(link.Text, link.Action, link.Controller, link.RouteValues, link.HtmlAttributes).ToHtmlString();
sb.Append(li.ToString());
}
ul.InnerHtml = sb.ToString();
return MvcHtmlString.Create(ul.ToString());
}
}
And then apply the menu in your views:
<%= Html.Menu(new[] {
new Link { Text = "Manage Payment Run", Action = "ManagePaymentRun", Controller = "Home" },
new Link { Text = "About", Action = "About", Controller = "Home" },
}) %>
Now if you navigate to /home/ManagePaymentRun the first li will get the class white and if you navigate to /home/about the second li will get this class.
All that is left now is to style those rules:
.white {
/** TODO **/
}
.grey {
/** TODO **/
}
Check out this answer to one of my questions. It is a HtmlHelper that returns a class name based on controller and/or action.