I have some tabular data with the headers ('Type', 'Name'). I would like to select all items in column 'name', to check if they contain a search string. Each item in that column has the role 'gridcell', and the description 'Name'. See attached image1.
getByRole('gridcell', {description: /name/i}) doesn't work. I've looked through the typescript declarations of the queries and nothing seems helpful. How can one accomplish this?
Use getAllByRole('cell', {description: /name/i}) to retrieve an array containing the cells in column Name.
Check the array contains a certain value using toContain(item).
https://jestjs.io/docs/expect#tocontainitem
An example (using React):
'App.js'
function App() {
return (
<div className='App'>
<table>
<thead>
<tr>
<td id='name'>Name</td>
</tr>
</thead>
<tbody>
<tr>
<td aria-describedby='name'>J Blogs</td>
</tr>
<tr>
<td aria-describedby='name'>J Doe</td>
</tr>
<tr>
<td aria-describedby='name'>J Hancock</td>
</tr>
</tbody>
</table>
</div>
);
}
export default App;
'App.test.js'
import { render, screen } from '#testing-library/react';
import App from './App';
test('retrieves all cells described by name', () => {
render(<App />);
const cells = screen.getAllByRole('cell', {description: /name/i});
const cellValues = cells.map(cell => cell.textContent);
expect(cellValues).toContain('J Doe');
});
Related
I am attempting to scrape a link from a td cell adjacent to another td labeling the type or description of the link using puppeteer. There are no classes or id distinguishing these td cells other than the text content
<tr>
<td scope="row">1</td>
<td scope="row">10-Q</td>
<td scope="row">nflx-093018x10qxdoc.htm</td>
<td scope="row">10-Q</td>
<td scope="row">1339833</td>
</tr>
<tr class="blueRow">
<td scope="row">2</td>
<td scope="row">EXHIBIT 31.1</td>
<td scope="row">nflx311_q32018.htm</td>
<td scope="row">EX-31.1</td>
<td scope="row">14914</td>
</tr>
<tr>
<td scope="row">3</td>
<td scope="row">EXHIBIT 31.2</td>
<td scope="row">nflx312_q32018.htm</td>
<td scope="row">EX-31.2</td>
<td scope="row">14553</td>
</tr>
<tr class="blueRow">
<td scope="row">4</td>
<td scope="row">EXHIBIT 32.1</td>
<td scope="row">nflx321_q32018.htm</td>
<td scope="row">EX-32.1</td>
<td scope="row">12406</td>
</tr>
the link after td containing '10Q'
XPath expressions
This is where XPath expression are great:
//td[contains(., '10-Q')]/following-sibling::td[1]/a[1]
This XPath expression queries for a td element containing the text 10-Q. Then it will take the following td element and return the first link (a) inside. Alternatively, you could use //td[text()='10-Q']/ in the beginning, if you don't just want the element to contain the text, but to exactly match it.
Usage within puppeteer
To get the element with puppeteer, use the page.$x function. To extract information (like href) from the queried node, use page.evaluate.
Putting all together, the code looks like this:
const [linkHandle] = await page.$x("//td[contains(., '10-Q')]/following-sibling::td[1]/a[1]");
const address = await page.evaluate(link => link.href, linkHandle);
You can do this with vanila javascript,
// find all tr elements
[...document.querySelectorAll('tr')]
// check which one of them includes the word
.find(e=>e.innerText.includes('10-Q'))
// get the link inside
.querySelector('a')
With puppeteer $eval, this can be simplified,
page.$$eval('tr', eachTr=> eachTr.find(e=>e.innerText.includes('10-Q')).querySelector('a'))
Or page.evaluate,
page.evaluate(()=> {
// find all tr elements
return [...document.querySelectorAll('tr')]
// check which one of them includes the word
.find(e=>e.innerText.includes('10-Q'))
// get the link inside
.querySelector('a')
// do whatever you want to do with this
.href
})
Readable solution.
I have seen questions pertaining to accordion but not entirely to my specific need. My table is populated using spacebars, more specific a nested each loop like this:
<tbody>
{{#each piece in pieces}}
<tr id="{{piece._id}}" class="itemList table-warning">
<th class="name tText">{{piece.name}} {{piece._id}}</th>
<td class="pdf tText" style="text-align: center"><a class ="pdf" href="{{piece.pdf}}" target="_blank"><i class="fa fa-file-text-o" aria-hidden="true"></i></a></td>
<td class="audio tText" style="text-align: center"><a class="audio" href="{{piece.audio}}" target="_blank"><i class="fa fa-volume-up" aria-hidden="true"></i></a></td>
<td class="format tText">{{piece.instrumentation}}</td>
<th class="price tText" >${{piece.price}}</th>
<td><input class ="qty" type ="number" name ="quantity" value="0" min="0"></td>
</tr>
<!-- Row that is being clicked-->
<tr class="partsList">
<td colspan="3"></td>
<th class="partName tText">{{piece.name}} Parts</th>
<td colspan="2"></td>
</tr>
{{#each part in piece.parts}}
<!-- Rows that should accordion -->
<!-- Currently ALL rows accordion on click. Need to accordion based on _id-->
<tr class="partList">
<td colspan="3"></td>
<td class="pname tText">{{piece.name}}: {{part.pname}}</td>
<td class="price tText">${{part.pprice}}</td>
<td><input class="qty" type="number" name="quantity" value="0" min="0"></td>
</tr>
{{/each}}
{{/each}}
</tbody>
I have a click function like so:
'click .partsList': function(e){
e.preventDefault();
$('.partList').nextUntil('tr.itemList').toggle();
}
The accordion function works, however it works with every instance of the each loop. i.e. every tr class ="partsList" will accordion at the same time on click.
To my understanding of the each loop, I can access the _id of a document using {{piece._id}}. If I set the table row id to equal that however, it only reads the _id of the FIRST document in the collection.
What I need is on click for the <tr class="partList"> to accordion based on _id. Or perhaps you would go about this a different way than bootstrap tables?
Please let me know if my question needs clarification.
You could filter the clicked .partslist using a data-* attribute. This causes jQuery to select only this specific items. Note that you need to attach the data-* attribute to the row that is clicked and to the rows that should collapse:
<tbody>
{{#each piece in pieces}}
...
<!-- Row that is being clicked-->
<!-- use the _id value of the piece context as data attribute -->
<tr class="partsList" data-id="{{piece._id}}">
<td colspan="3"></td>
<th class="partName tText">{{piece.name}} Parts</th>
<td colspan="2"></td>
</tr>
{{#each part in piece.parts}}
<!-- Rows that should accordion -->
<!-- Currently ALL rows accordion on click. Need to accordion based on _id-->
<!-- use the _id value of the piece context as data attribute -->
<tr class="partList" data-target="{{piece._id}}">
...
</tr>
{{/each}}
{{/each}}
</tbody>
'click .partsList': function(e, templateInstance){
e.preventDefault();
// get the data-id attribute of the clicked row
const targetId = templateInstance.$(e.currentTarget).data('id')
// skip if this row is not intended to toggle
if (!targetId) return
// toggle based on data-target attribute
templateInstance.$(`.partList[data-target="${targetId}"]`).nextUntil('tr.itemList').toggle();
}
I need to dynamically poplulate a table like this. The problem is, it is not a simple table. It has the "rowspan" characteristics.
For a single entry there are multiple fields entries which are being stored in separate rows.
This is a little tricky to populate with Wicket. Any help , advises, suggestions would be great.
This is what the table looks like on the HTML page:
https://jsfiddle.net/sayrandhri/4ktmy6cn/2/
<table>
<tr>
<th>Name</th>
<th>Role</th>
<th>Company</th>
<th>Request</th>
<th>Change</th>
</tr>
<tr>
<td rowspan=2>ABC</td>
<td rowspan=2>User</td>
<td>Y</td>
<td>True</td>
<td>False</td>
</tr>
<tr>
<td>Telecom</td>
<td>True</td>
<td>False</td>
</tr>
<tr>
<td rowspan=3>XYZ </td>
<td rowspan=3>User</td>
<td>O </td>
<td>False</td>
<td>False</td>
</tr>
<tr>
<td>Q</td>
<td>True</td>
<td>True</td>
</tr>
<tr>
<td>R</td>
<td>False</td>
<td>False</td>
</tr>
</table>
You can try following approach:
HTML:
<table>
<tr>
<th>Name</th>
<th>Role</th>
<th>Company</th>
<th>Request</th>
<th>Change</th>
</tr>
<tbody wicket:id="userList">
<tr wicket:id="providerList">
<td wicket:id="userName"></td>
<td wicket:id="roleName"></td>
<td wicket:id="provider"></td>
<td wicket:id="request"></td>
<td wicket:id="change"></td>
</tr>
</tbody>
</table>
Java:
add(new ListView<User>("userList", new PropertyModel<>(this, "users")) {
#Override
protected void populateItem(ListItem<User> listItem) {
final User user = listItem.getModelObject();
listItem.add(new ListView<Provider>("providerList", user.getProviders()) {
#Override
protected void populateItem(ListItem<Provider> listItem) {
final Provider provider = listItem.getModelObject();
Label nameLabel = new Label("userName", user.getName());
Label roleNameLabel = new Label("roleName", user.getRoleName());
listItem.add(nameLabel);
listItem.add(roleNameLabel);
if (user.getProviders().indexOf(provider) == 0) {
AttributeAppender attributeAppender =
AttributeAppender.append("rowspan", user.getProviders().size());
nameLabel.add(attributeAppender);
roleNameLabel.add(attributeAppender);
} else {
nameLabel.setVisible(false);
roleNameLabel.setVisibilityAllowed(false);
}
listItem.add(new Label("provider", provider.getName()));
listItem.add(new Label("request", provider.isRequest()));
listItem.add(new Label("change", provider.isChange()));
}
});
}
});
Be advised that this way user without any providers wont show up on the list at all.
wicket:container solves this problem.
Your html file would look like this:
<wicket:container wicket:id="doubleRow">
<tr>
...
First Row components
...
</tr>
<tr>
...
Second Row components
...
</tr>
</wicket:container>
Assuming you are using a DataTable with Columns:
In the cell that spans several rows you set the rowspan Attribute with a Model. The value in that model will be increased as long as the value of that cell is not changing.
Once you detect a change in the content, you create a new Model with a rowspan value of 1 and assign that again to the current cell.
Something like this might do it, but I cannot test it at the moment:
private IModel<Integer> currentRowSpanModel = new Model<>(0);
...
new AbstractColumn<Object, String>(new Model<>("ColumnWithRowSpan")) {
#Override
public void populateItem(Item<ICellPopulator<Object>> cellItem, String componentId, IModel<Object> rowModel) {
if (/*Content of cell has changed compared to last row*/) {
// write the content to the cell and add the rowspan Attribute
cellItem.add(new Label(componentId, "Content"));
currentRowSpanModel = new Model<>(1);
cellItem.add(AttributeModifier.replace("rowspan", currentRowSpanModel));
} else {
// Hide cell with same content and instead increase rowspan
cellItem.add(new WebMarkupContainer(componentId));
cellItem.setVisible(false);
currentRowSpanModel.setObject(currentRowSpanModel.getObject()+1);
}
}
}
I have a table structure as follows. Now I need to sort these nested tables separately. Forexample: sorting chapter's row will only update chapters order in a separate table. Whereas, sorting items will update their order in another table.
I managed to setup the code and sorting. However, when I drag the items from chapter 4, it pass on the order of the items in from chapter 1 since they come before chapter 4???
Could someone help me with sorting only relevant items??
NOTE: This list is dynamic coming from database. So I am interested in one jquery code covering all the ordering bits.
<table id=subsortsortable>
<tbody class=content>
<tr id="chapter_1"><td>Chapter one</td></tr>
<tr id="chapter_2"><td>Chapter two</td></tr>
<tr id="chapter_3">
<td>
<table>
<tbody class=subcontent>
<tr id="item_31"><td>three.one</td></tr>
<tr id="item_32"><td>three.two</td></tr>
</tbody>
</table>
</td>
</tr>
<tr id="chapter_4">
<td>
<table>
<tbody class=subcontent>
<tr id="item_41"><td>four.one</td></tr>
<tr id="item_42"><td>four.two</td></tr>
<tr id="item_43"><td>four.three</td></tr>
<tr id="item_44"><td>four.four</td></tr>
<tr id="item_45"><td>four.five</td></tr>
</tbody>
</table>
</td>
</tr>
<tr id="chapter_4"><td>Chapter Four</td></tr>
</tbody>
</table>
The code I am using is as follows:
//for sorting chapters - which is outer table
$("#subsortable tbody.content").sortable({
opacity: 0.7,
cursor: 'move',
placeholder: "ui-state-highlight",
forcePlaceholderSize: true,
update: function(){
var order = $('#subsortable tbody.content').sortable('serialize') + '&action=updateChaptersOrder';
$.post("/admin/ajax/ajax_calls.php", order, function(theResponse){
});
}
});
// For sorting and updating items within a specific chapter - which is nested tbody
$("tbody.sortItems").subcontent({
opacity: 0.7,
cursor: 'move',
placeholder: "ui-state-highlight",
forcePlaceholderSize: true,
update: function(){
var order = $('tbody.subcontent').sortable('serialize');// + '&action=updateListings';
$.post("/admin/ajax/ajax_calls.php", order, function(theResponse){
});
}
});
I have got the answer to my own question.. In case someone else encounter the same problem. I have changed the following code inside the internal table:
var order = $('tbody.subcontent').sortable('serialize');
to
var order = $(this).sortable('serialize');
everyone, I encounter a problem when I try to filter the drop down list.
The situation is I wish to filter the Agent dropdown list by using the Company dropdown list in same view/form. Since I need to display the Agents that only belong to the selected Company. But I have no idea to do that.
Any solution, please?
First add Method in Controller as follows
MvcApplication2.Models.MySampleDBEntities db = new Models.MySampleDBEntities();
public ActionResult SearchNames(string ddlcontent)
{
var list = new List<string>();
var nameqry = from n in db.Images
select n.PlayerName;
list.AddRange(nameqry.Distinct());
ViewBag.ddlcontent = new SelectList(list);
var names = from m in db.Images
select m;
if (string.IsNullOrEmpty(ddlcontent))
return View(names);
else//Filter content basedon dropdownlist selected item
return View(names.Where(s => s.PlayerName.Contains(ddlcontent)));
}
then bind it to view as follows
#model IEnumerable<MvcApplication2.Models.Image>
#{
ViewBag.Title = “SearchNames”;
}
<h2>SearchNames</h2>
#using (#Html.BeginForm(“SearchNames”, “Names”, FormMethod.Get))
{
#Html.DropDownList(“ddlcontent”, “All”)<input type=”submit” value=”Filter” />;
}
<table border=”4″ style=”border: medium dashed #FF0000″>
<tr>
<th>
PlayerName
</th>
<th>
Play
</th>
<th>
CountryName
</th>
<th>
Image
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.PlayerName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Play)
</td>
<td>
#Html.DisplayFor(modelItem => item.CountryName)
</td>
<td>
<img src=”#item.ImagePath” height=”100″ width=”100″/>
</td>
</tr>
}
</table>
2 options really...either split the server-based form post into multiple steps, or use jquery to handle this behavior JIT