I'm writing a custom command getFirst, which returns the first element according to a given predicate. Everything works fine, but I wanted to specify a specific prevSubject parameter instead of true, in order to prevent improper usage.
In the documentation, the only mentioned options are false, true, optional, element, document or window, but it doesn't say how to specify an array-like structure, like the cy.each does.
Any idea how that could be done?
Here's my code:
Cypress.Commands.add('getFirst', {prevSubject: true},
<TSourceSubject, TPredicateSubject, TResult>(
subject: TSourceSubject,
getPredicateSubject : (sourceSubject : TSourceSubject) => Chainable<TPredicateSubject>,
predicate: (predicateSubject: TPredicateSubject) => boolean) => {
cy.wrap(subject).each((item : TSourceSubject) => {
getPredicateSubject(item).then<TPredicateSubject>((predicateSubject : any) => {
if (predicate(predicateSubject)) {
cy.wrap(item).as('getFirstItemAlias');
}
});
});
return cy.get('#getFirstItemAlias');
});
PS: If someone has an idea how to get rid of the getFirstItemAlias alias, or if there's some way to make its scope local, it would be very helpful too.
Thanks!
I was curious so I looked at Cypress' source code to see how this was done.
There aren't any additional undocumented options for prevSubject: https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cy/ensures.coffee#L21-L39
cy.each() is using { prevSubject: true } and explicitly checking that the subject is an array: https://github.com/cypress-io/cypress/blob/0f73bb7e1910296469f3f631a7f2303f4ecd035e/packages/driver/src/cy/commands/connectors.coffee#L374-L383
Related
I'm using Gatsbyjs to build a blog and I can't use the onCreatePage API to pass data from my graphql query into page templates.
My query grabs data from Kentico Cloud and it looks like this.
{
allKenticoCloudTypeBlogPost{
edges{
node{
contentItems{
elements{
url_slug{
value
}
}
}
}
}
}
}
This is a valid query and it returns data that looks like this.
The problem comes in my gatsby-node.js file where I want to utilize this query to build out pages using my predefined template.
Specifically in the createPage method which looks like this.
result.data.allKenticoCloudTypeBlogPost.edges.map(({node}) => {
createPage({
path: `${node.contentItems.elements.url_slug.value}`,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
slug: node.contentItems.elements.url_slug.value,
}
})
});
The error that displays is the following.
TypeError: Cannot read property 'url_slug' of undefined
gatsby-node.js:31 result.data.allKenticoCloudTypeBlogPost.edges.map
C:/Users/xxxx/Desktop/Marketing Repos/xxxx/gatsby-node.js:31:57
I decided to investigate doing a console.table on node.contentItems, as it appears as though the elements part is where it gets tripped up.
The result of console.table(node.contentItems) just before the createPage method is this.
It appears that node.contentItems has a member called url_slug rather than the elements member that I expected.
I thought I could then solve my problem by updating my createPage method call like so.
result.data.allKenticoCloudTypeBlogPost.edges.map(({node}) => {
console.table(node.contentItems);
createPage({
path: `${node.contentItems.url_slug.value}`,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
slug: node.contentItems.url_slug.value,
}
})
});
But then I get an error saying
TypeError: Cannot read property 'value' of undefined.
I truly don't understand how I can do a table log and see the url_slug member, but then when I try to access it, it says that it's undefined. All while I know that my query is correct because I can run it in graphiQL and get back the exact data I expect.
Any help would be appreciated. Thank you.
In your query result, node.contentItems is an array, even though you're trying to access it as if it's an object:
path: `${node.contentItems.elements.url_slug.value}`,
^^^^^^^^
console.log(contentItems) // [ { elements: {...} }, { elements: {...} }, ... ]
I think your confusion probably stems from the way console.table display data. It's confusing if you don't already know the shape of your data. Your screenshot says, this object has 4 properties with index 0 -> 3 (so likely an array), each has one property called elements (listed on table header), which is an object with the only property url_slug.
I'm not familiar with KenticoCloud, but maybe your posts are nested in contentItems, in which case you should loop over it:
result.data.allKenticoCloudTypeBlogPost.edges.map(({node}) => {
node.contentItems.forEach(({ elements }) => {
createPage({
path: elements.url_slug.value,
context: { slug: elements.url_slug.value },
component: ...
})
})
});
Is there a reason you are wrapping node with curly brackets in your map argument?
You might have already tried this, but my first intuition would be to do this instead:
result.data.allKenticoCloudTypeBlogPost.edges.map(node => {
console.log(node.contentItems)
createPage({
path: `${node.contentItems.elements.url_slug.value}`,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
slug: node.contentItems.elements.url_slug.value,
}
})
});
We are in the situation that the response of our GraphQL Query has to return some dynamic properties of an object. In our case we are not able to predefine all possible properties - so it has to be dynamic.
As we think there are two options to solve it.
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
/*
THIS is our special field which needs to return a dynamic object
*/
},
// ...
},
});
As you can see in the example code is element the property which has to return an object. A response when resolve this could be:
{
name: 'some name',
elements: {
an_unkonwn_key: {
some_nested_field: {
some_other: true,
},
},
another_unknown_prop: 'foo',
},
}
1) Return a "Any-Object"
We could just return any object - so GraphQL do not need to know which fields the Object has. When we tell GraphQL that the field is the type GraphQlObjectType it needs to define fields. Because of this it seems not to be possible to tell GraphQL that someone is just an Object.
Fo this we have changed it like this:
elements: {
type: new GraphQLObjectType({ name: 'elements' });
},
2) We could define dynamic field properties because its in an function
When we define fields as an function we could define our object dynamically. But the field function would need some information (in our case information which would be passed to elements) and we would need to access them to build the field object.
Example:
const MyType = new GraphQLObjectType({
name: 'SomeType',
fields: {
name: {
type: GraphQLString,
},
elements: {
type: new GraphQLObjectType({
name: 'elements',
fields: (argsFromElements) => {
// here we can now access keys from "args"
const fields = {};
argsFromElements.keys.forEach((key) => {
// some logic here ..
fields[someGeneratedProperty] = someGeneratedGraphQLType;
});
return fields;
},
}),
args: {
keys: {
type: new GraphQLList(GraphQLString),
},
},
},
// ...
},
});
This could work but the question would be if there is a way to pass the args and/or resolve object to the fields.
Question
So our question is now: Which way would be recommended in our case in GraphQL and is solution 1 or 2 possible ? Maybe there is another solution ?
Edit
Solution 1 would work when using the ScalarType. Example:
type: new GraphQLScalarType({
name: 'elements',
serialize(value) {
return value;
},
}),
I am not sure if this is a recommended way to solve our situation.
Neither option is really viable:
GraphQL is strongly typed. GraphQL.js doesn't support some kind of any field, and all types defined in your schema must have fields defined. If you look in the docs, fields is a required -- if you try to leave it out, you'll hit an error.
Args are used to resolve queries on a per-request basis. There's no way you can pass them back to your schema. You schema is supposed to be static.
As you suggest, it's possible to accomplish what you're trying to do by rolling your own customer Scalar. I think a simpler solution would be to just use JSON -- you can import a custom scalar for it like this one. Then just have your elements field resolve to a JSON object or array containing the dynamic fields. You could also manipulate the JSON object inside the resolver based on arguments if necessary (if you wanted to limit the fields returned to a subset as defined in the args, for example).
Word of warning: The issue with utilizing JSON, or any custom scalar that includes nested data, is that you're limiting the client's flexibility in requesting what it actually needs. It also results in less helpful errors on the client side -- I'd much rather be told that the field I requested doesn't exist or returned null when I make the request than to find out later down the line the JSON blob I got didn't include a field I expected it to.
One more possible solution could be to declare any such dynamic object as a string. And then pass a stringified version of the object as value to that object from your resolver functions. And then eventually you can parse that string to JSON again to make it again an object on the client side.
I'm not sure if its recommended way or not but I tried to make it work with this approach and it did work smoothly, so I'm sharing it here.
I'm using handsontable on an angular app with ngHandsontable. I can see that I need to set search to true in the table settings, which should make a query method available.
Can someone explain how I am able to access that method through my angular controller?
<input class="" id="handsonSearch" placeholder="Search..." ng-model="searchQuery" />
<hot-table settings="tableSettings.settings"
datarows="mappingData"
col-headers="true"
height="700">
<hot-column data="Column1" title="'Column One'"></hot-column>
</hot-table>
Angular
function GlobalMappingController($scope) {
$scope.tableSettings = {
settings: {
contextMenu: true,
colHeaders: true,
dropdownMenu: true,
afterChange: afterChange,
beforeChange: beforeChange,
search: true,
query: $scope.searchQuery
}
};
The issue is access to the root handsOnTable instance created by ngHandsOnTable isn't as intuitive as you might think, but in order to access all the functions handsOnTable has by itself you have to do something like this.
First, declare the variable we'll use as the instance of the table
$scope.handsOnTable;
You can access the root for handsOnTable by setting the afterInit parameter in the settings array
$scope.tableSettings = {
settings: {
contextMenu: true,
colHeaders: true,
dropdownMenu: true,
afterChange: afterChange,
beforeChange: beforeChange,
search: true,
query: $scope.searchQuery,
//insert this
afterInit: function() {
$scope.handsOnTable = this;
}
}
};
You can then set an ng-change on the input to something like this:
function search() {
$scope.handsOnTable.search.query($scope.searchQuery);
$scope.handsOnTable.render();
}
You'll also need to, in the same manner as the afterInit, set afterchange with the same statement. If you've got your own custom version of the function then just put it in after everything else has ran.
Keep in mind you'll have to keep the input box bound to searchQuery and add onChange="search()" but that should work - at least it did for me.
I am triying remove field in a big document, therefore I would like to do something:
collection.update({'_id' => #id}, {"$unset" => {'herField'})
But it is not possible. I don't want to rewrite entire document, any idea?
EDIT: I am using https://github.com/mongodb/mongo-ruby-driver
Your syntax looks slightly incorrect. As per docs:
collection.update( { _id: #id }, { $unset: { herField: true } }, { multi: true });
Need the 'multi' option if you want to update multiple documents. E.g. from all records on this collection.
http://docs.mongodb.org/manual/reference/operator/unset/#op._S_unset
I'm using multiple textboxes for users to entry different credit cards#, with jquery validations. Ambiguously, only the first text box validation is working. Validation's are not working for the other boxes. There are no js errors too in error console.
It'll be very helpful if someone can please give me a clue.
//for first textbox
$("#cust_reg").validate({
rules: {
cc_num_local: {
required: true,
creditcard: true
}
}
});
//for second textbox
$("#cust_reg").validate({
rules: {
cc_num_roam: {
required: true,
creditcard: true
}
}
});
the relevant html only: http://pastie.textmate.org/2422338
You can have more then one HTML element using the id of cust_reg. If you do then only one will be available to your JavaScript code since one supersedes the other. It also isn't valid HTML. You'll need to change the name of the second field to be something different like cust_reg2.
Since you have one form and two different ID's you might try combining them in your code.
$("#cust_reg").validate({
rules: {
cc_num_local: {
required: true,
creditcard: true
},
cc_num_roam: {
required: true,
creditcard: true //Not sure if this can be the same name
}
}
});