How to test parent child relationship in react-testing-library? - reactjs

Lets say this is my HTML, created from using semantic-ui.
I select number 35 from my dropdown and the result becomes this:
<div name="genre" style="width:400px" role="combobox" aria-expanded="false" class="ui compact fluid multiple search selection dropdown">
<a class="ui label" value="35">Comedy<i aria-hidden="true" class="delete icon"></i></a>
<div role="option" aria-checked="false" aria-selected="true" class="selected item" style="pointer-events: all;"><span class="text">Comedy</span></div>
</div>
Ideally I want to test the parent items children like this:
parent: name="genre"
child: value="35", text = Comedy
This doesn't seem possible.
I can only test one selector level for example - by getByText('Comedy') which is too generic since there are other 'Comedy' elements on the same page.
So how to select something like this:
getByWhatever('Some parent').getChildByWhatever('Some child').exists()

You can use the within query to get elements within another specific element.
const combobox = screen.getByRole('combobox'); // Retrieve the parent
expect(combobox).toHaveAttribute('name', 'genre');
const link = within(combobox).getByRole('link', { name: 'Comedy' }); // Retrieve the link child from within the parent
expect(link).toHaveAttribute('value', '35');

Related

Screen reader not reading list values while navigating with arrow keys

I have an input field where a typeahead popup comes on searching for something. The screen reader is unable to read the values of suggestions in the popup.
I am maintaining the focus using $ActiveIndex variable. I am able to navigate the list using only the arrow keys, the screen reader just reads the input text when navigating the popup list and not the actual values in the suggestions
The HTML code is something like this:
<input type="text"
class="search"
title="Search User"
ng-model="vm.SearchText"
ng-model-options="{ debounce: 300 }"
aria-label="{{ placeholder }}"
ng-required="requiredattr" />
<ul class="ui list menu typeahead popup-layer vertical" ng-show="vm.MenuShown" ng-mousedown="vm.RetainFocus()">
<li class="item"
ng-class="{ active: $index == vm.ActiveIndex }"
ng-click="vm.Add(match)"
ng-repeat="match in vm.Matches track by $index">
<div class="header">
<i class="ban icon" ng-if="match.Deleted"></i>
<span ng-bind-html="match.DisplayName"></span> <!-- I want this DisplayName to be read -->
</div>
</li>
</ul>
The UI looks like this
The screen reader is just reading "suh" everytime I navigate the results with arrow keys.
Things I have tried:
Adding aria-label attribute to list item - Didn't work, I guess the element should be tab focusable for the aria label to be read out.
Adding tabindex = -1 to the list item to make it focusable but not navigable. Didn't work either.
You can use aria-active-descendant attribute and set it to the list options IDs and also use aria-owns property from the input box to refer the list items.

How to handle conditional sub-category in AngularJS product gallery

Just starting out learning AngularJS and decided to mock up a basic product gallery using what I've learned so far and I've hit a roadblock. Currently I have a simple product gallery with 3 templates(category listing, products in category listing and product overview). What I would like to do is set up some sort of conditional, where if the products in a selected category have a sub-category, it displays a list of sub-categories using the category-list template. If they don't have a sub-category it just goes straight to the product-list template.
I have created this Plunker showing where I am at so far.
In the above example, if someone clicks on "Cars" I want it to then show a listing of sub-categories using the category-list template. So when you click "Cars" it would take you to a screen with 2 buttons: 4-door and 2-door. Clicking on one of those buttons would then show you the products from those sub-categories using the product-list template. However, if you were to click on "Trucks" from the initial screen, it would just take you directly to the product-list template since the trucks don't have sub-categories.
Here is my category-list template:
<section id="categories" ng-hide="productsVisible">
<div ng-repeat="product in vm.products" class="category">
<div ng-click="vm.selectCategory(product); showProducts();">
<button>{{product.category}}</button>
</div>
</div>
</section>
And here is my product-list template:
<section id="products" ng-show="productsVisible">
<div ng-repeat="product in vm.selectedCategory.items" class="product">
<a href ng-click="vm.selectProduct(product); showResults();">{{product.name}}</a>
</div>
</section>
See my updated plunker
Basically, you need to extend the selectCategory method by grouping the sub-categories and checking whether we're about to enter this sub-category in subsequent click. Like this:
vm.selectCategory = function(category) {
var subCats = [],
map = {};
if (category.items && !category.items[0].subCategory){
vm.selectedCategory = category;
vm.inSubCat = true;
return;
}
vm.inSubCat = !category.items;
if (category.items) category.items.forEach(function(e){
if (!map[e.subCategory]) subCats.push({category: e.subCategory, name: category.category});
map[e.subCategory] = true;
});
vm.products = subCats;
if (vm.inSubCat) vm.selectedCategory = {items: vm.data.filter(function(c){
return c.category == category.name;
})[0].items.filter(function(p){
return p.subCategory == category.category;
}) };
}
I would suggest your data model could use some work, and put all the products in a single array with categories and subcategories as properties. However, you can get what you want with this change to the products-list.html.
<div ng-show="vm.selectedCategory.category=='Cars'">
<input type="radio" ng-model="subcategory" value="2-Door">Coupe
<input type="radio" ng-model="subcategory" value="4-Door">Sedan
</div>
<section id="products" ng-show="productsVisible">
<div ng-repeat="product in vm.selectedCategory.items" class="product">
<a ng-show="product.subCategory===subcategory" href ng-click="vm.selectProduct(product); showResults();">{{product.name}}</a>
</div>
</section>
I advice you to refactor the code in two possible ways:
a) Try to remove lines from controller that control the view (the process of displaying different directives) and use events in directives
b) Control your view by using ng-show and ng-hide directives that will show or hide some part of your code.

How to use ng-repeat in multiple div's?

I am working with angularJS ng-repeat. So, I want to use my ng-repeat value in another div. I am doing the following. But it doesn't loop through and give me the exact value.
Code in ng-repeat
<div uib-slide index="$index" class="uibSlider" ng-repeat='id in code' ng-click="$parent.ContentId = id.ContentId" ng-class="{'selected' : ContentId.Id === ContentId}">
<paragraph tag>{{id.ContentId}}</paragraph tag>
</div>
Code in Another Div
<div>
<label>{{(code| filter: {Id: ContentId}: true)[1].ContentId}}</label>
</div>
So, the problem is I have bunch of data and in the another div if I make the array value from 0 to 1. i'm getting null value. But automatically the value isn't changing based on what I contentID select. It always gives me the same value. I'm not sure where I'm going wrong.
Any Help would be appreciated.
You can use ng-repeat-start | ng-repeat-end
<div uib-slide ng-repeat-start='id in code'>
<paragraph tag>{{id.ContentId}}</paragraph tag>
</div>
<div ng-repeat-end>
<label>{{(id.ContentId}}</label>
</div>
If you are trying to show the same data in a modal, depending where the user clicks:
<div ng-repeat="id in code">
<!-- when the user clicks here, the modal will open. The ID will be the same as the one clicking -->
<a ng-click="openModal(id)">Open modal for id: {{id}}</a>
</div>
Then in your controller you will have a function called openModal which takes a parameter (id) that will open the modal and pass along the data.

Angular js drag and drop

I want to have drag and drop feature in the following structure.
Im using this library to make the above structure draggable.
But it has certain conditions to follow.
Main Parent is not draggable.
Main Parent can have any number of children.
Children can have any number of items.
Lets take Child 4.1. Say Item1 is of type admin and Item2 and Item3 are type users. Child can have only one admin but can have any number or users.
Next the condition for the child elements.
Child 4 has Child 4.1, but child 4.1 cannot have a inner child 4.1.1.
Child can move only upto level 3.
Child can be dragged from level 3 to level 2 and vice versa.
In this case, Child 3 can be dragged to as Child 4.2 and Child 4.1 can be dragged to as a new Child 5 or level 3 child named Child 3.1.
Im trying to integrate nested and type drag and drop to achieve this structure.
EDIT 1
Fiddle of what i have achieved so far.
Here type checking is done between 'men' and 'woman'. Each container can have only 3 men and 2 woman.
Now I want to make the list/child draggable and to be drop inside other list/child and these children will be inside one main parent which cannot be dragged
<script type="text/ng-template" id="types.html">
<ul dnd-list="list.people"
dnd-allowed-types="list.allowedTypes"
dnd-dragover="dragoverCallback(index,type,list,((list.people | filter:{type: 'men'}).length >= list.maxM),((list.people | filter: {type: 'woman'}).length >= list.maxW))"
dnd-drop="dropCallback(index, item, type)"
dnd-disable-if="(list.people.length >= (list.maxM+list.maxW))">
<li ng-repeat="person in list.people"
dnd-draggable="person"
dnd-type="person.type"
dnd-moved="list.people.splice($index, 1)"
dnd-effect-allowed="move" class="background-{{person.type}}">
<dnd-nodrag>
<div dnd-handle class="handle">:::</div>
<div class="name">
<input type="text" ng-model="person.name" class="background-{{person.type}} form-control input-sm">
</div>
</dnd-nodrag>
</li>
<li class="dndPlaceholder">
Drop any <strong>{{list.allowedTypes.join(' or ')}}</strong> here
</li>
</ul>
</script>
<div class="typesDemo">
<div ng-repeat="list in lists" class="col-md-4">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">List of {{list.label}} (max. {{list.max}})</h3>
</div>
<div class="panel-body" ng-include="'types.html'"></div>
</div>
</div>
</div>
Used the callback listeners to satisfy my needs.
dnd-dragstart="dragStartCallBack(list,item,$index)"
dnd-dragover="dragoverCallback(event,list,type,index)"
dnd-drop="dropCallback(event,list,item,index)"
dnd-inserted="insertedCallback(event,list,item,index)"

How to select element from options populated

I need to write a script with Selenium-Webdriver. I am stuck in a situation where I need to enter text (e.g : "tt") in the textbox and a list gets populated (hidden values). We need to select an option from the populated list. (Like we do in "Google" search).
<div class="select2-search">
<input type="text" autocomplete="off" class="select2-input tabindex="-1" style>
</div>
<ul class="select2-results">
<li class="select2-results-dept-0 select2-result select2-result-selectable select2-new">
<div class="select2-result-label">
<span class="select2-match">et</span>
</div>
</li>
<li class="select2-results-dept-0 select2-result select2-result-selectable select2-highlighted">
<div class="select2-result-label">"Secr"
<span class="select2-match">et</span>"ary"
</div>
</li>
<ul>
So i'll write a little code to help you.
I'm using www.google.com as exemple.
WebElement inputElement = driver.findElement(By.id("gbqfq")); // get the input
inputElement.sendKeys("zyzz"); // you want to search him on internet ;)
// get all suggestions in a list
List<WebElement> suggestionList = driver.findElements(By.CssSelector(".gsq_a table tbody tr td:first-of-type span"));
To select an option you have 2 choices, first :
// you get a random option and click on it
suggestionList.get(1).click();
or
// you only want to click on the link that contains that.
for(WebElement elem: suggestionList)
{
String text = elem.getText();
if(text.equals("zyzz motivation"))
elem.click();
}

Resources