I m having some trouble testing the apparition of a loader when clicking a button.
I have the following three test :
it('#exigence1 - display a failing message on button fail ', () => {
page.navigateTo();
page.getButtonFail().click();
expect(page.getErrorMessage()).toBeTruthy();
});
it('#exigence2 - display a loader on waiting list ', () => {
page.navigateTo();
page.getButtonLoad().click();
expect(page.getLoader()).toBeTruthy();
});
it('#exigence3 - display a list of items on message ', () => {
page.navigateTo();
page.getButtonLoad().click();
expect(page.getPokeList()).toBeTruthy();
});
When I click the getButtonLoad, I display a loader during the remote fetch. I need to test that this loader appears on the page, but my previous second test doesn't pass.
Here's my implementation :
buttonClick() {
this.showError = false;
this.displayLoader = true;
this.http.get('http://pokeapi.co/api/v2/pokemon/')
.map(res => res.json())
.subscribe(res => {
this.pokemons = res.results;
this.displayLoader = false;
});
}
With my page definition :
export class ProtractorDemonstrationPage {
navigateTo() {
return browser.get('/');
}
getPokeList() {
return element(by.css('.poke-list')).getText();
}
getButtonLoad() {
return element(by.css('.btn-primary'));
}
getButtonFail() {
return element(by.css('.btn-warn'));
}
getErrorMessage() {
return element(by.css('.panel-danger')).getText();
}
getLoader() {
return element(by.css('.loader')).getText();
}
}
I don't know at all how to make a test that validate this case.
EDIT : What I see there is that the click, when clicked, is waiting for the function to finish its stuff before getting to the next instruction. Here's my problem. I need to click, and don't wait for the async stuff and directly check that my loader is there.
EDIT 2 : Here's the code of the function :
load() {
this.showError = false;
this.displayLoader = true;
this.http.get('http://pokeapi.co/api/v2/pokemon/')
.map(res => res.json())
.subscribe(res => {
this.pokemons = res.results;
this.displayLoader = false;
});
}
Does
The issue is with page.getLoader() and your expect statement. With the page.getLoader() call you are returning the text of that element but in your expect statement you are expecting this function to return a boolean value.
You should change your page.getLoader() to return:
return element(by.css('.loader')).isDisplayed();
or use keep using .getText() but change your expect statement to:
expect(page.getLoader()).toBe('This string');
Related
_subscriptions.push(
// Update list on OG selection
ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.getSubOgs();
this.updateGroup(group);
},
),
I have a block of code inside subscribe. However, it seems that they are not executed in order. this.getSubOgs() is executed before this.getDevices(). Both are HTTP calls that returns an observable. How do I make sure this.getDevices() is executed first?
Click to See Codes
this is basically #MishaMashina's suggestion
_subscriptions.push(ogViewModel.current$.subscribe(
(group) => {
this.groupUuid = group.ogUuid;
this.groupId = group.id;
this.getDevices();
this.updateGroup(group);
},
),
both getDevices() and getSubOgs() remain seperate methods
in
public getDevices() { this.myApiClient.getDevices().Subscribe(
let onlyThisTime: boolean = true;
(next) => doStuff
(error) => doErrorStuff
() => {if(onlyThisTime){this.getSubOgs();}}
)}
```
function displayResults(responseJson) {
const gamedata = responseJson.results.map(game => {
return {
name: game.name,
consoles: game.platforms,
metacritc: game.metacritic,
genre: game.genres
};
});
console.log(gamedata);
inputData(gamedata);
}
function platformdata(consoles) {
return consoles.map(system => {
return system.platform.name;
});
}
function inputData(gamedata) {
gamedata.map(input => {
$(`#home-list`).html(`
<h1>${input.name}</h1>
<h5>${input.metacritc}</h5>
<span>${input.system}</span>
`);
});
}
```
I have been trying to get information from an array but have not been successful in obtaining the information. The information for the game platforms is somewhat nested and I have been trying to dig it out but to no avail.
https://api.rawg.io/api/games?page_size=1
Best way I can show the information more in detail is to just advise to throw the link above into postman and you'll see what I am trying to work with. Basically it is under results > platforms > platform > name. When I add this information into the map function it comes up undefined. Running it now they come up with saying object with commas. I'd like it to just come up with just the information leaving out the commas. I can't figure out how to get join() to go into html(). Thank you very much!
Edit:
1) Results I'd like is to be able to pull up is within the platforms tree but is buried. If I just use game.platforms it produces [object, Object]. If I try to add more to the line in gamedata it will produce undefined.
2) In "gamedata.map(input => {" ?
3) Yes I tried making a helper function based on code I found online. The code I found online used excessive li and ul
```
function platformnames(platforms) {
return platforms.map(system => {
return '<li>' system.platform.name + '</li>';
});
}
function pullArray(gamedata) {
gamedata.map(function(input) {
let platformNames = input.platforms.map(
system => `<li>${system.platform.name}</li>`
);
$(`#home-container`)
.append(`<li><ul><li>${platformNames}</li></ul></li>`)
.join(' ');
});
}
```
This worked but gave really odd results.
4) No I'm adding it all to the same ID as one pull.
5) That is me trying to mine the information from platforms on an API. It's buried in there and I haven't found a good solution.
function formatParams(params) {
const queryItems = Object.keys(params).map(
key => `${key}=${params[key]}`
);
console.log(queryItems);
return queryItems.join('&');
}
const opts = {
headers: {
'User-Agent': `<ClassProject> / <VER 0.01> <Currently in Alpha testing>`
}
};
function fetchAPI() {
const params = {
...($('.search-param').val() && {
search: $('.search-param').val()
}),
...($('.genre-param').val() && {
genres: $('.genre-param').val()
}),
...($('.platforms-param').val() && {
platforms: $('.platforms-param').val()
}),
...($('.publishers-param').val() && {
publishers: $('.publishers-param').val()
}),
page_size: '1'
};
console.log(params);
const baseURL = 'https://api.rawg.io/api/games';
const queryString = formatParams(params);
let url = `${baseURL}?${queryString}`;
console.log(url);
fetch(`${url}`, opts)
.then(response => response.json())
.then(responseJson => displayResults(responseJson))
.catch(error => {
console.log(`Something went wrong: ${error.message}`);
});
}
function displayResults(responseJson) {
const gamedata = responseJson.results.map(game => {
return {
name: game.name,
consoles: game.platforms,
metacritc: game.metacritic,
genre: game.genres
};
});
console.log(gamedata);
inputData(gamedata);
}
function inputData(gamedata) {
let html = '';
gamedata.forEach(input => {
html += `<h1>${input.name}</h1>`;
html += `<h5>Metacritic: ${input.metacritic ||
'No metacritic rating'}</h5>`;
html += 'Platforms:<br />';
input.consoles.forEach(e => {
html += `<span>${e.platform.name}</span><br />`;
});
html += `<br /><span>System: ${input.system}</span>`;
});
document.getElementById('home-list').innerHTML = html;
}
function pageLoad() {
$(document).ready(function() {
fetchAPI();
});
}
pageLoad();
So I'm close thanks to the help of everyone here. Now I'm returning "Metacritic: No metacritic rating" or if I remove that or part an undefined. What am I missing?
The snippet below gets you the platform names. I modified/created
the displayResults() function to only return a value (and also corrected the typo in metacritic (metacritc -> metacritic))
the inputData() function to create a correct HTML and append it to the container
a fetchData() function to actually fetch the data
an unnamed function to initiate fetch and display the data
You should look at your data - you don't use game.genres (although you map it) and you would like to display input.system that is not mapped.
function displayResults(responseJson) {
return responseJson.results.map(game => {
return {
name: game.name,
consoles: game.platforms,
metacritic: game.metacritic,
genre: game.genres
};
});
}
function platformdata(consoles) {
return consoles.map(system => {
return system.platform.name;
});
}
function inputData(gamedata) {
let html = ''
gamedata.forEach(input => {
html += `<h1>${input.name}</h1>`
html += `<h5>Metacritic: ${input.metacritic || 'No metacritic rating'}</h5>`
html += 'Platforms:<br />'
input.consoles.forEach(e => {
html += `<span>${e.platform.name}</span><br />`
})
html += `<br /><span>System: ${input.system}</span>`
});
document.getElementById('home-list').innerHTML = html
}
async function fetchData() {
const data = await fetch('https://api.rawg.io/api/games?page_size=5')
const json = await data.json()
return json
}
(async function() {
const json = await fetchData()
inputData(displayResults(json))
})();
<div id="home-list"></div>
And although it does work - you're not supposed to use more than one h1 tag on a site - it will be an HTML validation warning (SEO!). If you will display only one game per page, then forget my remark :)
I'm working on an Angular application, but I'm in trouble with the update of the datas.
1- I'm creating a datagrid
2- I'm requesting to get the data from a specific user
3- I'm requesting to get the name of a department by it's uuid (contained in the previous request) : informatique
4- Datagrid is updated
When I modified the name of department without a rest request, the name is updated. But, when, in the same location in the code, I replace this by the request, the datagrid is not well updated. (but the result is well recepted)
ngOnInit() {
this.context = { componentParent: this };
this.subscription.push(this.translate.get('Purchase').subscribe((res) => {
this.gridOptions.onGridReady = (params) => {
this.gridApi = params.api;
this.gridApi.setColumnDefs(this.createColumnDef());
this.gridApi.refreshCells({ force: true });
this.gridOptions.api.sizeColumnsToFit();
}
// Limit Purchases to user budgetscope matches
this.userService.getUserInfo().subscribe((user) => {
this.subscription.push(this.purchase.findByUserId(user.id).subscribe((purchases: PurchaseOrder[]) => {
this.rowData = purchases;
//change the uuid of the department by it's name
this.rowData.forEach(element => {
console.log('1');
let mafonction = (element) => {
this.departmentService.findByDepartmentUuid(element.department).subscribe((department: Department) => {
element.departement = department.name;
console.log(element.departement);
});
};
mafonction(element);
});
console.log('2');
}))
}),
() => {
// ...
};
}));
console.log('4');
}
When I'm executing this code, I got, in the console :
1
2
informatique
but no name in the datagrid.
When I just replace :
this.rowData.forEach(element => {
console.log('1');
// ...
mafonction(element);
});
by
this.rowData.forEach(element => {element.department = "nothing2"});
'nothing2' is displayed in the datagrid.
I solved the issue : this.rowData = purchases; should have put in the last block (department.findByUuid), in order to display the last datas.
I have modal component with form. I want to inform fields of this form that form data was successfully sent to database and clear its fields.
Component code:
//ItemModal.js
addItem(e) {
e.preventDefault();
const item = {
id: this.props.itemsStore.length + 1,
image: this.fileInput.files[0] || 'http://via.placeholder.com/350x150',
tags: this.tagInput.value,
place: this.placeInput.value,
details: this.detailsInput.value
}
console.log('addded', item);
this.props.onAddItem(item);
this.fileInput.value = '';
this.tagInput.value = '';
this.placeInput.value = '';
this.detailsInput.value = '';
this.setState({
filled: {
...this.state.filled,
place: false,
tags: false
},
loadingText: 'Loading...'
});
}
...
render() {
return (
<div className="text-center" >
<div className={"text-center form-notification " + ((this.state.loadingText) ? 'form-notification__active' : '' )}>
{(this.state.loadingText) ? ((this.props.loadingState === true) ? 'Item added' : this.state.loadingText) : '' }
</div>
)
}
action.js
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item )
.then(res => {
dispatch({type:"ADD_ITEM", item});
dispatch({type:"ITEM_LOADED", status: true});
})
}
helper.js
else if (action.type === 'ITEM_LOADED') {
const status = action.status;
return {
...state,
isItemLoaded: status
}
}
Currently I have few issues with my code:
1. field are clearing right after click, but they should clear after changing state of loadingState. I tried to check it in separate function on in componentWillReceiveProps whether state is changed and it worked, but I faces another problem, that after closing this modal there were errors, that such fields doesn't exist.
2. loadingText should become '' (empty) after few seconds. Tried same approach with separate function and componentWillReceiveProps as at first issue.
In constructor keep a copy of your initial state in a const as follows:
const stateCopy = Object.create(this.state);
When your ajax request completes, in the sucess callback you can reset the state with this copy as follows:
this.setStae({
...stateCopy
});
One of the few ways to achieve this is to use async await which will resolve the promises and then return the value after that you can clear the values
1st approach using the async await
Here is the example
handleSubmit = async event => {
event.preventDefault();
// Promise is resolved and value is inside of the response const.
const response = await API.delete(`users/${this.state.id}`);
//dispatch your reducers
};
Now in your react component call it
PostData() {
const res = await handleSubmit();
//empty your model and values
}
Second approach is to use the timer to check the value is changed or not
for this we need one variable add this to the service
let timerFinished=false;
one function to check it is changed or not
CheckTimers = () => {
setTimeout(() => {
if (timerFinished) {
//empty your modal and clear the values
} else {
this.CheckTimers();
}
}, 200);
}
on your add item change this variable value
export function onAddItem(item) {
axios.post('http://localhost:3001/api/items/', item)
.then(res => {
timerFinished = true;
dispatch({
type: "ADD_ITEM",
item
});
dispatch({
type: "ITEM_LOADED",
status: true
});
})
}
and here is how we need to call it.
PostData = (items) => {
timerFinished = false;
onAddItem(items);
this.CheckTimers();
}
If you check this what we done is continuously checking the variable change and emptied only once its done.
One thing you need to handle is to when axios failed to post the data you need to change the variable value to something and handle it, you can do it using the different values 'error','failed','success' to the timerFinished variable.
I'm trying to execute 3 functions, and after than console.log the values that they change. I think there should be better approach for this kind of problems, but I'm not sure what it is. What I've done is I went old school, and added loading flag. Basically, loading = 3, when function is loaded, loading--
I'd like to demonstrate my current code (well actually it's not the same, but it will work for demo purposes), so you can get the feeling:
data:() => ({
loading: 3,
first: null,
second: null,
third: null
}),
methods: {
first() {
this.$route.get('/data/for/first').then(response => {
this.first = response.data;
this.loading--;
})
},
second() {
this.$route.get('/data/for/second').then(response => {
this.second = response.data;
this.loading--;
})
},
third() {
this.$route.get('/data/for/third/a').then(responseA => {
let thirdA = responseA.data;
this.$route.get('/data/for/third/b').then(responseB => {
let thirdB = responseB.data;
if (thirdA === thirdB) {
this.third = true;
}
this.loading--;
})
})
},
fireFunctions() {
this.first();
this.second();
this.third();
}
},
watch: {
loading: function() {
if (this.loading === 0) {
console.log(this.first, this.second, this.third)
}
}
}
The output looks like this:
dataForFirst, dataForSecond, dataForThird;
But, if I don't use the watcher, and load this.fireFunctions() in mounted() i get:
dataForFirst, dataForSecond, undefined;
Now, as I understand, this is happening because this.third() needs more time to process the data. As you can see in the code, I added loading flag. So, fire functions will only execute when all of the functions are loaded.
I don't think this is the best approach, so I'd like to hear your opinion on this one.
How would you handle it?
Use Promise.all to wait on all your async functions to return and then run whatever code you need to afterward, example:
methods: {
async all() {
let [first, second, third] = await Promise.all([
this.$route.get('/data/for/first'),
this.$route.get('/data/for/second'),
this.$route.get('/data/for/third')
]);
this.first = first;
this.second = second;
this.third = third;
console.log(first, second, third);
}
}