<query>.loading will not change to true - apollo

What are the possible reasons for query being stuck on loading = true (networkStatus = 1)?
I cannot get a query result on refetch and cannot log 'called2'
graphql(_stepQuery, {
name: 'stepQuery',
options: ({goalDocId}) => ({
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
variables: {
goalDocId
}
})
}
)
componentWillReceiveProps(nextProps) {
let stepIdsFromServer
if (nextProps.currentGoalSteps.length > this.props.currentGoalSteps.length) {
console.log('called')
this.props.stepQuery.refetch()
console.log('this.props', this.props)
console.log('nextProps',nextProps)
if (!nextProps.stepQuery.loading) {
// console.log('nextProps.stepQuery.allSteps', nextProps.stepQuery.allSteps)
console.log('called2')
}

This looks quite dangerous for a infinite loop.
First the refetch function is a Promise, so you will not be able to know the correct query state right after the call for refetching. You would need to go on in the .then function. See refetch Api.
Second the query in the end is executed inside the graphql wrapper Component. So you should not check the loading state and refetch in the componentWillReceiveProps function, Because when the query is executed again the whole component is instantiated again and will enter the componentWillReceiveProps function with resetted states and so on.
If you need some kind of search, i suggest you use a mutation as a workaround (using withApollo wrapper and in the componentWillReceiveProps you call this.props.client("MUTATION")), because this will not render the whole component.

Related

cypress .next at end of list yields '' but I need to break instead

I am writing Cypress tests for an application that has a dynamic group/list of items. Each item has a details link that when clicked creates a popup with said details. I need to close that popup and move to the next item in the group.
I first tried to use the .each() command on the group and then would .click({multiple:true}) the details but the popup would cover the the next click. Adding {foce:true} does allow all of the popups to display but I don't think that is in the spirit of how the application should function.
My latest attempt has been to create a custom command using .next() to iterate through the group. This works but when .next() reaches the end of the group it yields "" and so the test ultimately fails.
The actual error I get is:
Expected to find element: ``, but never found it. Queried from element: <div.groups.ng-star-inserted>
the .spec.ts
describe('Can select incentives and view details', () => {
it('Views incentive details', () => {
cy.optionPop('section#Incentives div.groups:first')
})
})
the index.ts
Cypress.Commands.add('optionPop', (clickable) => {
cy.get(clickable).find('[ng-reflect-track="Estimator, open_selection_dial"]').click()
cy.get('mat-dialog-container i.close').click()
cy.get(clickable).next().as('clicked').then(($clicked) => {
//fails at .next ^ because '' is yielded at end of list
cy.wrap($clicked).should('not.eq','')
})
cy.optionPop('#clicked')
})
You basically have the right idea, but it might work better in a plain JS function rather than a custom command.
function openPopups(clickables) {
if (clickables.length === 0) return // exit when array is empty
const clickable = clickables.pop() // extract one and reduce array
cy.wrap(clickable)
.find('[ng-reflect-track="Estimator, open_selection_dial"]').click()
cy.get('mat-dialog-container i.close')
.should('be.visible') // in case popup is slow
.click()
// wait for this popup to go, then proceed to next
cy.get('mat-dialog-container')
.should('not.be.visible')
.then(() => {
openPopups(clickables) // clickables now has one less item
})
}
cy.get('section#Incentives div.groups') // get all the popups
.then($popups => {
const popupsArray = Array.from($popups) // convert jQuery result to array
openPopups(popupsArray)
})
Some extra notes:
Using Array.from($popups) because we don't know how many in the list, and want to use array.pop() to grab each item and at the same time reduce the array (it's length will control the loop exit).
clickables is a list of raw elements, so cy.wrap(clickable) makes the individual element usable with Cypress commands like .find()
.should('be.visible') - when dealing with popup, the DOM is often altered by the click event that opens it, which can be slow relative to the speed the test runs at. Adding .should('be.visible') is a guard to make sure the test is not flaky on some runs (e.g if using CI)
.should('not.be.visible').then(() => ... - since you have some problems with multiple overlapping popups this will ensure each popup has gone before testing the next one.

Compare two values and make decision in Cypress

So I have two values on a page that I need to compare and as per the result perform some actions.
//First Block
cy.get('selctor1').invoke('text').then(somevalue => {
cy.get('selector2').should('have.text', somevalue).then(() => {
#Do Something when equal
})
})
//Second Block
cy.get('selctor1').invoke('text').then(somevalue => {
cy.get('selector2').should('not.have.text', somevalue).then(() => {
#Do Something when not equal
})
})
So for the positive case when both values are equal everything works fine. But for the case when two values are not equal, it's only checking the first block and fails. What should I do so that it executes the second block when values are not equal and not the first block?
Sorry for not being clear the first time. Here is my edited answer:
Then vs Should:
Try to avoid then where possible. then is not repeatable and will introduce unexpected behaviour.
But also will should introduce unexpeced behaviour.
Example for a bad usage of then:
describe("asd", () => {
it("fails but retries", () =>{
console.log("######### first test")
cy.wrap({ fn: () => console.log(new Date())})
.invoke("fn")
.should(r => expect(r).to.eq(true));
})
it("fails but retries not", () =>{
console.log("######### next test")
cy.wrap({ fn: () => console.log(new Date())})
.invoke("fn")
.then(r => {
expect(r).to.eq(true)
});
})
})
In this example you see the same code twice but the first block uses should while the second block uses then. The assertion must fail but in the first block, the assertion is repeated. Open the DEV COnsole to see many retries for the first block but no retry in the second.
This is what I mean by "unexpected" behaviour. Let's say, you wrap a object that is dynamically extended (maybe by a UI action) and you are expecting a property on this object. In the second block (then) the UI acton must be executed very fast and before thethenis executed so that theexpect` does not fail.
In the should case, you have 4 seconds (in case of `defaultCommandTimeout is not overwritten) left until the assert will finally fail.
Bad usage of should:
describe("ad", () => {
it("test", () => {
cy.visit("https://www.cypress.io/")
cy.get("*[aria-label='pricing']")
.invoke('text').should(someValue => {
cy.get("asdad", {timeout: 5000}).should("not.exist");
})
})
})
What would you expect? A green test? No, this test fails:
Why is this the case? Because get introduces an implicit assert "should exist" (see: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Default-Assertions ).
Should with callback skips the default assertion (see: https://docs.cypress.io/api/commands/should.html#Notes ).I think they skip it by toggling it by flag. This could have the effect of reversing the flag again and thus forces cypress to check if "asdad" does exist even though we use should not exist.
There is an issue for this stuff: https://github.com/cypress-io/cypress/issues/5963
I do not know why cy.log has the behaviour you mentioned in your case. So either you use then if you want to use cy commands within then callback or you avoid the usage of cy commands and use should with explicit assertions (expect). Maybe after that issue is fixed, cy.log also can be used.
Old Answer:
cy.get('selctor1').invoke('text').should(someValue => {
const $el = Cypress.$('selector2');
if ($el.text() ==== someValue) {
// positive
expect()....
} else {
// negative
expect()....
}
})
You can use should with a callback. This callback (and the previous invoke command) is executed as long as the timeout is reached or no assertion fails.
You always can use the raw jQuery object to work with. This depends on whether or not you need all the checks cypress is executing during a get().
Please let me know if you need further assistance.

Baqend onUpdate Handler

Will doing partialupdate() cause code in a data class' onUpdate Handler to run?
I have this setup in the data class:
exports.onUpdate = function(db, obj) {
DB.log.info(obj.ShiftID);
db.Shifts.load(obj.ShiftID)
.then((Shift) => {
DB.log.info(Shift);
if (Shift.User == db.User.me) {
Shift.User = null;
Shift.status = 0;
return Shift.update();
}
})
};
(yes, role 2 for node has permissions to query and update the Shifts data class)
But I am getting zero logs when I make a partialupdate(). Do I need to do a real update query...load the object, modify the data, update()?
Also it seems that this code causes the partialupdate() to not run at all, but when I delete the handler, it starts working again.
Yes, that is currently an unimplemented feature since a partial update can't execute an onUpdate handler since there is no object which can be passed to the update handler.
On the other hand, a partial update can't be executed directly since that will result in a security issue (since your onUpdate handler can contain validation code etc.)
So we currently reject any partial update on a class which has an onUpdate handler because there doesn't exist a way how we can actually validate the partial update against your onUpdate code.
We have planned that you can define an extra onPartial handler where you can take some extra steps before the partialUpdate is executed. But that handler will only get the partial update and not the object itself.
I'm pretty sure that partialupdate() will not cause the onUpdate Handler to run.
When I put the log line in and edit the records using website data manager it does log as expected. Not a big deal, I can just rewrite the query to be a full update.
BUT having any code in there does break partialupdate() which is not good.
Here is the code I'm using that works as long as there is nothing in the onUpdateHandler:
requestShift(shiftID) {
db.ready().then((db) => {
db.Applicants.find()
.where({
"shiftID": { "$in": [shiftID] },
})
.singleResult((applicants) => {
return applicants.partialUpdate()
.add("applicants", db.User.me.id)
.add("photos", this.props.UserData.photo)
.execute()
})
Alert.alert(
'Confirmation',
'Shift has been requested.',
)
this.props.navigation.dispatch(goToFindShifts)
})
}

apollo react: proper way to switch a query's params

In my app I have a sidebar with a list of "saved searches" and a central area that should show the results of a search. Whenever I click on a saved search link, I want to update the central area with the results of that search.
What is the proper way to do this with apollo-react?
I tried with this:
// SidebarConnector.js:
const withVideoSearch = graphql(
VIDEO_SEARCH_QUERY,
{
name: 'videoSearchQuery',
props: ({ videoSearchQuery }) => {
return ({
searchVideos: videoSearchQuery.refetch,
});
},
}
);
export default withVideoSearch(Sidebar);
My saved searches are doing a searchVideos({ query: "some query" }) on click which, based on the above, is doing a refetch for the VIDEO_SEARCH_QUERY query with different variables.
This works fine, the call is made to the graphql server and results are returned just fine.
For the main component that shows the list of results I use:
export default graphql(VIDEO_SEARCH_QUERY)(ResultList);
Initially the main component gets its results from the server as if the query was done without variables which is fine, exactly how I want it.
The problem is that every refetch seems to create a different entry in ROOT_QUERY in apollo's store and my main component is "locked" into the one without variables.
Here's what apollo's store looks like after the initial fetch and one of the refetches triggered from a saved search:
ROOT_QUERY
searchVideos({"query":"coke"}): [Video]
0:▾Video:arLaecAu5ns
searchVideos({"query":null}): [Video]
0:▾Video:3HXg-oVMA0c
So my question is how to either switch the main component to the "current search" or how to overwrite the store on every refresh so that there's only one key so the main component updates correctly.
For completeness here's my VIDEO_SEARCH_QUERY:
export const VIDEO_SEARCH_QUERY = gql`
query searchVideos($query: String) {
searchVideos(query: $query) {
...fVideo
}
}
${fVideo}
`;
Maybe I'm misunderstanding your use case, but it seems like there's no need to utilize refetch here. It would be simpler to persist whatever the selected search string is as state, pass that state down as a prop to your main component and then just use that prop as the variable in your GraphQL request. So the graphql call inside your ResultList component would look something like this:
const options = props => ({ variables: { query: props.searchString } })
export default graphql(VIDEO_SEARCH_QUERY, { options })(ResultList);
Then just have your onClick handler for each saved search set the state to whatever that search string is, and Apollo will do the rest. This is super easy with Redux -- just fire off the appropriate action. If you're not using Redux, you may have to lift the state up so it can then be passed down as a prop, but the concept is the same.

Ember Data and Handsontable recursion

When a Handsontable is instantiated, it calls a recursive method to build a data schema: https://github.com/handsontable/handsontable/blob/be8654f78ca84efc982047ca6b399e6c6d99f893/src/dataMap.js#L28, which in turns calls objectEach: https://github.com/handsontable/handsontable/blob/master/src/helpers/object.js#L235-L245
However, with an Ember Data record, it tries to iterate over properties like store, which means it gets caught in an endless loop.
Is there any way to bypass the recursiveDuckSchema method?
Unless Handsontable has some interface to allow pre-parsing data before it reaches the core of the plugin, I would say you may have better luck by transforming your ember-data models into something handsometable understands.
let queryParams = //Your query params
let data = this.get('getTheContent'); //Your models
let handsomeData = data.map(function(item, index, enumerable)){
return { id: item.get('id'), name: item.get('name'), other: item.get('other') }
};
// Result is [{id: 1, name: 'John', other: 'Other'}, {...}]