get two divs by class name in karma test (Angular 4.0) - javascript

I have something like this in view:
<div>
<div class="header-title">Example title 1</div>
</div>
<div>
<div class="header-title">Example title 2</div>
</div>
In my karma test I would like to investigate all divs by class name and check if inner text is correct so I have following code in test:
[...]
debugTest = fixture.debugElement.query(By.css('.header-title'));
elementTest = debugTest.nativeElement;
[...]
it('should component div has a correct value', () => {
fixture.detectChanges();
const content = elementTest.textContent;
expect(content).toContain('Example Title 1');
});
Following code works but I always get the first dom with .header-title class. How to extract next one? What if I have 20 divs with the same class name how to test them all?

Use queryAll() instead of query(), which returns an array.
query() returns single DebugElement which is always the first matching element, whereas queryAll() returns DebugElement[].
debugTest = fixture.debugElement.queryAll(By.css('.header-title'));
So that you can access like
elementTest1 = debugTest[0].nativeElement;
elementTest2 = debugTest[1].nativeElement;

Related

Can a javascript variable be made local to a specific html element?

As a novice Javascript programmer, I'd like to create an html document presenting a feature very similar to the "reveal spoiler" used extensively in the Stack Exchange sites.
My document therefore has a few <div> elements, each of which has an onClick event listner which, when clicked, should reveal a hiddent text.
I already know that this can be accomplished, e.g., by
<div onclick="this.innerHTML='Revealed text'"> Click to reveal </div>
However, I would like the text to be revealed to be initially stored in a variable, say txt, which will be used when the element is clicked, as in:
<div onclick="this.innerHTML=txt"> Click to reveal </div>
Since there will be many such <div> elements, I certainly cannot store the text to be revealed in a global variable. My question is then:
Can I declare a variable that is local to a specific html element?
Yes you can. HTML elements are essentially just Javascript Objects with properties/keys and values. So you could add a key and a value to an HTML element object.
But you have to add it to the dataset object that sits inside the element, like this:
element.dataset.txt = 'This is a value' // Just like a JS object
A working example of what you want could look like this:
function addVariable() {
const myElement = document.querySelector('div')
myElement.dataset.txt = 'This is the extended data'
}
function showExtendedText(event) {
const currentElement = event.currentTarget
currentElement.innerHTML += currentElement.dataset.txt
}
addVariable() // Calling this one immediately to add variables on initial load
<div onclick="showExtendedText(event)">Click to see more </div>
Or you could do it by adding the variable as a data-txt attribute right onto the element itself, in which case you don't even need the addVariable() function:
function showExtendedText(event) {
const currentElement = event.currentTarget
currentElement.innerHTML += currentElement.dataset.txt
}
<div onclick="showExtendedText(event)" data-txt="This is the extended data">Click to see more </div>
To access the data/variable for the specific element that you clicked on, you have to pass the event object as a function paramater. This event object is given to you automatically by the click event (or any other event).
Elements have attributes, so you can put the information into an attribute. Custom attributes should usually be data attributes. On click, check if a parent element has one of the attributes you're interested in, and if so, toggle that parent.
document.addEventListener('click', (e) => {
const parent = e.target.closest('[data-spoiler]');
if (!parent) return;
const currentMarkup = parent.innerHTML;
parent.innerHTML = parent.dataset.spoiler;
parent.dataset.spoiler = currentMarkup;
});
<div data-spoiler="foo">text 1</div>
<div data-spoiler="bar">text 2</div>
That's the closest you'll get to "a variable that is local to a specific html element". To define the text completely in the JavaScript instead, one option is to use an array, then look up the clicked index of the spoiler element in the array.
const spoilerTexts = ['foo', 'bar'];
const spoilerTags = [...document.querySelectorAll('.spoiler')];
document.addEventListener('click', (e) => {
const parent = e.target.closest('.spoiler');
if (!parent) return;
const currentMarkup = parent.innerHTML;
const index = spoilerTags.indexOf(parent);
parent.innerHTML = spoilerTexts[index];
spoilerTexts[index] = currentMarkup;
});
<div class="spoiler">text 1</div>
<div class="spoiler">text 2</div>
There are also libraries that allow for that sort of thing, by associating each element with a component (a JavaScript function/object used by the library) and somehow sending a variable to that component.
// for example, with React
const SpoilerElement = ({ originalText, spoilerText }) => {
const [spoilerShown, setSpoilerShown] = React.useState(false);
return (
<div onClick={() => setSpoilerShown(!spoilerShown)}>
{ spoilerShown ? spoilerText : originalText }
</div>
);
};
const App = () => (
<div>
<SpoilerElement originalText="text 1" spoilerText="foo" />
<SpoilerElement originalText="text 2" spoilerText="bar" />
</div>
)
ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div class='react'></div>
Thanks everybody for your answers, which helped immensely! However, as a minimalist, I took all that I learned from you and came up with what I believe is the simplest possible code achieving my goal:
<div spoiler = "foo" onclick="this.innerHTML=this.getAttribute('spoiler')">
Click for spoiler
</div>
<div spoiler = "bar" onclick="this.innerHTML=this.getAttribute('spoiler')">
Click for spoiler
</div>

Calling a function from a JS file not working

I'm using React to view my pages.
I came across this problem if I try to call a function from a .js file nothing happens.
Basically, I have a small program that has two columns. Each column has a <p> tag that contains Column 1 and Column 2. There is a button below that once you click on it, both Columns should switch.
index.js
import "../style.css";
//import "./java.js";
class index extends React.Component {
render() {
return(
<div className="container">
<div className="columns" id="columnsToSwitch">
<div className="column1" id="column1_id">
<p>Column1</p>
</div>
<div className="column2" id="column2_id">
<p>Column 2</p>
</div>
</div>
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
</div>
);
}
}
export default index;
java.js
const switchCol = document.querySelectorAll("div.columns");
const el = document.getElementById("switch_id");
if(el) {
el.addEventListener("click", switchColumns, false);
}
switchCol.forEach(switches => switches.addEventListener('click', switchColumns));
function switchColumns(){
const makeSwitch1 = document.getElementById('column1_id');
document.getElementById('column2_id').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2_id');
document.getElementById('column1_id').appendChild(makeSwitch2);
}
Method 1:
I tried to import the .js file that contains the function.
Nothing is happening after clicking "Switch".
Method 2:
Using onClick within a tag.
<div className="switch" id="switch_id" onClick={this.switchColumns}>Switch</div>
I get a couple of errors,
Uncaught TypeError: Cannot read properties of undefined (reading 'switchColumns')
The above error occurred in the <index> component:
On This line:
const switchCol = document.querySelectorAll(".switchCol");
There's no elements with the class of 'switchCol' so you're going to get an empty NodeList which causes the forEach loop to not execute so there are no click events on the columns themselves.
In the forEach block:
switchCol.forEach(switches => {
switches.addEventListener("column1", switchColumns);
switches.addEventListener("column2", switchColumns);
});
"column1" and "column2" are not valid event listeners, and there doesn't need to be two event listeners for one element. I think you mean to write the following:
switchCol.forEach(switch => switch.addEventListener('click', switchColumns))
Now onto your main switching column function:
function switchColumns(){
const makeSwitch1 = document.getElementById('column1');
document.getElementById('column2').appendChild(makeSwitch1);
const makeSwitch2 = document.getElementById('column2');
document.getElementById('column1').appendChild(makeSwitch2);
}
Variables makeSwitch1 and makeSwitch2 are going to be undefined as you do not have any elements with an id of column1 and column2 respectfully. Which is causing your issue with the second fix you tried.

how to use wrapper.find to find the text of div' div element

I am trying to write enzyme test and would like to access the text of the following div.
<div className="toolbar__contentInformation">
<div className="text smallfont ellipsis">Alex</div>
<div className="text smallfont ellipsis">12</div>
</div>
test.js
let component = React.createElement(App});
let wrapper = enzyme.enzyme.mount(component);
let val = wrapper.find('div.toolbar__contentInformation') //how to access text = Alex ???
There are a couple of ways to achieve your goal.
let val = wrapper.find('div.toolbar__contentInformation').chidlren();
expect(wrapper.find('div.toolbar__contentInformation').childAt(0).text()).to.equal('Alex');
expect(wrapper.find('div.toolbar__contentInformation').childAt(1).text()).to.equal('12');
Or you can refer directly to children using this function at():
expect(wrapper.find('.text.smallfont.ellipsis').at(0).text()).to.equal('Alex');
expect(wrapper.find('.text.smallfont.ellipsis').at(1).text()).to.equal('12');
Or use function first and last()
expect(wrapper.find('.text.smallfont.ellipsis').first().text()).to.equal('Alex');
expect(wrapper.find('.text.smallfont.ellipsis').last().text()).to.equal('12');
Alternatively, you can simply refer to the:
wrapper.find('div.toolbar__contentInformation')[index]

Test for an element to exist only once in Jest/RTL

I am testing a component with which you can add sub-components by pressing on a '+' icon.
The rendered HTML is somewhere in the lines of:
<div>
<div>
<div>
From <input type="text" />
</div>
<div>
To <input type="text" />
</div>
<div>+</div>
</div>
</div>
So in the initial test, I test for the text to be there:
// test setup
test('From and to occur only once', () => {
const { getByText } = setup();
expect(getByText('From')).toBeInTheDocument();
expect(getByText('To')).toBeInTheDocument();
});
This all works perfectly fine. But I want to make sure initially the content is shown only once.
So my next test would be something in the lines of:
// test setup
test('When add button is clicked there From and To exist two times', () => {
const { getByText } = setup();
const addButton = getByText("+")
// first row
expect(getByText('From')).toBeInTheDocument();
expect(getByText('To')).toBeInTheDocument();
fireEvent.click(addButton);
// second row
expect(getByText('From')).toBeInTheDocument();
expect(getByText('To')).toBeInTheDocument();
});
How would I make a differentiation between the first and second time the elements occur?
Consider using getAllBy (after the click) instead of getBy. That can produce an array of all the elements found and you can expect() the total length of that to be 2.

how to get an outerHTML, innerHTML, and getText() of an element

I'm newbie to protractor framework, and I've been trying for a while to figure out how to get the outerHTML/InnerHTML/getText() (child elements) so that I can test if an element <img> is rendered onto a view. Heads up, we've an ng-grid and I'm trying to look up in its first column to see if it contains an img element also check if it contains an attribute i.e. src=res/someImg.png.
Here is what I got
html
<div>
<a>
<div>
<div>
<span>
<i><img src="res/someImg.png"></i>
</span>
</div>
<div>
...
</div>
<div>
...
</div>
</div>
</a>
</div>
test
it('should render an icon in agent list', function () {
var row = element.all(by.repeater('row in renderedRows')).get(3);
expect(row).not.toEqual(null); //pass
expect(row.element(by.css('img')).getAttribute('src').getText()).toMatch(/someImg.png/);//fail with null
expect(row.element(by.css('span')).outerHTML).toBe('<i><img src="res/someImg.png"></i>'); //fails
expect(row.element(by.css('i')).innerHTML).toBe('<img src="res/someImg.png">'); //fails
});
Can someone tell what am I doing wrong please?
Use getAttribute() in all 3 cases - for src, outerHTML and innerHTML:
expect(row.element(by.css('img')).getAttribute('src')).toMatch(/someImg.png/);
expect(row.element(by.css('span')).getAttribute('outerHTML')).toBe('<i><img src="res/someImg.png"></i>');
expect(row.element(by.css('i')).getAttribute('innerHTML')).toBe('<img src="res/someImg.png">');
Tested - works for me.
A little more explicitly:
expect(row.element(by.css('img')).getAttribute('src')).toMatch(/someImg.png/);
expect(row.element(by.css('span')).getOuterHtml()).toBe('<i><img src="res/someImg.png"></i>');
expect(row.element(by.css('i')).getInnerHtml()).toBe('<img src="res/someImg.png">');
As alecxe said on Aug 24 '16, "getOuterHtml() and getInnerHtml() are now deprecated in WebDriverJS and Protractor" (see comment from https://stackoverflow.com/a/27575804/3482730)
You should now use the following to get innerHTML code (as indicated here: https://github.com/angular/protractor/issues/4041#issuecomment-372022296):
let i = browser.executeScript("return arguments[0].innerHTML;", element(locator)) as Promise<string>;
Example using a helper function:
function getInnerHTML(elem: ElementFinder): Promise<string> {
return getInnerHTMLCommon(elem, elem.browser_);
}
function getInnerHTMLCommon(elem: WebElement|ElementFinder, webBrowser: ProtractorBrowser): Promise<string> {
return webBrowser.executeScript("return arguments[0].innerHTML;", elem) as Promise<string>;
}
const html = await getInnerHTML(browser.element(by.xpath("div[1]")));
console.log(html);

Categories

Resources