I'm trying to understand how do i return a boolean from cypress page object?
Use case:
I am trying to check if an element is present on the page. If so,
return a boolean.
Code:
class DataTable {
constructor() {
return this;
}
has(fieldName) {
// HOW DO I RETURN A BOOLEAN HERE?
return cy.get('.view-and-field-name').contains(fieldName)
}
}
Mainpage.spec.js
const dataTable = new DataTable();
expect(dataTable.has(field.fieldName)).to.be.true;
I'd appreciate some insights on what I am doing wrong.
Thank you for the time.
According to this post, and according to cypress suggestions for custom commands and page objects I suggest the next custom command:
Cypress.Commands.add("validateIfElementExistsInDom", (selector) => {
cy.get('body')
.then($body => {
expect($body.find(selector).length > 0).to.be.true
})
})
And you can use it in the test like :
cy.validateIfElementExistsInDom(fieldName)
And a general return boolean function example:
Cypress.Commands.add("validateIfElementExistsInDomAsBoolean", (selector) => {
return cy.get('body')
.then($body => {
return cy.wrap($body.find(selector).length > 0) //Cy wrap is needed so the function is chainable
})
})
//in test code
cy.validateIfElementExistsInDomAsBoolean(fieldName)
.then(boolean => expect(boolean).to.be.true)
The expect() on Mainpage has already been executed internally in cy.get('.view-and-field-name').contains(fieldName), so it is redundant.
If dataTable.has(field.fieldName) fails, the test stops there - it will never pass out a false value.
If you want has(fieldName) to pass out a boolean, you need to switch to jquery inside, e.g
has(fieldName) {
const jquerySelector = `.view-and-field-name:contains(${fieldName})`;
return Cypress.$(jquerySelector);
}
But it is much simpler to go with Cypress commands and use
const dataTable = new DataTable();
dataTable.has(field.fieldName);
Personally, I'd ditch the page object and just use the command directly in the test.
cy.get is not a synchronous function due to the retry-ability strategy,
so there is no way to return Boolean directly.
If necessary, you can use variant of should with callback.
In your case, a simpler variant with exist is suitable (or be.visible, possibly more accurate for your case):
class DataTable {
has(fieldName) {
return cy.get('.view-and-field-name').contains(fieldName);
}
}
const dataTable = new DataTable();
dataTable.getField(field.fieldName).should('exist');
Related
I have a Store which will be provided to the component. In this Store file, there are several getter function. But I find only this getter function will be executed three times since this.rawMonthlyImpacts will be only changed once when the api get response from backend. I am so confused because other getter function in this file will be only executed once. During every execution, this.rawMonthlyImpacts is always same. Because this function is time-consuming, so I want to figure out why this happens. Hope you can give me some advice. Thanks!
get Impacts(){
const monthlyImpacts = new Map<string, Map<string, number>>();
if (this.rawMonthlyImpacts) {
this.rawMonthlyImpacts.forEach((impact) => {
if (impact.Impact > 0) {
const month = TimeConversion.fromTimestampToMonthString(impact.Month);
const tenantId = impact.TenantId;
const tenantImpact = impact.Impact;
if (!monthlyImpacts.has(month)) {
const tenantList = new Map<string, number>();
monthlyImpacts.set(month, tenantList.set(tenantId, tenantImpact));
} else {
const tenantWithImpactMap = monthlyImpacts.get(month);
if (!tenantWithImpactMap.has(tenantId)) {
tenantWithImpactMap.set(tenantId, tenantImpact);
} else {
tenantWithImpactMap.set(tenantId, tenantWithImpactMap.get(tenantId) + tenantImpact);
}
monthlyImpacts.set(month, tenantWithImpactMap);
}
}
});
}
return monthlyImpacts;
},
Update: I have find that there are other two functions use this.Impacts. If I remove these two functions, the getter function will only be executed only once. I think the getter function uses the cache to store data, so once the data is calculated for the first time, subsequent calls to the getter function should not be re-executed, only the value in the cache needs to be retrieved. So I am very confused about why this getter function will be executed 3 times.
getImpactedTenants(month: string): string[] {
return Array.from(this.Impacts.get(month).keys());
},
get overallMonthlyImpactedTenants(): Map<string, number> {
return new Map<string, number>(
Array.from(this.Impacts)?.map((monthEntries) => {
const month = monthEntries[0];
const impactedTenants = monthEntries[1].size;
return [month, impactedTenants];
})
);
}
Hard to tell what exactly is happening without more context, but remember that with a get function, every single time you reference that property (.Impacts in this case) the get function will be called.
Assuming that each impact stored in this.rawMonthlyImpacts which you loop through is an instance of the class with this getter, then as far as I'm aware, you are calling the get function each time you reference impact.Impacts, such as in the conditional:
if (impact.Impact > 0) {
I might be way off though; I'm unfamiliar with React and so my answer is based only on my experience with vanilla JS.
I have the knowledge of unit testing standalone functions like helper classes but how do I deal with functions that are not standalone, typically within a class file that has multiple validations and results?
Example below showing multiple conditions check and response in different result.
Do I call the valueCheck and proceedApiCheck function in my test case? But there're different scenarios or actions I don't need within test. (eg, setState / navigating)
Do I write a new function of valueCheck and proceedApiCheck in my test case? But this will means I'm having 2 different logic in my code. Someday if I change my logic in app, my test case will not fail since it's referring to the old logic.
Can any of you shed some light on this?
Example
export class Screen1 extends React.Component {
valueCheck = (value) => {
if(value === 'abc'){
this.setState({ isNavigating:true, transfer: true })
this.proceedApiCheck(value)
}
if(value === '123'){
this.setState({ isNavigating:true, transfer: false })
this.proceedApiCheck(value)
}
}
proceedApiCheck = async(value) =>{
let data
try{
data = await FirstApi(value);
this.setState(data)
}catch(){
this.navigateToScreen('Failure')
return;
}
switch(data.name){
case 'fake adidas':
this.navigateToScreen('Failure')
return;
case 'fake nike':
this.navigateToScreen('Failure')
return;
}
try{
const result = await secondApi(data.price);
switch(result.currency){
case 'EURO':
this.navigateToScreen('Euro')
case 'Pound':
this.navigateToScreen('Pound')
default:
this.navigateToScreen('Dollar')
}
}catch(){
this.navigateToScreen('Failure')
return;
}
}
}
You've made a valuable discovery:
The most straightforward way to write code is not necessarily the most robust way to write code.
This unfortunate problem is exacerbated by the way most people use classes: the implicit this reference makes it very easy to do too much in one method, let alone one class. And that has led you to a second valuable discovery:
One of the most valuable things about isolated unit tests is they give you feedback on your design.
Here the difficulty isolating the test is telling you that you've coupled separate concerns in a way that's difficult to tease apart. You've got a method with seven (7!!) different exit points, and now you're stuck because that's a lot of mocking to try to trigger the appropriate logic to make sure you hit all of them.
Consider the following alternative:
const FAIL = {}; // could also use Symbol() here, any unique ref
const BAD_NAMES = ['fake whatever'];
async function apiCall1() {
const resp = await fetch(someURL);
return resp.json();
}
function validate1(data) {
return data?.name === undefined || BAD_NAMES.includes(data?.name)
? FAIL
: data.name;
}
// You can imagine what validation and fetching look like for
// the second API call
function processData(data) {
switch(data.currency){
case 'EURO':
return 'Euro';
case 'Pound':
return 'Pound';
default:
return 'Dollar';
}
}
async function doTheThing() { // use a better name IRL
try {
const first = await apiCall1();
const data = validate1(first);
if (data === FAIL) throw new Error('whatever');
const second = await apiCall2(data.whatever);
const data2 = validate2(second);
if (data2 === FAIL) throw new Error('something else');
// process data we now know is good.
return processData(data);
} catch (err) {
console.error(err);
return 'Failure';
}
}
class Screen1 extends React.Component {
async proceedApiCheck () {
const nextScreen = await doTheThing();
this.navigateToScreen(nextScreen);
}
}
Here we return the failure screen trigger in one place only. The functions all have clear exits.
There is a convenience function that unboxes the request (or hides the details of an xhr if that's how you roll).
All of the validation and business logic is independently testable. It's also in one place instead of spread on various places in a lengthy method.
All of the logic is in plain functions that return values, no mocks needed other than for fetch.
The only thing that proceedApiCheck does is get the value from the logic function and navigate to the correct screen, and is simple to mock for a test.
You could, if you're really in to classes, also make all those functions static methods of your class if that's how you roll, but the important thing is that almost none of your tests need complicated mocks, and it should be much more obvious what the code paths are and how to test them.
The high-falutin' software engineering practice you want to adhere to (as pointed out by #DrewReese in the comments) is the Single Responsibility Principle which states that functions/methods should only do one logical operation. A validator only validates, a conditional only does dispatch, a function that gets data from an external source should only get the data (rather than act on it), etc.
if you think it is necessary to do unit test for valueCheck (maybe it is a complicated function in real case), move it outside the class and make it testable alone. Then it should be something like:
function valueCheck(value) {
switch(value) {
case 'abc':
return {shouldProceedApiCheck: true, newState:{ isNavigating:true, transfer: true }}
case '123':
return {shouldProceedApiCheck: true, { isNavigating:true, transfer: false }}
default:
return {shouldProceedApiCheck: false, newState: {} }
}
.....
class Screen1 extends React.Component {
....
whenToCall = ()=>{
const {newState, shouldProceedApiCheck} = valueCheck(value)
this.setState(valueCheck(newState), ()=>{
if(shouldProceedApiCheck) {
this.proceedApiCheck(value)
}
})
}
....
}
Further reading: https://medium.com/front-end-weekly/making-testable-javascript-code-2a71afba5120
I made an input that let me filter a table of softwares.
<input type="text" id="softwares-search" class="form-control" aria-label="Input de recherche" aria-describedby="softwares-search">
Then in javascript my filter work well if I console.log(....)
But when I replace it with a return, nothing is returned. I think it is due to my var affectation through the event listener :
const maxwell = () => {
search = document.querySelector('#softwares-search').value;
return softwares.filter(row => row.name.includes(search) || row.description.includes(search));
}
const softwaresSearch = document.querySelector('#softwares-search');
if (softwaresSearch) {
var results = softwaresSearch.addEventListener('keyup', maxwell)
console.log(results);
}
Thank all
EDIT 1 :
I was so angry, so blind, I had S#!t in my eyes, no need to use a global :(
const softwaresSearch = document.getElementById('softwares-search');
if (softwaresSearch) {
softwaresSearch.addEventListener('keyup', (e) => {
search = document.getElementById('softwares-search').value;
var filtredSoftwares = softwares.filter(e => e.name.includes(search) || e.description.includes(search) );
renderTable(filtredSoftwares);
});
}
const renderTable = (softwares) => {
Object.values(softwares).forEach(value=>{
console.log(value);
});
// Todo build HTML table
}
Instead of returning I think you just need to replace the current array like this
const maxwell = () => {
search = document.querySelector('#softwares-search').value;
softwares = softwares.filter(row => row.name.includes(search) || row.description.includes(search));
}
And results is not needed:
const softwaresSearch = document.querySelector('#softwares-search');
if (softwaresSearch) {
softwaresSearch.addEventListener('keyup', maxwell)
}
As far as I know, softwareSearch.addEventListener won't return anything, since that is an event listener, and does not return any value. It simply executes the function passed in the 2nd parameter. You could try doing this instead
softwaresSearch.addEventListener('keyup', () => {
var results = maxwell();
console.log(results);
});
What this would do is that, it would call your maxwell function when the keyup event, since that is what it looks you are trying to do.
Please share all relevant code before posting a question, this code includes the variable "softwares" that exist outside what is visible to us.
Additionally, there are some issues with your code.
I don't understand your naming of the maxwell function. You should name functions as verbs, not anything else. A function is a machine that is doing something, and possibly returning something. It should be named to what it is doing.
On the second line, you say "search = ... ", but you didn't declare it as a variable.
You are returning something based on a value that isn't validated ('search' can be either undefined or a string value in this case), hence, your return will most likely just return undefined and not any value at all.
Your function can possibly not return anything at all since you are returning something within your if-statement. You can use a closure to always return something.
I would also suggest passing a search string as a variable to your function that should return a list based on the search query. Getting in the habit of short, concise functions with expected inputs/outputs, will make your code more readable and less error-prone and less likely to produce unwanted side-effects.
I don't know the rest of your code, but I don't recommend assigning variables in the global scope. Your "maxwell", "softwareSearch" variables both exist in the global space, unless you have wrapped them in another function block already (such as jquerys $(document).ready(() => { ...everything here is scoped })
You are getting the same element in two different places in your code.
Here is an updated code sample, but I can't test it since I don't know the rest of your code.
/*
* Executing the whole thing in this IIFE will make all variables declared inside here scoped to this block only,
* thus they can't interfere with other code you may write
*/
(() => {
const listOfSoftwares = softwares; // --- get your softwares variable here somehow, I don't know where "software" comes from.
// input element
const search = document.querySelector('#softwares-search');
/**
* Filter search results
* #param {string} query Search query
* #returns {Array} The results array
*/
const filterSoftwareSearchResults = (query) => {
let results = [];
results = listOfSoftwares.filter(software => software.description.includes(query) || software.title.includes(query))
// Verify
console.log(results);
// Should return array of results, even if empty
return results;
}
if (search) {
search.addEventListener('keyup', () => {
filterSoftwareSearchResults(search.value)
})
}
})()
The addEventListener function always returns undefined, so your results variable is undefined.
Returning from the callback function (maxwell) is also of no use.
You either need to do something with the data inside of your callback, or maybe pass the data to a global variable.
I have a selector that returns an array. The elements in the array themselves have derived data. I essentially need a recursive memoization selector that returns a derived array composed of derived elements.
my current attempt is:
export const selectEntitesWithAssetBuffers = createSelector(
[selectSceneEntities, getAssets],
(entities, loadedAssets) => {
return entities.map((entity) => {
entity.buffers = entity.assets.map((assetName) => {
return loadedAssets[assetName].arrayBuffer;
})
return entity;
})
}
)
My concerns here are anytime entities or loadedAssets change this will recompute the entire list. What I'm expecting to setup is something like a selectEntityWithBuffer that would get passed to the entities.map. Ideally, I want this to only recompute when an entity.assets array changes.
Reselect allows you to provide custom equality definitions to your selectors.
import { defaultMemoize, createSelectorCreator } from 'reselect'
const compareByAssets = (a, b) => {
return a.every((element, index) => {
return element.assets === b[index].assets
});
};
const createAssetsComparatorSelector = createSelectorCreator(
defaultMemoize,
compareByAssets
);
const selectSceneEntitiesByAssetsComparator = createAssetsComparatorSelector((state) => {
//however you normally get entities for the pre-existing selectors
});
Now you can use this new selectSceneEntitiesByAssetsComparator in place of the previous selectSceneEntities in the above code you provided and it will only re-run when the equality check in compareByAssets fails.
Feel free to further update that comparator function if a strict comparison of assets === assets doesn't suite your needs.
As a proof of concept, I'd try to provide loadedAssets object to the result function by bypassing reselect identity checks.
// Keep a private selector instance
let cachedSelector;
export const selectEntitesWithAssetBuffers = function(){
// loadedAssets should be recalculated on each call?
const loadedAssets = getAssets(arguments);
// create selector on first call
if(cachedSelector === undefined) {
cachedSelector = createSelector(
selectSceneEntities,
entities => {
return entities.map(entity => {
entity.buffers = entity.assets.map((assetName) => {
return loadedAssets[assetName].arrayBuffer;
})
return entity;
})
}
)
}
// Return selector result
return cachedSelector(arguments);
}
Getting deeper memoization than what you've got is kind of a tricky problem because Reselect doesn't really support passing arguments to selectors. If you're returning an array from your selector, and the input used to build that array has changed, it's sort of the intended behavior from Reselect that you will need to recompute. See the advice in the readme for dynamic arguments.
Why is it that myCollection.find().fetch() returns an empty array [] even though the call is made within if(data){...}? Doesn't the if statement ensure that the collection has been retrieved before executing the console.log()?
Template.chart.rendered = function() {
var data = myCollection.find().fetch();
if(data) {
console.log(data);
}
$('#chart').render();
}
This returns [] in the browser Javascript console.
You could use count() instead which returns the number of results. data itself would be an empty array, [] which isn't falsey ( [] == true ).
Also don't use fetch() unless you're going to use the raw data for it because its quite taxing. You can loop through it with .forEach if you need to.
var data = myCollection.find();
if(data.count())
console.log(data);
//If you need it for something/Not sure if this is right but just an example
$('#chart').render(data.fetch())
The problem is that you have to wait for data from the server. When you just use Template.name.rendered function it is immediately invoked. You have to use Template.name.helpers function to wait for data from the server. Everything is described in the documentation.
It seems when you "remove autopublish" you have to also subscribe on the client.
if(Meteor.isClient) {
Meteor.startup(function() {
Myvars = new Mongo.Collection("myvars");
Meteor.subscribe('myvars')
});
}
and enable allow and publish on the server
if(Meteor.isServer) {
Meteor.startup(function () {
Myvars = new Mongo.Collection("myvars");
Myvars.allow({
insert: function () {
return true;
},
update: function () {
return true;
},
remove: function () {
return true;
}
});
if (Myvars.find().count() == 0) {
Myvars.insert({myvalue:'annoyed'});
}
Meteor.publish("myvars", function() {
return Myvars.find();
});
});
}
I'm new to this as well. I was just looking to have a global value that all clients could share. Seems like a useful idea (from a beginner's perspective) and a complete oversight on the Meteor teams behalf, it was nowhere clearly documented in this way. I also still have no idea what allow fetch is, that too is completely unclear in the official documentation.
It does, but in javascript you have the following strange behaviour
if ([]){
console.log('Oops it goes inside the if')
} // and it will output this, nontheless it is counter-intuitive
This happens because JS engine casts Boolean([]) to true. You can how different types are casted to Boolean here.
Check if your array is not empty in the beginning.
a = [];
if (a.length){
//do your thing
}