I'd like to check if all links in a <ul> have a href attribute.
Component (uses Gatsby's <Link>) :
const DropdownMenu = ({ open }) => {
return (
<ul className="dropdown-menu">
{open && (
<>
<li>
<Link to="/link1/">Link 1</Link>
</li>
<li>
<Link to="/link2/">Link 2</Link>
</li>
<li>
<Link to="/link3/">Link 3</Link>
</li>
</>
)}
</ul>
)
}
Rendered HTML :
...
<ul
class="dropdown-menu"
>
<li>
<a
href="/link1/"
>
Link 1
</a>
</li>
<li>
<a
href="/link2/"
>
Link 2
</a>
</li>
<li>
<a
href="/link3/"
>
Link 3
</a>
</li>
</ul>
...
Test :
test('links have href', () => {
render(<DropdownMenu open={true}></DropdownMenu>)
const listLinks = screen.getAllByRole('link')
expect(listLinks).toHaveAttribute('href')
})
Output :
received value must be an HTMLElement or an SVGElement.
Received has type: array
Received has value: [Link 1, Link 2 ...]
Is there a way (like a map or something) to check if all elements have correct href ?
I might be using the wrong approch here.
As the error says, .toHaveAttribute should be called on an HTMLElement, but you called it on listLinks which is an array of HTMLElement (HTMLElement[]).
If you really want to test the href value of each link, you simply need to make your assertions in a loop :
listLinks.forEach((link) => {
expect(link).toHaveAttribute('href', expected);
});
I'm afraid there is no such utility method as a map or something for this use case.
That being said, if there is no condition to set each to prop to the links (like the code you shared here), I think that this test is not necessary.
What you are actually testing here is Gatsby Link set the correct href attribute to the generated link from the to prop that has been passed, but this is theoretically none of your business, Gatsby already makes sure it works properly.
In my humble opinion, what you should test is the logic you define : making sure the links are rendered if open prop is true.
The rest is for extra safety, but probably not necessary.
.forEach in Jest could give false positives, use it.each instead
Related
I Just want to have dynamic tailwind classes values that changes when change a data value, it is possible to do it using tailwind?
In my example I have a double side menus and a main content, I want to set the menus width and programmatically change the margins that have the main content.
I don't know why but tailwind doesn't apply my crafted classes even if in the browser shows the right class in the div element.
Left side menu:
(right is equal)
<nav
class="fixed overflow-x-scroll bg-gray-700 top-16 h-screen"
:class="classes.leftSidebar"
>
<h1 class="text-xl text-center font-bold pt-5">Menu</h1>
<ul class="list-none text-white text-center">
<li class="my-8">
Teams
</li>
<li class="my-8">
Projects
</li>
<li class="my-8">
Favourites
</li>
<li class="my-8">
Notifications
</li>
<li class="my-8">
Members
</li>
</ul>
</nav>
Content:
<main :class="classes.main">
<slot></slot>
</main>
Script:
data() {
return {
showingNavigationDropdown: false,
sidebar_left_w: 64,
sidebar_right_w: 64,
}
},
computed: {
classes() {
return {
leftSidebar: `w-[${this.sidebar_left_w}rem]`,
rightSidebar: `w-[${this.sidebar_right_w}rem]`,
main: [`mr-[${this.sidebar_right_w}rem]`, `ml-[${this.sidebar_left_w}rem]`],
}
}
},
I also tried leftSidebar: `w-${this.sidebar_left_w}`, but same results
This is not possible with Tailwind CSS
The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS:
Don't construct class names dynamically
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
<!-- This will not work -->
In the example above, the strings text-red-600 and text-green-600 do not exist, so Tailwind will not generate those classes.
Instead, make sure any class names you’re using exist in full:
Always use complete class names
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
As long as you always use complete class names in your code, Tailwind will generate all of your CSS perfectly every time.
source
In my Laravel 5/vuejs 2/ VueRouter / app I have navigation area :
<li class="navigation_item_top">
<router-link :to="{ name: 'DashboardIndex' }" class="a_link">
Dashboard
</router-link>
</li>
<li class="active navigation_item_top" >
Customers
<ul class="collapse list-unstyled ml-2" id="backendCustomersSubmenu">
<li class="mt-2">
<router-link :to="{name : 'listCustomers'}" class="a_link">
Customers
</router-link>
</li>
<li class="mt-2">
<router-link :to="{name : 'listCustomers', params: { filter: 'new' } }" class="a_link">
New Customers
</router-link>
</li>
<li class="mt-2">
<router-link :to="{name : 'listCustomers', params: { filter: 'inactive' } }" class="a_link">
Inactive Customers
</router-link>
</li>
</ul>
</li>
Where there are 3 links listCustomers with different default filter opening page and it works ok if from
DashboardIndex page I move to any listCustomers.
But it does not work if from opened listCustomers page I open listCustomers with other filter.
I suppose as VueRouter considers all listCustomers as 1 page.
If there is a way to make it and selecting other listCustomers to reopen filter?
Not sure if i understand your Q very well, but i think you want to:
Append query to url. e.g company.com/listCustomers?filter=new
Make Vue notice the change and do something with that change
If that's the case then try this :
<template>
<--! HTML HERE -->
</template>
<script>
export default {
watch: {
'$route'(to, from){
// you don't need this but just in-case.
//this.$forceUpdate();
//If you're using query like ?filter=new, this should work
if (this.$route.query.filter) {
if (this.$route.query.filter== 'new') {
}else{
}
}
}
}
}
</script>
N.B
If you want use parameters it means you expect your url to be:
domain.com/listCustomers/inactive.
so just try the basics and link like this:
<router-link to="/listCustomers/inactive">
or
<router-link to="/listCustomers/new">.
And if your want queries your url is going to be:
domain.com/listCustomers?filter=new.
and you should pass exact prop to activate active page style.
then you need to watch for changes in the watch hook just like i did my answer
Now all the been said,
linking with parameters should work without any problem, but if you decide to use any Navigation Guards technique, like router.afterEach.
Please do not forget to add next(), to allow it to move on after your code. otherwise it won't navigate.
read Navigation Guards.
I hope you will understand.
I am trying to test my vuejs component via jest that contains materialize select.
When performing a component test, I get the following error in materialize.js:
TypeError: Cannot set property 'tabIndex' of null at Dropdown._makeDropdownFocusable
How fix this error?
This problem can happen when the input field is not wrapped inside a div with the class input-field:
<div class="input-field">
<input type="text" class="autocomplete"></input>
</div>
Adding a div with the class "input-field might solve this problem.
use id selector instead class selector. for example call dropdown like this :
html :
<a class='dropdown-trigger' id="dropdowner" href='#' data-target='dropdown1'>Drop Me!</a>
<!-- Dropdown Structure -->
<ul id='dropdown1' class='dropdown-content'>
<li>one</li>
<li>two</li>
<li class="divider" tabindex="-1"></li>
<li>three</li>
<li><i class="material-icons">view_module</i>four</li>
<li><i class="material-icons">cloud</i>five</li>
</ul>
js:
$('#dropdowner').dropdown();
Can only be used once.
data-target="name_target" must not be repeated
Exam1.❌
<nav>
<div class="nav-wrapper">
<ul class="right hide-on-med-and-down">
<li><a class="dropdown-trigger" href="#!" data-target="name_target1">Dropdown<i class="material-icons right">arrow_drop_down</i></a></li>
<li><a class="dropdown-trigger" href="#!" data-target="name_target1">Dropdown<i class="material-icons right">arrow_drop_down</i></a></li>
</ul>
</div>
</nav>
<!-- Dropdown Structure -->
<ul id="name_target1" class="dropdown-content">
<li>one</li>
<li>two</li>
</ul>
Exam2.✔️
<nav> <div class="nav-wrapper">
Logo
<ul class="right hide-on-med-and-down">
<li><a class="dropdown-trigger" href="#!" data-target="name_target2">Dropdown<i enter code here class="material-icons right">arrow_drop_down</i></a></li>
</ul> </div> </nav> <ul id="name_target2" class="dropdown-content"> <li>one</li> <li>two</li> </ul>
When I ran into this issue I was trying to create the whole dropdown list dynamically in JS. The fix for me was creating the list and any default list elements in HTML:
<div id="select1" class=\"input-field col s12\">
<select>
<option value="" selected>Default</option>
</select>
<label>Test</label>
</div>
Then appending any dynamic values in JS:
contents.forEach(function(content) {
var buffer = "<option></option>";
var template = $(buffer);
$(template).text(content);
$("select1").find("select").append(template);
});
$("select").formSelect();
pre 1.0.0 you would use data-activates, if data-target is not specified you will get this error
My problem was, that jQuery object was not attached to the DOM yet, so inner materialise code could not init element due to inability to find element by ID:
// materializecss initing dropdown (in my case for input autocomplete), where `t` is the input element
i.id = M.getIdFromTrigger(t),
i.dropdownEl = document.getElementById(i.id),
i.$dropdownEl = h(i.dropdownEl),
M.getIdFromTrigger(t) returned some random ID (not the one I provided) and dropdownEl was inited with null, and later method _makeDropdownFocusable failed on using it `this.dropdownEl.tabIndex = 0
So my problem code looked like this:
let root = $('#root'); // root in the DOM already
let wrapper = $('<div>'); // wrapper is just created and NOT attached to the DOM yet
let input = $('<input>').appendTo(wrapper); // creating input and attaching to the wrapper, but still not in DOM
initAutocomplete(input) // M.Autocomplete.init logic here FAILS
root.append(wrapper) // too late, error above
So the quick fix is to append elements first and only than do M.Autocomplete.init
I just stumbled this issue too while using Materializecss for my Vue project. As mentioned by sajjad, using id selector instead of class works. However, this is problem for initializing multiple dropdown, since each dropdown must have unique ID.
The way I solve this is just by selecting all the elements with the '.dropdown-trigger' class, and initialize every each of those. It works for me.
$.each($('.dropdown-trigger'), function(index, value) {
$(value).dropdown();
});
i have a component in my example an accordion and i want to reuse it with different templates inside it's content-area.
what i have achieved is to create the accordion.
<div class="c_accordion">
{{#each data}}
<div class="c_accordion__item">
<h3 class="c_accordion__headline">{{this.title}}</h3>
<div class="c_accordion__content">
{{../template}} <-- renders [object object]
{{> ('../template') data=this.content}} <-- there i want to render diffrent templates
{{> list data=this.content}} <-- this works without problems
</div>
</div>
{{/each}}
</div>
And now i want to render different templates insinde my accordion (for example a list partial)
{{> accordion template=list data=about.services}}
But i always get an error "Warning: The partial undefined could not be found Use"
I tried it also with the helper lookup, but also the same result with an error.
Generally is that the correct way or there are others approaches to solve this problem??
I'm using grunt-assemble to build static sites. this is the handlbars version ## v4.0.5 - November 19th, 2015
gregor
ok i found a solution ;)
based on this
assemble - Render a list of strings as Handlebars partial
//insert the accordion
{{> accordion partial="list" data=about.services}}
//accordion.hbs
<div class="c_accordion">
...
{{dynamicPartial ../partial this.content}}
...
</div>
//helper dynamicPartial
module.exports.register = function (Handlebars, context) {
Handlebars.registerHelper('dynamicPartial', function(name, data) {
var partial = Handlebars.partials[name];
var template = Handlebars.compile(partial);
var output = template({"data": data});
return new Handlebars.SafeString(output);
})
};
//list.hbs
<ul>
{{#each data}}
<li>{{this}}</li>
{{/each}}
</ul>
I'm using nested accordions with Materializecss. I want to be able to have nested accordions, but to let each level to only have 1 item of the accordion opened (as of data-collapsible='accordion').
I can't get it to work: if I set data-collapsible='accordion' I cannot open nested accordions, and if I set data-collapsible='collapsible', I can open any number of items per accordion.
Any workaround?
Thanks!
If you are managing the inner elements of the collapsibles dinamically, then you need to "initialize" them using a jquery method included in "materialize.js". This is written in the "materializecss" documentation here.
I'll provide a practical example.
Given the next HTML:
...
<ul class="collapsible" data-collapsible="accordion">
<li>
<div class="collapsible-header">
My nested collapsible
</div>
<div class="collapsible-body">
<ul class="nested collapsible" data-collapsible="accordion">
<!-- No data initially -->
</ul>
</div>
</li>
<li>
<div class="collapsible-header">Second</div>
<div class="collapsible-body">
<p>Normal data...</p>
</div>
</li>
</ul>
...
I suppose the problem comes because you are appending data into the ".nested" div, and it's not working as an accordion as expected.
You should then do something like:
// ... Your handler code ...
// ... Data appended into $('.nested')
$('.nested').collapsible({accordion: true});
// ...
The {accordion: true} option is not mandatory, as it will be treated as an accordion by default.
It should work in this case. Good luck.